"""Support for Traccar device tracking.""" from __future__ import annotations from datetime import timedelta import logging from homeassistant.components.device_tracker import TrackerEntity from homeassistant.config_entries import ConfigEntry 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 AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from . import DOMAIN, TRACKER_UPDATE from .const import ( ATTR_ACCURACY, ATTR_ALTITUDE, ATTR_BATTERY, ATTR_BEARING, ATTR_LATITUDE, ATTR_LONGITUDE, 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: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> 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 hass.data[DOMAIN]["devices"]: return hass.data[DOMAIN]["devices"].add(device) async_add_entities( [TraccarEntity(device, latitude, longitude, battery, accuracy, attrs)] ) hass.data[DOMAIN]["unsub_device_tracker"][entry.entry_id] = ( 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 dev_reg.devices.get_devices_for_config_entry_id(entry.entry_id) for identifier in device.identifiers } if not dev_ids: return entities = [] for dev_id in dev_ids: hass.data[DOMAIN]["devices"].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 def battery_level(self): """Return battery value of the device.""" return self._battery 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(ATTR_LATITUDE) self._attr_longitude = attr.get(ATTR_LONGITUDE) self._attr_location_accuracy = attr.get(ATTR_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) 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()