"""Code to set up a device tracker platform using a config entry.""" from __future__ import annotations from typing import final from homeassistant.components import zone from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, STATE_HOME, STATE_NOT_HOME, ) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from .const import ATTR_HOST_NAME, ATTR_IP, ATTR_MAC, ATTR_SOURCE_TYPE, DOMAIN, LOGGER async def async_setup_entry(hass, entry): """Set up an entry.""" component: EntityComponent | None = hass.data.get(DOMAIN) if component is None: component = hass.data[DOMAIN] = EntityComponent(LOGGER, DOMAIN, hass) return await component.async_setup_entry(entry) async def async_unload_entry(hass, entry): """Unload an entry.""" return await hass.data[DOMAIN].async_unload_entry(entry) class BaseTrackerEntity(Entity): """Represent a tracked device.""" @property def battery_level(self): """Return the battery level of the device. Percentage from 0-100. """ return None @property def source_type(self): """Return the source type, eg gps or router, of the device.""" raise NotImplementedError @property def state_attributes(self): """Return the device state attributes.""" attr = {ATTR_SOURCE_TYPE: self.source_type} if self.battery_level: attr[ATTR_BATTERY_LEVEL] = self.battery_level return attr class TrackerEntity(BaseTrackerEntity): """Base class for a tracked device.""" @property def should_poll(self): """No polling for entities that have location pushed.""" return False @property def force_update(self): """All updates need to be written to the state machine if we're not polling.""" return not self.should_poll @property def location_accuracy(self): """Return the location accuracy of the device. Value in meters. """ return 0 @property def location_name(self) -> str: """Return a location name for the current location of the device.""" return None @property def latitude(self) -> float: """Return latitude value of the device.""" return NotImplementedError @property def longitude(self) -> float: """Return longitude value of the device.""" return NotImplementedError @property def state(self): """Return the state of the device.""" if self.location_name: return self.location_name if self.latitude is not None: zone_state = zone.async_active_zone( self.hass, self.latitude, self.longitude, self.location_accuracy ) if zone_state is None: state = STATE_NOT_HOME elif zone_state.entity_id == zone.ENTITY_ID_HOME: state = STATE_HOME else: state = zone_state.name return state return None @final @property def state_attributes(self): """Return the device state attributes.""" attr = {} attr.update(super().state_attributes) if self.latitude is not None: attr[ATTR_LATITUDE] = self.latitude attr[ATTR_LONGITUDE] = self.longitude attr[ATTR_GPS_ACCURACY] = self.location_accuracy return attr class ScannerEntity(BaseTrackerEntity): """Base class for a tracked device that is on a scanned network.""" @property def ip_address(self) -> str | None: """Return the primary ip address of the device.""" return None @property def mac_address(self) -> str | None: """Return the mac address of the device.""" return None @property def hostname(self) -> str | None: """Return hostname of the device.""" return None @property def state(self) -> str: """Return the state of the device.""" if self.is_connected: return STATE_HOME return STATE_NOT_HOME @property def is_connected(self) -> bool: """Return true if the device is connected to the network.""" raise NotImplementedError @final @property def state_attributes(self): """Return the device state attributes.""" attr = {} attr.update(super().state_attributes) if self.ip_address is not None: attr[ATTR_IP] = self.ip_address if self.mac_address is not None: attr[ATTR_MAC] = self.mac_address if self.hostname is not None: attr[ATTR_HOST_NAME] = self.hostname return attr