"""Support for Traccar device tracking.""" from datetime import timedelta import logging from typing import override from homeassistant.components.device_tracker import ( TrackerEntity, TrackerEntityStateAttribute, ) from homeassistant.const import ATTR_BATTERY_LEVEL, EntityStateAttribute from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from . import DOMAIN, TRACKER_UPDATE, TraccarConfigEntry from .const import ( ATTR_ALTITUDE, ATTR_BEARING, ATTR_SPEED, EVENT_ALARM, EVENT_ALL_EVENTS, EVENT_COMMAND_RESULT, EVENT_DEVICE_FUEL_DROP, EVENT_DEVICE_MOVING, EVENT_DEVICE_OFFLINE, EVENT_DEVICE_ONLINE, EVENT_DEVICE_OVERSPEED, EVENT_DEVICE_STOPPED, EVENT_DEVICE_UNKNOWN, EVENT_DRIVER_CHANGED, EVENT_GEOFENCE_ENTER, EVENT_GEOFENCE_EXIT, EVENT_IGNITION_OFF, EVENT_IGNITION_ON, EVENT_MAINTENANCE, EVENT_TEXT_MESSAGE, ) _LOGGER = logging.getLogger(__name__) DEFAULT_SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = DEFAULT_SCAN_INTERVAL EVENTS = [ EVENT_DEVICE_MOVING, EVENT_COMMAND_RESULT, EVENT_DEVICE_FUEL_DROP, EVENT_GEOFENCE_ENTER, EVENT_DEVICE_OFFLINE, EVENT_DRIVER_CHANGED, EVENT_GEOFENCE_EXIT, EVENT_DEVICE_OVERSPEED, EVENT_DEVICE_ONLINE, EVENT_DEVICE_STOPPED, EVENT_MAINTENANCE, EVENT_ALARM, EVENT_TEXT_MESSAGE, EVENT_DEVICE_UNKNOWN, EVENT_IGNITION_OFF, EVENT_IGNITION_ON, EVENT_ALL_EVENTS, ] async def async_setup_entry( hass: HomeAssistant, entry: TraccarConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Configure a dispatcher connection based on a config entry.""" @callback def _receive_data(device, latitude, longitude, battery, accuracy, attrs): """Receive set location.""" if device in entry.runtime_data: return entry.runtime_data.add(device) async_add_entities( [TraccarEntity(device, latitude, longitude, battery, accuracy, attrs)] ) entry.async_on_unload(async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data)) # Restore previously loaded devices dev_reg = dr.async_get(hass) dev_ids = { identifier[1] for device in dr.async_entries_for_config_entry(dev_reg, entry.entry_id) for identifier in device.identifiers } if not dev_ids: return entities = [] for dev_id in dev_ids: entry.runtime_data.add(dev_id) entity = TraccarEntity(dev_id, None, None, None, None, None) entities.append(entity) async_add_entities(entities) class TraccarEntity(TrackerEntity, RestoreEntity): """Represent a tracked device.""" _attr_has_entity_name = True _attr_name = None def __init__(self, device, latitude, longitude, battery, accuracy, attributes): """Set up Traccar entity.""" self._attr_location_accuracy = accuracy self._attr_extra_state_attributes = attributes self._device = device self._battery = battery self._attr_latitude = latitude self._attr_longitude = longitude self._unsub_dispatcher = None self._attr_unique_id = device self._attr_device_info = DeviceInfo( name=device, identifiers={(DOMAIN, device)}, ) @property @override def battery_level(self) -> int | None: """Return battery value of the device.""" return self._battery @override async def async_added_to_hass(self) -> None: """Register state update callback.""" await super().async_added_to_hass() self._unsub_dispatcher = async_dispatcher_connect( self.hass, TRACKER_UPDATE, self._async_receive_data ) # don't restore if we got created with data if self.latitude is not None or self.longitude is not None: return if (state := await self.async_get_last_state()) is None: self._attr_latitude = None self._attr_longitude = None self._attr_location_accuracy = 0 self._attr_extra_state_attributes = { ATTR_ALTITUDE: None, ATTR_BEARING: None, ATTR_SPEED: None, } self._battery = None return attr = state.attributes self._attr_latitude = attr.get(EntityStateAttribute.LATITUDE) self._attr_longitude = attr.get(EntityStateAttribute.LONGITUDE) self._attr_location_accuracy = attr.get( TrackerEntityStateAttribute.GPS_ACCURACY, 0 ) self._attr_extra_state_attributes = { ATTR_ALTITUDE: attr.get(ATTR_ALTITUDE), ATTR_BEARING: attr.get(ATTR_BEARING), ATTR_SPEED: attr.get(ATTR_SPEED), } self._battery = attr.get(ATTR_BATTERY_LEVEL) @override async def async_will_remove_from_hass(self) -> None: """Clean up after entity before removal.""" await super().async_will_remove_from_hass() self._unsub_dispatcher() @callback def _async_receive_data( self, device, latitude, longitude, battery, accuracy, attributes ): """Mark the device as seen.""" if device != self._device: return self._attr_latitude = latitude self._attr_longitude = longitude self._battery = battery self._attr_location_accuracy = accuracy self._attr_extra_state_attributes.update(attributes) self.async_write_ha_state()