Move legacy device tracker setup to legacy module (#43447)
This commit is contained in:
parent
8779592952
commit
a745594712
3 changed files with 341 additions and 339 deletions
|
@ -1,24 +1,18 @@
|
|||
"""Provide functionality to keep track of devices."""
|
||||
import asyncio
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_GPS_ACCURACY, STATE_HOME
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_utc_time_change
|
||||
from homeassistant.helpers.typing import ConfigType, GPSType, HomeAssistantType
|
||||
from homeassistant.const import ( # noqa: F401 pylint: disable=unused-import
|
||||
ATTR_GPS_ACCURACY,
|
||||
STATE_HOME,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
from homeassistant.loader import bind_hass
|
||||
|
||||
from . import legacy, setup
|
||||
from .config_entry import ( # noqa: F401 pylint: disable=unused-import
|
||||
async_setup_entry,
|
||||
async_unload_entry,
|
||||
)
|
||||
from .const import (
|
||||
from .const import ( # noqa: F401 pylint: disable=unused-import
|
||||
ATTR_ATTRIBUTES,
|
||||
ATTR_BATTERY,
|
||||
ATTR_CONSIDER_HOME,
|
||||
ATTR_DEV_ID,
|
||||
ATTR_GPS,
|
||||
ATTR_HOST_NAME,
|
||||
|
@ -29,60 +23,21 @@ from .const import (
|
|||
CONF_NEW_DEVICE_DEFAULTS,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_TRACK_NEW,
|
||||
DEFAULT_CONSIDER_HOME,
|
||||
DEFAULT_TRACK_NEW,
|
||||
DOMAIN,
|
||||
PLATFORM_TYPE_LEGACY,
|
||||
SOURCE_TYPE_BLUETOOTH,
|
||||
SOURCE_TYPE_BLUETOOTH_LE,
|
||||
SOURCE_TYPE_GPS,
|
||||
SOURCE_TYPE_ROUTER,
|
||||
)
|
||||
from .legacy import DeviceScanner # noqa: F401 pylint: disable=unused-import
|
||||
|
||||
SERVICE_SEE = "see"
|
||||
|
||||
SOURCE_TYPES = (
|
||||
SOURCE_TYPE_GPS,
|
||||
SOURCE_TYPE_ROUTER,
|
||||
SOURCE_TYPE_BLUETOOTH,
|
||||
SOURCE_TYPE_BLUETOOTH_LE,
|
||||
)
|
||||
|
||||
NEW_DEVICE_DEFAULTS_SCHEMA = vol.Any(
|
||||
None,
|
||||
vol.Schema({vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean}),
|
||||
)
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_SCAN_INTERVAL): cv.time_period,
|
||||
vol.Optional(CONF_TRACK_NEW): cv.boolean,
|
||||
vol.Optional(CONF_CONSIDER_HOME, default=DEFAULT_CONSIDER_HOME): vol.All(
|
||||
cv.time_period, cv.positive_timedelta
|
||||
),
|
||||
vol.Optional(CONF_NEW_DEVICE_DEFAULTS, default={}): NEW_DEVICE_DEFAULTS_SCHEMA,
|
||||
}
|
||||
)
|
||||
PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema)
|
||||
SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema(
|
||||
vol.All(
|
||||
cv.has_at_least_one_key(ATTR_MAC, ATTR_DEV_ID),
|
||||
{
|
||||
ATTR_MAC: cv.string,
|
||||
ATTR_DEV_ID: cv.string,
|
||||
ATTR_HOST_NAME: cv.string,
|
||||
ATTR_LOCATION_NAME: cv.string,
|
||||
ATTR_GPS: cv.gps,
|
||||
ATTR_GPS_ACCURACY: cv.positive_int,
|
||||
ATTR_BATTERY: cv.positive_int,
|
||||
ATTR_ATTRIBUTES: dict,
|
||||
ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES),
|
||||
ATTR_CONSIDER_HOME: cv.time_period,
|
||||
# Temp workaround for iOS app introduced in 0.65
|
||||
vol.Optional("battery_status"): str,
|
||||
vol.Optional("hostname"): str,
|
||||
},
|
||||
)
|
||||
from .legacy import ( # noqa: F401 pylint: disable=unused-import
|
||||
PLATFORM_SCHEMA,
|
||||
PLATFORM_SCHEMA_BASE,
|
||||
SERVICE_SEE,
|
||||
SERVICE_SEE_PAYLOAD_SCHEMA,
|
||||
SOURCE_TYPES,
|
||||
DeviceScanner,
|
||||
async_setup_integration as async_setup_legacy_integration,
|
||||
see,
|
||||
)
|
||||
|
||||
|
||||
|
@ -92,78 +47,8 @@ def is_on(hass: HomeAssistantType, entity_id: str):
|
|||
return hass.states.is_state(entity_id, STATE_HOME)
|
||||
|
||||
|
||||
def see(
|
||||
hass: HomeAssistantType,
|
||||
mac: str = None,
|
||||
dev_id: str = None,
|
||||
host_name: str = None,
|
||||
location_name: str = None,
|
||||
gps: GPSType = None,
|
||||
gps_accuracy=None,
|
||||
battery: int = None,
|
||||
attributes: dict = None,
|
||||
):
|
||||
"""Call service to notify you see device."""
|
||||
data = {
|
||||
key: value
|
||||
for key, value in (
|
||||
(ATTR_MAC, mac),
|
||||
(ATTR_DEV_ID, dev_id),
|
||||
(ATTR_HOST_NAME, host_name),
|
||||
(ATTR_LOCATION_NAME, location_name),
|
||||
(ATTR_GPS, gps),
|
||||
(ATTR_GPS_ACCURACY, gps_accuracy),
|
||||
(ATTR_BATTERY, battery),
|
||||
)
|
||||
if value is not None
|
||||
}
|
||||
if attributes:
|
||||
data[ATTR_ATTRIBUTES] = attributes
|
||||
hass.services.call(DOMAIN, SERVICE_SEE, data)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
"""Set up the device tracker."""
|
||||
tracker = await legacy.get_tracker(hass, config)
|
||||
await async_setup_legacy_integration(hass, config)
|
||||
|
||||
legacy_platforms = await setup.async_extract_config(hass, config)
|
||||
|
||||
setup_tasks = [
|
||||
legacy_platform.async_setup_legacy(hass, tracker)
|
||||
for legacy_platform in legacy_platforms
|
||||
]
|
||||
|
||||
if setup_tasks:
|
||||
await asyncio.wait(setup_tasks)
|
||||
|
||||
async def async_platform_discovered(p_type, info):
|
||||
"""Load a platform."""
|
||||
platform = await setup.async_create_platform_type(hass, config, p_type, {})
|
||||
|
||||
if platform is None or platform.type != PLATFORM_TYPE_LEGACY:
|
||||
return
|
||||
|
||||
await platform.async_setup_legacy(hass, tracker, info)
|
||||
|
||||
discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
|
||||
|
||||
# Clean up stale devices
|
||||
async_track_utc_time_change(
|
||||
hass, tracker.async_update_stale, second=range(0, 60, 5)
|
||||
)
|
||||
|
||||
async def async_see_service(call):
|
||||
"""Service to see a device."""
|
||||
# Temp workaround for iOS, introduced in 0.65
|
||||
data = dict(call.data)
|
||||
data.pop("hostname", None)
|
||||
data.pop("battery_status", None)
|
||||
await tracker.async_see(**data)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA
|
||||
)
|
||||
|
||||
# restore
|
||||
await tracker.async_setup_tracked_device()
|
||||
return True
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
import asyncio
|
||||
from datetime import timedelta
|
||||
import hashlib
|
||||
from typing import Any, List, Sequence
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import util
|
||||
|
@ -25,32 +27,343 @@ from homeassistant.const import (
|
|||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_per_platform, discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_registry import async_get_registry
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_time_interval,
|
||||
async_track_utc_time_change,
|
||||
)
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import GPSType, HomeAssistantType
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers.typing import ConfigType, GPSType, HomeAssistantType
|
||||
from homeassistant.setup import async_prepare_setup_platform
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.yaml import dump
|
||||
|
||||
from .const import (
|
||||
ATTR_ATTRIBUTES,
|
||||
ATTR_BATTERY,
|
||||
ATTR_CONSIDER_HOME,
|
||||
ATTR_DEV_ID,
|
||||
ATTR_GPS,
|
||||
ATTR_HOST_NAME,
|
||||
ATTR_LOCATION_NAME,
|
||||
ATTR_MAC,
|
||||
ATTR_SOURCE_TYPE,
|
||||
CONF_CONSIDER_HOME,
|
||||
CONF_NEW_DEVICE_DEFAULTS,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_TRACK_NEW,
|
||||
DEFAULT_CONSIDER_HOME,
|
||||
DEFAULT_TRACK_NEW,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
PLATFORM_TYPE_LEGACY,
|
||||
SCAN_INTERVAL,
|
||||
SOURCE_TYPE_BLUETOOTH,
|
||||
SOURCE_TYPE_BLUETOOTH_LE,
|
||||
SOURCE_TYPE_GPS,
|
||||
SOURCE_TYPE_ROUTER,
|
||||
)
|
||||
|
||||
SERVICE_SEE = "see"
|
||||
|
||||
SOURCE_TYPES = (
|
||||
SOURCE_TYPE_GPS,
|
||||
SOURCE_TYPE_ROUTER,
|
||||
SOURCE_TYPE_BLUETOOTH,
|
||||
SOURCE_TYPE_BLUETOOTH_LE,
|
||||
)
|
||||
|
||||
NEW_DEVICE_DEFAULTS_SCHEMA = vol.Any(
|
||||
None,
|
||||
vol.Schema({vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean}),
|
||||
)
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_SCAN_INTERVAL): cv.time_period,
|
||||
vol.Optional(CONF_TRACK_NEW): cv.boolean,
|
||||
vol.Optional(CONF_CONSIDER_HOME, default=DEFAULT_CONSIDER_HOME): vol.All(
|
||||
cv.time_period, cv.positive_timedelta
|
||||
),
|
||||
vol.Optional(CONF_NEW_DEVICE_DEFAULTS, default={}): NEW_DEVICE_DEFAULTS_SCHEMA,
|
||||
}
|
||||
)
|
||||
PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema)
|
||||
|
||||
SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema(
|
||||
vol.All(
|
||||
cv.has_at_least_one_key(ATTR_MAC, ATTR_DEV_ID),
|
||||
{
|
||||
ATTR_MAC: cv.string,
|
||||
ATTR_DEV_ID: cv.string,
|
||||
ATTR_HOST_NAME: cv.string,
|
||||
ATTR_LOCATION_NAME: cv.string,
|
||||
ATTR_GPS: cv.gps,
|
||||
ATTR_GPS_ACCURACY: cv.positive_int,
|
||||
ATTR_BATTERY: cv.positive_int,
|
||||
ATTR_ATTRIBUTES: dict,
|
||||
ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES),
|
||||
ATTR_CONSIDER_HOME: cv.time_period,
|
||||
# Temp workaround for iOS app introduced in 0.65
|
||||
vol.Optional("battery_status"): str,
|
||||
vol.Optional("hostname"): str,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
YAML_DEVICES = "known_devices.yaml"
|
||||
EVENT_NEW_DEVICE = "device_tracker_new_device"
|
||||
|
||||
|
||||
def see(
|
||||
hass: HomeAssistantType,
|
||||
mac: str = None,
|
||||
dev_id: str = None,
|
||||
host_name: str = None,
|
||||
location_name: str = None,
|
||||
gps: GPSType = None,
|
||||
gps_accuracy=None,
|
||||
battery: int = None,
|
||||
attributes: dict = None,
|
||||
):
|
||||
"""Call service to notify you see device."""
|
||||
data = {
|
||||
key: value
|
||||
for key, value in (
|
||||
(ATTR_MAC, mac),
|
||||
(ATTR_DEV_ID, dev_id),
|
||||
(ATTR_HOST_NAME, host_name),
|
||||
(ATTR_LOCATION_NAME, location_name),
|
||||
(ATTR_GPS, gps),
|
||||
(ATTR_GPS_ACCURACY, gps_accuracy),
|
||||
(ATTR_BATTERY, battery),
|
||||
)
|
||||
if value is not None
|
||||
}
|
||||
if attributes:
|
||||
data[ATTR_ATTRIBUTES] = attributes
|
||||
hass.services.call(DOMAIN, SERVICE_SEE, data)
|
||||
|
||||
|
||||
async def async_setup_integration(hass: HomeAssistantType, config: ConfigType) -> None:
|
||||
"""Set up the legacy integration."""
|
||||
tracker = await get_tracker(hass, config)
|
||||
|
||||
legacy_platforms = await async_extract_config(hass, config)
|
||||
|
||||
setup_tasks = [
|
||||
legacy_platform.async_setup_legacy(hass, tracker)
|
||||
for legacy_platform in legacy_platforms
|
||||
]
|
||||
|
||||
if setup_tasks:
|
||||
await asyncio.wait(setup_tasks)
|
||||
|
||||
async def async_platform_discovered(p_type, info):
|
||||
"""Load a platform."""
|
||||
platform = await async_create_platform_type(hass, config, p_type, {})
|
||||
|
||||
if platform is None or platform.type != PLATFORM_TYPE_LEGACY:
|
||||
return
|
||||
|
||||
await platform.async_setup_legacy(hass, tracker, info)
|
||||
|
||||
discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
|
||||
|
||||
# Clean up stale devices
|
||||
async_track_utc_time_change(
|
||||
hass, tracker.async_update_stale, second=range(0, 60, 5)
|
||||
)
|
||||
|
||||
async def async_see_service(call):
|
||||
"""Service to see a device."""
|
||||
# Temp workaround for iOS, introduced in 0.65
|
||||
data = dict(call.data)
|
||||
data.pop("hostname", None)
|
||||
data.pop("battery_status", None)
|
||||
await tracker.async_see(**data)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA
|
||||
)
|
||||
|
||||
# restore
|
||||
await tracker.async_setup_tracked_device()
|
||||
|
||||
|
||||
@attr.s
|
||||
class DeviceTrackerPlatform:
|
||||
"""Class to hold platform information."""
|
||||
|
||||
LEGACY_SETUP = (
|
||||
"async_get_scanner",
|
||||
"get_scanner",
|
||||
"async_setup_scanner",
|
||||
"setup_scanner",
|
||||
)
|
||||
|
||||
name: str = attr.ib()
|
||||
platform: ModuleType = attr.ib()
|
||||
config: Dict = attr.ib()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""Return platform type."""
|
||||
for methods, platform_type in ((self.LEGACY_SETUP, PLATFORM_TYPE_LEGACY),):
|
||||
for meth in methods:
|
||||
if hasattr(self.platform, meth):
|
||||
return platform_type
|
||||
|
||||
return None
|
||||
|
||||
async def async_setup_legacy(self, hass, tracker, discovery_info=None):
|
||||
"""Set up a legacy platform."""
|
||||
LOGGER.info("Setting up %s.%s", DOMAIN, self.type)
|
||||
try:
|
||||
scanner = None
|
||||
setup = None
|
||||
if hasattr(self.platform, "async_get_scanner"):
|
||||
scanner = await self.platform.async_get_scanner(
|
||||
hass, {DOMAIN: self.config}
|
||||
)
|
||||
elif hasattr(self.platform, "get_scanner"):
|
||||
scanner = await hass.async_add_executor_job(
|
||||
self.platform.get_scanner, hass, {DOMAIN: self.config}
|
||||
)
|
||||
elif hasattr(self.platform, "async_setup_scanner"):
|
||||
setup = await self.platform.async_setup_scanner(
|
||||
hass, self.config, tracker.async_see, discovery_info
|
||||
)
|
||||
elif hasattr(self.platform, "setup_scanner"):
|
||||
setup = await hass.async_add_executor_job(
|
||||
self.platform.setup_scanner,
|
||||
hass,
|
||||
self.config,
|
||||
tracker.see,
|
||||
discovery_info,
|
||||
)
|
||||
else:
|
||||
raise HomeAssistantError("Invalid legacy device_tracker platform.")
|
||||
|
||||
if scanner:
|
||||
async_setup_scanner_platform(
|
||||
hass, self.config, scanner, tracker.async_see, self.type
|
||||
)
|
||||
return
|
||||
|
||||
if not setup:
|
||||
LOGGER.error("Error setting up platform %s", self.type)
|
||||
return
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
LOGGER.exception("Error setting up platform %s", self.type)
|
||||
|
||||
|
||||
async def async_extract_config(hass, config):
|
||||
"""Extract device tracker config and split between legacy and modern."""
|
||||
legacy = []
|
||||
|
||||
for platform in await asyncio.gather(
|
||||
*(
|
||||
async_create_platform_type(hass, config, p_type, p_config)
|
||||
for p_type, p_config in config_per_platform(config, DOMAIN)
|
||||
)
|
||||
):
|
||||
if platform is None:
|
||||
continue
|
||||
|
||||
if platform.type == PLATFORM_TYPE_LEGACY:
|
||||
legacy.append(platform)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unable to determine type for {platform.name}: {platform.type}"
|
||||
)
|
||||
|
||||
return legacy
|
||||
|
||||
|
||||
async def async_create_platform_type(
|
||||
hass, config, p_type, p_config
|
||||
) -> Optional[DeviceTrackerPlatform]:
|
||||
"""Determine type of platform."""
|
||||
platform = await async_prepare_setup_platform(hass, config, DOMAIN, p_type)
|
||||
|
||||
if platform is None:
|
||||
return None
|
||||
|
||||
return DeviceTrackerPlatform(p_type, platform, p_config)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_scanner_platform(
|
||||
hass: HomeAssistantType,
|
||||
config: ConfigType,
|
||||
scanner: Any,
|
||||
async_see_device: Callable,
|
||||
platform: str,
|
||||
):
|
||||
"""Set up the connect scanner-based platform to device tracker.
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
|
||||
update_lock = asyncio.Lock()
|
||||
scanner.hass = hass
|
||||
|
||||
# Initial scan of each mac we also tell about host name for config
|
||||
seen: Any = set()
|
||||
|
||||
async def async_device_tracker_scan(now: dt_util.dt.datetime):
|
||||
"""Handle interval matches."""
|
||||
if update_lock.locked():
|
||||
LOGGER.warning(
|
||||
"Updating device list from %s took longer than the scheduled "
|
||||
"scan interval %s",
|
||||
platform,
|
||||
interval,
|
||||
)
|
||||
return
|
||||
|
||||
async with update_lock:
|
||||
found_devices = await scanner.async_scan_devices()
|
||||
|
||||
for mac in found_devices:
|
||||
if mac in seen:
|
||||
host_name = None
|
||||
else:
|
||||
host_name = await scanner.async_get_device_name(mac)
|
||||
seen.add(mac)
|
||||
|
||||
try:
|
||||
extra_attributes = await scanner.async_get_extra_attributes(mac)
|
||||
except NotImplementedError:
|
||||
extra_attributes = {}
|
||||
|
||||
kwargs = {
|
||||
"mac": mac,
|
||||
"host_name": host_name,
|
||||
"source_type": SOURCE_TYPE_ROUTER,
|
||||
"attributes": {
|
||||
"scanner": scanner.__class__.__name__,
|
||||
**extra_attributes,
|
||||
},
|
||||
}
|
||||
|
||||
zone_home = hass.states.get(hass.components.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_create_task(async_see_device(**kwargs))
|
||||
|
||||
async_track_time_interval(hass, async_device_tracker_scan, interval)
|
||||
hass.async_create_task(async_device_tracker_scan(None))
|
||||
|
||||
|
||||
async def get_tracker(hass, config):
|
||||
"""Create a tracker."""
|
||||
yaml_path = hass.config.path(YAML_DEVICES)
|
||||
|
@ -349,17 +662,17 @@ class Device(RestoreEntity):
|
|||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
attr = {ATTR_SOURCE_TYPE: self.source_type}
|
||||
attributes = {ATTR_SOURCE_TYPE: self.source_type}
|
||||
|
||||
if self.gps:
|
||||
attr[ATTR_LATITUDE] = self.gps[0]
|
||||
attr[ATTR_LONGITUDE] = self.gps[1]
|
||||
attr[ATTR_GPS_ACCURACY] = self.gps_accuracy
|
||||
attributes[ATTR_LATITUDE] = self.gps[0]
|
||||
attributes[ATTR_LONGITUDE] = self.gps[1]
|
||||
attributes[ATTR_GPS_ACCURACY] = self.gps_accuracy
|
||||
|
||||
if self.battery:
|
||||
attr[ATTR_BATTERY] = self.battery
|
||||
attributes[ATTR_BATTERY] = self.battery
|
||||
|
||||
return attr
|
||||
return attributes
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
@ -453,13 +766,13 @@ class Device(RestoreEntity):
|
|||
self.last_update_home = state.state == STATE_HOME
|
||||
self.last_seen = dt_util.utcnow()
|
||||
|
||||
for attr, var in (
|
||||
for attribute, var in (
|
||||
(ATTR_SOURCE_TYPE, "source_type"),
|
||||
(ATTR_GPS_ACCURACY, "gps_accuracy"),
|
||||
(ATTR_BATTERY, "battery"),
|
||||
):
|
||||
if attr in state.attributes:
|
||||
setattr(self, var, state.attributes[attr])
|
||||
if attribute in state.attributes:
|
||||
setattr(self, var, state.attributes[attribute])
|
||||
|
||||
if ATTR_LONGITUDE in state.attributes:
|
||||
self.gps = (
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
"""Device tracker helpers."""
|
||||
import asyncio
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
import attr
|
||||
|
||||
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_per_platform
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
from homeassistant.setup import async_prepare_setup_platform
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
CONF_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
PLATFORM_TYPE_LEGACY,
|
||||
SCAN_INTERVAL,
|
||||
SOURCE_TYPE_ROUTER,
|
||||
)
|
||||
|
||||
|
||||
@attr.s
|
||||
class DeviceTrackerPlatform:
|
||||
"""Class to hold platform information."""
|
||||
|
||||
LEGACY_SETUP = (
|
||||
"async_get_scanner",
|
||||
"get_scanner",
|
||||
"async_setup_scanner",
|
||||
"setup_scanner",
|
||||
)
|
||||
|
||||
name: str = attr.ib()
|
||||
platform: ModuleType = attr.ib()
|
||||
config: Dict = attr.ib()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""Return platform type."""
|
||||
for methods, platform_type in ((self.LEGACY_SETUP, PLATFORM_TYPE_LEGACY),):
|
||||
for meth in methods:
|
||||
if hasattr(self.platform, meth):
|
||||
return platform_type
|
||||
|
||||
return None
|
||||
|
||||
async def async_setup_legacy(self, hass, tracker, discovery_info=None):
|
||||
"""Set up a legacy platform."""
|
||||
LOGGER.info("Setting up %s.%s", DOMAIN, self.type)
|
||||
try:
|
||||
scanner = None
|
||||
setup = None
|
||||
if hasattr(self.platform, "async_get_scanner"):
|
||||
scanner = await self.platform.async_get_scanner(
|
||||
hass, {DOMAIN: self.config}
|
||||
)
|
||||
elif hasattr(self.platform, "get_scanner"):
|
||||
scanner = await hass.async_add_executor_job(
|
||||
self.platform.get_scanner, hass, {DOMAIN: self.config}
|
||||
)
|
||||
elif hasattr(self.platform, "async_setup_scanner"):
|
||||
setup = await self.platform.async_setup_scanner(
|
||||
hass, self.config, tracker.async_see, discovery_info
|
||||
)
|
||||
elif hasattr(self.platform, "setup_scanner"):
|
||||
setup = await hass.async_add_executor_job(
|
||||
self.platform.setup_scanner,
|
||||
hass,
|
||||
self.config,
|
||||
tracker.see,
|
||||
discovery_info,
|
||||
)
|
||||
else:
|
||||
raise HomeAssistantError("Invalid legacy device_tracker platform.")
|
||||
|
||||
if scanner:
|
||||
async_setup_scanner_platform(
|
||||
hass, self.config, scanner, tracker.async_see, self.type
|
||||
)
|
||||
return
|
||||
|
||||
if not setup:
|
||||
LOGGER.error("Error setting up platform %s", self.type)
|
||||
return
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
LOGGER.exception("Error setting up platform %s", self.type)
|
||||
|
||||
|
||||
async def async_extract_config(hass, config):
|
||||
"""Extract device tracker config and split between legacy and modern."""
|
||||
legacy = []
|
||||
|
||||
for platform in await asyncio.gather(
|
||||
*(
|
||||
async_create_platform_type(hass, config, p_type, p_config)
|
||||
for p_type, p_config in config_per_platform(config, DOMAIN)
|
||||
)
|
||||
):
|
||||
if platform is None:
|
||||
continue
|
||||
|
||||
if platform.type == PLATFORM_TYPE_LEGACY:
|
||||
legacy.append(platform)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unable to determine type for {platform.name}: {platform.type}"
|
||||
)
|
||||
|
||||
return legacy
|
||||
|
||||
|
||||
async def async_create_platform_type(
|
||||
hass, config, p_type, p_config
|
||||
) -> Optional[DeviceTrackerPlatform]:
|
||||
"""Determine type of platform."""
|
||||
platform = await async_prepare_setup_platform(hass, config, DOMAIN, p_type)
|
||||
|
||||
if platform is None:
|
||||
return None
|
||||
|
||||
return DeviceTrackerPlatform(p_type, platform, p_config)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_scanner_platform(
|
||||
hass: HomeAssistantType,
|
||||
config: ConfigType,
|
||||
scanner: Any,
|
||||
async_see_device: Callable,
|
||||
platform: str,
|
||||
):
|
||||
"""Set up the connect scanner-based platform to device tracker.
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
|
||||
update_lock = asyncio.Lock()
|
||||
scanner.hass = hass
|
||||
|
||||
# Initial scan of each mac we also tell about host name for config
|
||||
seen: Any = set()
|
||||
|
||||
async def async_device_tracker_scan(now: dt_util.dt.datetime):
|
||||
"""Handle interval matches."""
|
||||
if update_lock.locked():
|
||||
LOGGER.warning(
|
||||
"Updating device list from %s took longer than the scheduled "
|
||||
"scan interval %s",
|
||||
platform,
|
||||
interval,
|
||||
)
|
||||
return
|
||||
|
||||
async with update_lock:
|
||||
found_devices = await scanner.async_scan_devices()
|
||||
|
||||
for mac in found_devices:
|
||||
if mac in seen:
|
||||
host_name = None
|
||||
else:
|
||||
host_name = await scanner.async_get_device_name(mac)
|
||||
seen.add(mac)
|
||||
|
||||
try:
|
||||
extra_attributes = await scanner.async_get_extra_attributes(mac)
|
||||
except NotImplementedError:
|
||||
extra_attributes = {}
|
||||
|
||||
kwargs = {
|
||||
"mac": mac,
|
||||
"host_name": host_name,
|
||||
"source_type": SOURCE_TYPE_ROUTER,
|
||||
"attributes": {
|
||||
"scanner": scanner.__class__.__name__,
|
||||
**extra_attributes,
|
||||
},
|
||||
}
|
||||
|
||||
zone_home = hass.states.get(hass.components.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_create_task(async_see_device(**kwargs))
|
||||
|
||||
async_track_time_interval(hass, async_device_tracker_scan, interval)
|
||||
hass.async_create_task(async_device_tracker_scan(None))
|
Loading…
Add table
Reference in a new issue