/
device_tracker.py
169 lines (140 loc) · 5.11 KB
/
device_tracker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""Support for Tile device trackers."""
from __future__ import annotations
import logging
from pytile.tile import Tile
from homeassistant.components.device_tracker import (
AsyncSeeCallback,
SourceType,
TrackerEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.util.dt import as_utc
from . import TileData
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
ATTR_ALTITUDE = "altitude"
ATTR_CONNECTION_STATE = "connection_state"
ATTR_IS_DEAD = "is_dead"
ATTR_IS_LOST = "is_lost"
ATTR_LAST_LOST_TIMESTAMP = "last_lost_timestamp"
ATTR_LAST_TIMESTAMP = "last_timestamp"
ATTR_RING_STATE = "ring_state"
ATTR_TILE_NAME = "tile_name"
ATTR_VOIP_STATE = "voip_state"
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Tile device trackers."""
data: TileData = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
[
TileDeviceTracker(entry, data.coordinators[tile_uuid], tile)
for tile_uuid, tile in data.tiles.items()
]
)
async def async_setup_scanner(
hass: HomeAssistant,
config: ConfigType,
async_see: AsyncSeeCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> bool:
"""Detect a legacy configuration and import it."""
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_USERNAME: config[CONF_USERNAME],
CONF_PASSWORD: config[CONF_PASSWORD],
},
)
)
_LOGGER.info(
"Your Tile configuration has been imported into the UI; "
"please remove it from configuration.yaml"
)
return True
class TileDeviceTracker(CoordinatorEntity[DataUpdateCoordinator[None]], TrackerEntity):
"""Representation of a network infrastructure device."""
_attr_has_entity_name = True
_attr_name = None
_attr_translation_key = "tile"
def __init__(
self, entry: ConfigEntry, coordinator: DataUpdateCoordinator[None], tile: Tile
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_extra_state_attributes = {}
self._attr_unique_id = f"{entry.data[CONF_USERNAME]}_{tile.uuid}"
self._entry = entry
self._tile = tile
@property
def available(self) -> bool:
"""Return if entity is available."""
return super().available and not self._tile.dead
@property
def location_accuracy(self) -> int:
"""Return the location accuracy of the device.
Value in meters.
"""
if not self._tile.accuracy:
return super().location_accuracy
return int(self._tile.accuracy)
@property
def device_info(self) -> DeviceInfo:
"""Return device info."""
return DeviceInfo(identifiers={(DOMAIN, self._tile.uuid)}, name=self._tile.name)
@property
def latitude(self) -> float | None:
"""Return latitude value of the device."""
if not self._tile.latitude:
return None
return self._tile.latitude
@property
def longitude(self) -> float | None:
"""Return longitude value of the device."""
if not self._tile.longitude:
return None
return self._tile.longitude
@property
def source_type(self) -> SourceType:
"""Return the source type, eg gps or router, of the device."""
return SourceType.GPS
@callback
def _handle_coordinator_update(self) -> None:
"""Respond to a DataUpdateCoordinator update."""
self._update_from_latest_data()
self.async_write_ha_state()
@callback
def _update_from_latest_data(self) -> None:
"""Update the entity from the latest data."""
self._attr_extra_state_attributes = {
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,
}
for timestamp_attr in (
(ATTR_LAST_LOST_TIMESTAMP, self._tile.lost_timestamp),
(ATTR_LAST_TIMESTAMP, self._tile.last_timestamp),
):
if not timestamp_attr[1]:
# If the API doesn't return a value for a particular timestamp
# attribute, skip it:
continue
self._attr_extra_state_attributes[timestamp_attr[0]] = as_utc(
timestamp_attr[1]
)
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
self._update_from_latest_data()