Re-architect Tile integration with new pytile (#43071)
This commit is contained in:
parent
c8ad06e58a
commit
3841f0e42d
6 changed files with 103 additions and 126 deletions
|
@ -1,26 +1,23 @@
|
|||
"""The Tile component."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
|
||||
from pytile import async_login
|
||||
from pytile.errors import SessionExpiredError, TileError
|
||||
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
UpdateFailed,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util.async_ import gather_with_concurrency
|
||||
|
||||
from .const import DATA_COORDINATOR, DOMAIN, LOGGER
|
||||
from .const import DATA_COORDINATOR, DATA_TILE, DOMAIN, LOGGER
|
||||
|
||||
PLATFORMS = ["device_tracker"]
|
||||
DEVICE_TYPES = ["PHONE", "TILE"]
|
||||
|
||||
DEFAULT_ATTRIBUTION = "Data provided by Tile"
|
||||
DEFAULT_ICON = "mdi:view-grid"
|
||||
DEFAULT_INIT_TASK_LIMIT = 2
|
||||
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=2)
|
||||
|
||||
CONF_SHOW_INACTIVE = "show_inactive"
|
||||
|
@ -28,108 +25,71 @@ CONF_SHOW_INACTIVE = "show_inactive"
|
|||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Tile component."""
|
||||
hass.data[DOMAIN] = {DATA_COORDINATOR: {}}
|
||||
|
||||
hass.data[DOMAIN] = {DATA_COORDINATOR: {}, DATA_TILE: {}}
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
async def async_setup_entry(hass, entry):
|
||||
"""Set up Tile as config entry."""
|
||||
hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {}
|
||||
hass.data[DOMAIN][DATA_TILE][entry.entry_id] = {}
|
||||
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
client = await async_login(
|
||||
config_entry.data[CONF_USERNAME],
|
||||
config_entry.data[CONF_PASSWORD],
|
||||
session=websession,
|
||||
)
|
||||
try:
|
||||
client = await async_login(
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
session=websession,
|
||||
)
|
||||
hass.data[DOMAIN][DATA_TILE][entry.entry_id] = await client.async_get_tiles()
|
||||
except TileError as err:
|
||||
raise ConfigEntryNotReady("Error during integration setup") from err
|
||||
|
||||
async def async_update_data():
|
||||
"""Get new data from the API."""
|
||||
async def async_update_tile(tile):
|
||||
"""Update the Tile."""
|
||||
try:
|
||||
return await client.tiles.all()
|
||||
return await tile.async_update()
|
||||
except SessionExpiredError:
|
||||
LOGGER.info("Tile session expired; creating a new one")
|
||||
await client.async_init()
|
||||
except TileError as err:
|
||||
raise UpdateFailed(f"Error while retrieving data: {err}") from err
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=config_entry.title,
|
||||
update_interval=DEFAULT_UPDATE_INTERVAL,
|
||||
update_method=async_update_data,
|
||||
)
|
||||
coordinator_init_tasks = []
|
||||
for tile_uuid, tile in hass.data[DOMAIN][DATA_TILE][entry.entry_id].items():
|
||||
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id][
|
||||
tile_uuid
|
||||
] = DataUpdateCoordinator(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=tile.name,
|
||||
update_interval=DEFAULT_UPDATE_INTERVAL,
|
||||
update_method=partial(async_update_tile, tile),
|
||||
)
|
||||
coordinator_init_tasks.append(coordinator.async_refresh())
|
||||
|
||||
await coordinator.async_refresh()
|
||||
hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator
|
||||
await gather_with_concurrency(DEFAULT_INIT_TASK_LIMIT, *coordinator_init_tasks)
|
||||
|
||||
for component in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, component)
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
async def async_unload_entry(hass, entry):
|
||||
"""Unload a Tile config entry."""
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_unload(config_entry, component)
|
||||
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||
for component in PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN][DATA_COORDINATOR].pop(config_entry.entry_id)
|
||||
hass.data[DOMAIN][DATA_COORDINATOR].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class TileEntity(CoordinatorEntity):
|
||||
"""Define a generic Tile entity."""
|
||||
|
||||
def __init__(self, coordinator):
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
self._name = None
|
||||
self._unique_id = None
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
return self._attrs
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return DEFAULT_ICON
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID of the entity."""
|
||||
return self._unique_id
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
"""Respond to a DataUpdateCoordinator update."""
|
||||
self._update_from_latest_data()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _update_from_latest_data(self):
|
||||
"""Update the entity from the latest data."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Handle entity which will be added."""
|
||||
await super().async_added_to_hass()
|
||||
self._update_from_latest_data()
|
||||
|
|
|
@ -4,5 +4,6 @@ import logging
|
|||
DOMAIN = "tile"
|
||||
|
||||
DATA_COORDINATOR = "coordinator"
|
||||
DATA_TILE = "tile"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
|
|
@ -4,10 +4,11 @@ import logging
|
|||
from homeassistant.components.device_tracker.config_entry import TrackerEntity
|
||||
from homeassistant.components.device_tracker.const import SOURCE_TYPE_GPS
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import DATA_COORDINATOR, DOMAIN, TileEntity
|
||||
from . import DATA_COORDINATOR, DATA_TILE, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -19,17 +20,19 @@ ATTR_RING_STATE = "ring_state"
|
|||
ATTR_VOIP_STATE = "voip_state"
|
||||
ATTR_TILE_NAME = "tile_name"
|
||||
|
||||
DEFAULT_ATTRIBUTION = "Data provided by Tile"
|
||||
DEFAULT_ICON = "mdi:view-grid"
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up Tile device trackers."""
|
||||
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
TileDeviceTracker(coordinator, tile_uuid, tile)
|
||||
for tile_uuid, tile in coordinator.data.items()
|
||||
],
|
||||
True,
|
||||
TileDeviceTracker(
|
||||
hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id][tile_uuid], tile
|
||||
)
|
||||
for tile_uuid, tile in hass.data[DOMAIN][DATA_TILE][entry.entry_id].items()
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
@ -54,21 +57,19 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
|||
return True
|
||||
|
||||
|
||||
class TileDeviceTracker(TileEntity, TrackerEntity):
|
||||
class TileDeviceTracker(CoordinatorEntity, TrackerEntity):
|
||||
"""Representation of a network infrastructure device."""
|
||||
|
||||
def __init__(self, coordinator, tile_uuid, tile):
|
||||
def __init__(self, coordinator, tile):
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._name = tile["name"]
|
||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
self._tile = tile
|
||||
self._tile_uuid = tile_uuid
|
||||
self._unique_id = f"tile_{tile_uuid}"
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if entity is available."""
|
||||
return self.coordinator.last_update_success and not self._tile["is_dead"]
|
||||
return self.coordinator.last_update_success and not self._tile.dead
|
||||
|
||||
@property
|
||||
def battery_level(self):
|
||||
|
@ -78,53 +79,68 @@ class TileDeviceTracker(TileEntity, TrackerEntity):
|
|||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
return self._attrs
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return DEFAULT_ICON
|
||||
|
||||
@property
|
||||
def location_accuracy(self):
|
||||
"""Return the location accuracy of the device.
|
||||
|
||||
Value in meters.
|
||||
"""
|
||||
state = self._tile["last_tile_state"]
|
||||
h_accuracy = state.get("h_accuracy")
|
||||
v_accuracy = state.get("v_accuracy")
|
||||
|
||||
if h_accuracy is not None and v_accuracy is not None:
|
||||
return round(
|
||||
(
|
||||
self._tile["last_tile_state"]["h_accuracy"]
|
||||
+ self._tile["last_tile_state"]["v_accuracy"]
|
||||
)
|
||||
/ 2
|
||||
)
|
||||
|
||||
if h_accuracy is not None:
|
||||
return h_accuracy
|
||||
|
||||
if v_accuracy is not None:
|
||||
return v_accuracy
|
||||
|
||||
return None
|
||||
return self._tile.accuracy
|
||||
|
||||
@property
|
||||
def latitude(self) -> float:
|
||||
"""Return latitude value of the device."""
|
||||
return self._tile["last_tile_state"]["latitude"]
|
||||
return self._tile.latitude
|
||||
|
||||
@property
|
||||
def longitude(self) -> float:
|
||||
"""Return longitude value of the device."""
|
||||
return self._tile["last_tile_state"]["longitude"]
|
||||
return self._tile.longitude
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self._tile.name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID of the entity."""
|
||||
return f"tile_{self._tile.uuid}"
|
||||
|
||||
@property
|
||||
def source_type(self):
|
||||
"""Return the source type, eg gps or router, of the device."""
|
||||
return SOURCE_TYPE_GPS
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
"""Respond to a DataUpdateCoordinator update."""
|
||||
self._update_from_latest_data()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _update_from_latest_data(self):
|
||||
"""Update the entity from the latest data."""
|
||||
self._tile = self.coordinator.data[self._tile_uuid]
|
||||
self._attrs[ATTR_ALTITUDE] = self._tile["last_tile_state"]["altitude"]
|
||||
self._attrs[ATTR_IS_LOST] = self._tile["last_tile_state"]["is_lost"]
|
||||
self._attrs[ATTR_RING_STATE] = self._tile["last_tile_state"]["ring_state"]
|
||||
self._attrs[ATTR_VOIP_STATE] = self._tile["last_tile_state"]["voip_state"]
|
||||
self._attrs.update(
|
||||
{
|
||||
ATTR_ALTITUDE: self._tile.altitude,
|
||||
ATTR_IS_LOST: self._tile.lost,
|
||||
ATTR_RING_STATE: self._tile.ring_state,
|
||||
ATTR_VOIP_STATE: self._tile.voip_state,
|
||||
}
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Handle entity which will be added."""
|
||||
await super().async_added_to_hass()
|
||||
self._update_from_latest_data()
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"name": "Tile",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/tile",
|
||||
"requirements": ["pytile==4.0.0"],
|
||||
"requirements": ["pytile==5.1.0"],
|
||||
"codeowners": ["@bachya"]
|
||||
}
|
||||
|
|
|
@ -1846,7 +1846,7 @@ python_opendata_transport==0.2.1
|
|||
pythonegardia==1.0.40
|
||||
|
||||
# homeassistant.components.tile
|
||||
pytile==4.0.0
|
||||
pytile==5.1.0
|
||||
|
||||
# homeassistant.components.touchline
|
||||
pytouchline==0.7
|
||||
|
|
|
@ -918,7 +918,7 @@ python-velbus==2.1.2
|
|||
python_awair==0.2.1
|
||||
|
||||
# homeassistant.components.tile
|
||||
pytile==4.0.0
|
||||
pytile==5.1.0
|
||||
|
||||
# homeassistant.components.traccar
|
||||
pytraccar==0.9.0
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue