1 """The tractive integration."""
3 from __future__
import annotations
6 from dataclasses
import dataclass
8 from typing
import Any, cast
14 ATTR_BATTERY_CHARGING,
18 EVENT_HOMEASSISTANT_STOP,
31 ATTR_MINUTES_DAY_SLEEP,
32 ATTR_MINUTES_NIGHT_SLEEP,
40 TRACKER_HARDWARE_STATUS_UPDATED,
41 TRACKER_POSITION_UPDATED,
42 TRACKER_SWITCH_STATUS_UPDATED,
43 TRACKER_WELLNESS_STATUS_UPDATED,
47 Platform.BINARY_SENSOR,
48 Platform.DEVICE_TRACKER,
54 _LOGGER = logging.getLogger(__name__)
59 """A class that describes trackables."""
61 tracker: aiotractive.tracker.Tracker
68 @dataclass(slots=True)
70 """Class for Tractive data."""
72 client: TractiveClient
73 trackables: list[Trackables]
76 type TractiveConfigEntry = ConfigEntry[TractiveData]
80 """Set up tractive from a config entry."""
83 client = aiotractive.Tractive(
90 creds = await client.authenticate()
91 except aiotractive.exceptions.UnauthorizedError
as error:
93 raise ConfigEntryAuthFailed
from error
94 except aiotractive.exceptions.TractiveError
as error:
96 raise ConfigEntryNotReady
from error
101 trackable_objects = await client.trackable_objects()
102 trackables = await asyncio.gather(
105 except aiotractive.exceptions.TractiveError
as error:
106 raise ConfigEntryNotReady
from error
110 filtered_trackables = [item
for item
in trackables
if item]
112 entry.runtime_data =
TractiveData(tractive, filtered_trackables)
114 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
116 async
def cancel_listen_task(_: Event) ->
None:
117 await tractive.unsubscribe()
119 entry.async_on_unload(
120 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cancel_listen_task)
122 entry.async_on_unload(tractive.unsubscribe)
128 client: aiotractive.Tractive,
129 trackable: aiotractive.trackable_object.TrackableObject,
130 ) -> Trackables |
None:
131 """Generate trackables."""
132 trackable = await trackable.details()
135 if not trackable.get(
"device_id"):
138 if "details" not in trackable:
140 "Tracker %s has no details and will be skipped. This happens for shared trackers",
141 trackable[
"device_id"],
145 tracker = client.tracker(trackable[
"device_id"])
147 tracker_details, hw_info, pos_report = await asyncio.gather(
148 tracker.details(), tracker.hw_info(), tracker.pos_report()
151 if not tracker_details.get(
"_id"):
153 f
"Tractive API returns incomplete data for tracker {trackable['device_id']}",
156 return Trackables(tracker, trackable, tracker_details, hw_info, pos_report)
160 """Unload a config entry."""
161 return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
165 """A Tractive client."""
170 client: aiotractive.Tractive,
172 config_entry: ConfigEntry,
174 """Initialize the client."""
180 self.
_listen_task_listen_task: asyncio.Task |
None =
None
185 """Return user id."""
190 """Return True if subscribed."""
198 ) -> list[aiotractive.trackable_object.TrackableObject]:
199 """Get list of trackable objects."""
201 list[aiotractive.trackable_object.TrackableObject],
205 def tracker(self, tracker_id: str) -> aiotractive.tracker.Tracker:
206 """Get tracker by id."""
210 """Start event listener coroutine."""
214 """Stop event listener coroutine."""
217 await self.
_client_client.close()
220 server_was_unavailable =
False
223 async
for event
in self.
_client_client.events():
224 _LOGGER.debug(
"Received event: %s", event)
225 if server_was_unavailable:
226 _LOGGER.debug(
"Tractive is back online")
227 server_was_unavailable =
False
228 if event[
"message"] ==
"wellness_overview":
233 and self.
_last_hw_time_last_hw_time != event[
"hardware"][
"time"]
239 and self.
_last_pos_time_last_pos_time != event[
"position"][
"time"]
245 if bool(set(SWITCH_KEY_MAP.values()).intersection(event)):
247 except aiotractive.exceptions.UnauthorizedError:
251 "Authentication failed for %s, try reconfiguring device",
255 except (KeyError, TypeError)
as error:
256 _LOGGER.error(
"Error while listening for events: %s", error)
258 except aiotractive.exceptions.TractiveError:
261 "Tractive is not available. Internet connection is down?"
262 " Sleeping %i seconds and retrying"
264 RECONNECT_INTERVAL.total_seconds(),
269 self.
_hass_hass, f
"{SERVER_UNAVAILABLE}-{self._user_id}"
271 await asyncio.sleep(RECONNECT_INTERVAL.total_seconds())
272 server_was_unavailable =
True
278 ATTR_BATTERY_LEVEL: event[
"hardware"][
"battery_level"],
279 ATTR_TRACKER_STATE: event[
"tracker_state"].lower(),
280 ATTR_BATTERY_CHARGING: event[
"charging_state"] ==
"CHARGING",
283 TRACKER_HARDWARE_STATUS_UPDATED, event[
"tracker_id"], payload
289 for switch, key
in SWITCH_KEY_MAP.items():
290 if switch_data := event.get(key):
291 payload[switch] = switch_data[
"active"]
293 TRACKER_SWITCH_STATUS_UPDATED, event[
"tracker_id"], payload
299 if isinstance(event[
"sleep"], dict):
300 sleep_day = event[
"sleep"][
"minutes_day_sleep"]
301 sleep_night = event[
"sleep"][
"minutes_night_sleep"]
303 ATTR_ACTIVITY_LABEL: event[
"wellness"].
get(
"activity_label"),
304 ATTR_CALORIES: event[
"activity"][
"calories"],
305 ATTR_DAILY_GOAL: event[
"activity"][
"minutes_goal"],
306 ATTR_MINUTES_ACTIVE: event[
"activity"][
"minutes_active"],
307 ATTR_MINUTES_DAY_SLEEP: sleep_day,
308 ATTR_MINUTES_NIGHT_SLEEP: sleep_night,
309 ATTR_MINUTES_REST: event[
"activity"][
"minutes_rest"],
310 ATTR_SLEEP_LABEL: event[
"wellness"].
get(
"sleep_label"),
313 TRACKER_WELLNESS_STATUS_UPDATED, event[
"pet_id"], payload
318 "latitude": event[
"position"][
"latlong"][0],
319 "longitude": event[
"position"][
"latlong"][1],
320 "accuracy": event[
"position"][
"accuracy"],
321 "sensor_used": event[
"position"][
"sensor_used"],
324 TRACKER_POSITION_UPDATED, event[
"tracker_id"], payload
328 self, event_name: str, tracker_id: str, payload: dict[str, Any]
332 f
"{event_name}-{tracker_id}",
None __init__(self, HomeAssistant hass, aiotractive.Tractive client, str user_id, ConfigEntry config_entry)
None _send_hardware_update(self, dict[str, Any] event)
None _send_switch_update(self, dict[str, Any] event)
None _send_position_update(self, dict[str, Any] event)
aiotractive.tracker.Tracker tracker(self, str tracker_id)
None _send_wellness_update(self, dict[str, Any] event)
list[aiotractive.trackable_object.TrackableObject] trackable_objects(self)
None _dispatch_tracker_event(self, str event_name, str tracker_id, dict[str, Any] payload)
web.Response get(self, web.Request request, str config_key)
Trackables|None _generate_trackables(aiotractive.Tractive client, aiotractive.trackable_object.TrackableObject trackable)
bool async_setup_entry(HomeAssistant hass, TractiveConfigEntry entry)
bool async_unload_entry(HomeAssistant hass, TractiveConfigEntry entry)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)