1 """Code to set up a device tracker platform using a config entry."""
3 from __future__
import annotations
6 from typing
import final
8 from propcache
import cached_property
25 EventDeviceRegistryUpdatedData,
39 CONNECTED_DEVICE_REGISTERED,
45 DATA_COMPONENT: HassKey[EntityComponent[BaseTrackerEntity]] =
HassKey(DOMAIN)
46 DATA_KEY: HassKey[dict[str, tuple[str, str]]] =
HassKey(f
"{DOMAIN}_mac")
52 """Set up an entry."""
53 component: EntityComponent[BaseTrackerEntity] |
None = hass.data.get(DOMAIN)
55 if component
is not None:
56 return await component.async_setup_entry(entry)
58 component = hass.data[DATA_COMPONENT] = EntityComponent[BaseTrackerEntity](
61 component.register_shutdown()
63 return await component.async_setup_entry(entry)
67 """Unload an entry."""
73 hass: HomeAssistant, mac: str, ip_address: str |
None, hostname: str |
None
75 """Register a newly seen connected device.
77 This is currently used by the dhcp integration
78 to listen for newly registered connected devices
83 CONNECTED_DEVICE_REGISTERED,
87 ATTR_HOST_NAME: hostname,
99 """Register a mac address with a unique ID."""
100 mac = dr.format_mac(mac)
101 if DATA_KEY
in hass.data:
102 hass.data[DATA_KEY][mac] = (domain, unique_id)
108 data = hass.data[DATA_KEY] = {mac: (domain, unique_id)}
111 def handle_device_event(ev: Event[EventDeviceRegistryUpdatedData]) ->
None:
112 """Enable the online status entity for the mac of a newly created device."""
114 if ev.data[
"action"] !=
"create":
117 dev_reg = dr.async_get(hass)
118 device_entry = dev_reg.async_get(ev.data[
"device_id"])
120 if device_entry
is None:
126 for conn
in device_entry.connections:
127 if conn[0] == dr.CONNECTION_NETWORK_MAC:
135 if (unique_id := data.get(mac))
is None:
138 ent_reg = er.async_get(hass)
140 if (entity_id := ent_reg.async_get_entity_id(DOMAIN, *unique_id))
is None:
143 entity_entry = ent_reg.entities[entity_id]
149 entity_entry.config_entry_id
is None
152 config_entry := hass.config_entries.async_get_entry(
153 entity_entry.config_entry_id
157 and config_entry.pref_disable_new_entities
159 or entity_entry.disabled_by != er.RegistryEntryDisabler.INTEGRATION
164 ent_reg.async_update_entity(entity_id, disabled_by=
None)
166 hass.bus.async_listen(dr.EVENT_DEVICE_REGISTRY_UPDATED, handle_device_event)
170 """Represent a tracked device."""
172 _attr_device_info:
None =
None
173 _attr_entity_category = EntityCategory.DIAGNOSTIC
174 _attr_source_type: SourceType
178 """Return the battery level of the device.
180 Percentage from 0-100.
186 """Return the source type, eg gps or router, of the device."""
187 if hasattr(self,
"_attr_source_type"):
188 return self._attr_source_type
189 raise NotImplementedError
193 """Return the device state attributes."""
194 attr: dict[str, StateType] = {ATTR_SOURCE_TYPE: self.
source_typesource_type}
203 """A class that describes tracker entities."""
206 CACHED_TRACKER_PROPERTIES_WITH_ATTR_ = {
215 BaseTrackerEntity, cached_properties=CACHED_TRACKER_PROPERTIES_WITH_ATTR_
217 """Base class for a tracked device."""
219 entity_description: TrackerEntityDescription
220 _attr_latitude: float |
None =
None
221 _attr_location_accuracy: int = 0
222 _attr_location_name: str |
None =
None
223 _attr_longitude: float |
None =
None
224 _attr_source_type: SourceType = SourceType.GPS
228 """No polling for entities that have location pushed."""
233 """All updates need to be written to the state machine if we're not polling."""
238 """Return the location accuracy of the device.
242 return self._attr_location_accuracy
246 """Return a location name for the current location of the device."""
247 return self._attr_location_name
251 """Return latitude value of the device."""
252 return self._attr_latitude
256 """Return longitude value of the device."""
257 return self._attr_longitude
261 """Return the state of the device."""
266 zone_state = zone.async_active_zone(
269 if zone_state
is None:
270 state = STATE_NOT_HOME
271 elif zone_state.entity_id == zone.ENTITY_ID_HOME:
274 state = zone_state.name
282 """Return the device state attributes."""
283 attr: dict[str, StateType] = {}
284 attr.update(super().state_attributes)
287 attr[ATTR_LATITUDE] = self.
latitudelatitude
288 attr[ATTR_LONGITUDE] = self.
longitudelongitude
295 """A class that describes tracker entities."""
298 CACHED_SCANNER_PROPERTIES_WITH_ATTR_ = {
306 BaseTrackerEntity, cached_properties=CACHED_SCANNER_PROPERTIES_WITH_ATTR_
308 """Base class for a tracked device that is on a scanned network."""
310 entity_description: ScannerEntityDescription
311 _attr_hostname: str |
None =
None
312 _attr_ip_address: str |
None =
None
313 _attr_mac_address: str |
None =
None
314 _attr_source_type: SourceType = SourceType.ROUTER
318 """Return the primary ip address of the device."""
319 return self._attr_ip_address
323 """Return the mac address of the device."""
324 return self._attr_mac_address
328 """Return hostname of the device."""
329 return self._attr_hostname
333 """Return the state of the device."""
336 return STATE_NOT_HOME
340 """Return true if the device is connected to the network."""
341 raise NotImplementedError
345 """Return unique ID of the entity."""
351 """Device tracker entities should not create device registry entries."""
356 """Return if entity is enabled by default."""
370 platform: EntityPlatform,
371 parallel_updates: asyncio.Semaphore |
None,
373 """Start adding an entity to a platform."""
378 platform.platform_name,
392 """Return device entry."""
395 return dr.async_get(self.
hasshass).async_get_device(
396 connections={(dr.CONNECTION_NETWORK_MAC, self.
mac_addressmac_address)}
400 """Handle added to Home Assistant."""
404 or not self.
platformplatform.config_entry
412 LOGGER.debug(
"Entity %s unexpectedly has a device info", self.
entity_identity_id)
419 self.
entity_identity_id, device_id=device_entry.id
423 if self.
platformplatform.config_entry.entry_id
not in device_entry.config_entries:
426 add_config_entry_id=self.
platformplatform.config_entry.entry_id,
435 """Return the device state attributes."""
436 attr = super().state_attributes
439 attr[ATTR_IP] = ip_address
440 if (mac_address := self.
mac_addressmac_address)
is not None:
441 attr[ATTR_MAC] = mac_address
442 if (hostname := self.
hostnamehostname)
is not None:
443 attr[ATTR_HOST_NAME] = hostname
dict[str, StateType] state_attributes(self)
SourceType source_type(self)
int|None battery_level(self)
dr.DeviceEntry|None find_device_entry(self)
None add_to_platform_start(self, HomeAssistant hass, EntityPlatform platform, asyncio.Semaphore|None parallel_updates)
dict[str, StateType] state_attributes(self)
DeviceInfo|None device_info(self)
str|None ip_address(self)
bool entity_registry_enabled_default(self)
str|None mac_address(self)
None async_internal_added_to_hass(self)
int location_accuracy(self)
float|None longitude(self)
str|None location_name(self)
float|None latitude(self)
dict[str, StateType] state_attributes(self)
DeviceInfo|None device_info(self)
None async_update_device(HomeAssistant hass, ConfigEntry entry, str adapter, AdapterDetails details)
None _async_register_mac(HomeAssistant hass, str domain, str mac, str unique_id)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
None _async_connected_device_registered(HomeAssistant hass, str mac, str|None ip_address, str|None hostname)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
None async_update_entity(HomeAssistant hass, str entity_id)