From 5249fb4ae7f502833530ca051a0860ac8f3de6d4 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 14 Feb 2022 09:26:00 +0000 Subject: [PATCH] Fix bug where offline entities are shown with "Restored" warning * Restore entity attributes so that all attributes are available for offline entities --- custom_components/tplink_deco/__init__.py | 4 +- custom_components/tplink_deco/coordinator.py | 20 +++-- .../tplink_deco/device_tracker.py | 86 +++++++++++++------ 3 files changed, 78 insertions(+), 32 deletions(-) diff --git a/custom_components/tplink_deco/__init__.py b/custom_components/tplink_deco/__init__.py index 39d83aa..15cf24d 100644 --- a/custom_components/tplink_deco/__init__.py +++ b/custom_components/tplink_deco/__init__.py @@ -55,6 +55,9 @@ async def async_create_coordinator( entry.entry_id, ) data = {} + + # Populate client list with existing entries so that we keep track of disconnected clients + # since deco list_clients only returns connected clients. for entry in existing_entries: if entry.domain == DEVICE_TRACKER_DOMAIN: client = TPLinkDecoClient(api.host, entry.unique_id) @@ -75,7 +78,6 @@ async def async_setup(hass: HomeAssistant, config: Config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up this integration using UI.""" - _LOGGER.debug("async_setup_entry") if hass.data.get(DOMAIN) is None: hass.data.setdefault(DOMAIN, {}) _LOGGER.info(STARTUP_MESSAGE) diff --git a/custom_components/tplink_deco/coordinator.py b/custom_components/tplink_deco/coordinator.py index b188da2..ffdc0f1 100644 --- a/custom_components/tplink_deco/coordinator.py +++ b/custom_components/tplink_deco/coordinator.py @@ -36,8 +36,8 @@ def __init__(self, router_ip: str, mac: str) -> None: self.online = False self.connection_type = None self.interface = None - self.down_kilobytes_per_s = None - self.up_kilobytes_per_s = None + self.down_kilobytes_per_s = 0 + self.up_kilobytes_per_s = 0 self.last_activity = None def update( @@ -64,13 +64,12 @@ def __init__( api: TplinkDecoApi, scan_interval_seconds: int, consider_home_seconds: int, - data: dict[str:TPLinkDecoClient], + data: dict[str:TPLinkDecoClient] = {}, ) -> None: """Initialize.""" self._api = api self._consider_home_seconds = consider_home_seconds self._on_close: list[Callable] = [] - self.data = data super().__init__( hass, @@ -78,6 +77,10 @@ def __init__( name=DOMAIN, update_interval=timedelta(seconds=scan_interval_seconds), ) + # Must happen after super().__init__ + if len(data) > 0: + self.data = data + async_dispatcher_send(self.hass, SIGNAL_CLIENT_ADDED) async def _async_update_data(self): """Update data via api.""" @@ -107,9 +110,12 @@ async def _async_update_data(self): mac = client.mac if mac not in data: data[mac] = client - client.online = ( - utc_point_in_time - client.last_activity - ).total_seconds() < self._consider_home_seconds + if client.last_activity is None: + client.online = False + else: + client.online = ( + utc_point_in_time - client.last_activity + ).total_seconds() < self._consider_home_seconds if client_added: async_dispatcher_send(self.hass, SIGNAL_CLIENT_ADDED) diff --git a/custom_components/tplink_deco/device_tracker.py b/custom_components/tplink_deco/device_tracker.py index 8e59b91..e13e525 100644 --- a/custom_components/tplink_deco/device_tracker.py +++ b/custom_components/tplink_deco/device_tracker.py @@ -3,11 +3,13 @@ from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER from homeassistant.components.device_tracker.config_entry import ScannerEntity +from homeassistant.components.device_tracker.const import ATTR_IP from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN @@ -17,6 +19,11 @@ _LOGGER: logging.Logger = logging.getLogger(__package__) +ATTR_CONNECTION_TYPE = "connection_type" +ATTR_DOWN_KILOBYTES_PER_S = "down_kilobytes_per_s" +ATTR_INTERFACE = "interface" +ATTR_UP_KILOBYTES_PER_S = "up_kilobytes_per_s" + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities @@ -49,31 +56,49 @@ def add_untracked_entities(): add_untracked_entities() -class TplinkDecoDeviceTracker(CoordinatorEntity, ScannerEntity): +class TplinkDecoDeviceTracker(CoordinatorEntity, RestoreEntity, ScannerEntity): """TP Link Deco Entity.""" def __init__( self, coordinator: TplinkDecoDataUpdateCoordinator, client: TPLinkDecoClient ) -> None: """Initialize a TP-Link Deco device.""" + self._attr_connection_type = None + self._attr_interface = None + self._attr_ip_address = None + self._attr_name = None self._client = client + self._mac_address = client.mac + self._update_from_client() super().__init__(coordinator) + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + + # Restore old state + old_state = await self.async_get_last_state() + if old_state is not None: + if self._attr_connection_type is None: + self._attr_connection_type = old_state.attributes.get( + ATTR_CONNECTION_TYPE + ) + if self._attr_interface is None: + self._attr_interface = old_state.attributes.get(ATTR_INTERFACE) + if self._attr_ip_address is None: + self._attr_ip_address = old_state.attributes.get(ATTR_IP) + self.async_write_ha_state() + @property - def unique_id(self): - """Return a unique ID to use for this entity.""" - return self.mac_address + def mac_address(self): + """Return the MAC address.""" + return self._mac_address @property def source_type(self): """Return the source type.""" return SOURCE_TYPE_ROUTER - @property - def name(self): - """Return the name for this entity.""" - return self._client.name - @property def icon(self) -> str: """Return device icon.""" @@ -84,24 +109,14 @@ def is_connected(self): """Return true if the device is connected to the router.""" return self._client.online - @property - def ip_address(self) -> str: - """Return the primary ip address of the device.""" - return self._client.ip_address - - @property - def mac_address(self) -> str: - """Return the mac address of the device.""" - return self._client.mac - @property def extra_state_attributes(self): """Return extra state attributes.""" return { - "connection_type": self._client.connection_type, - "interface": self._client.interface, - "down_kilobytes_per_s": self._client.down_kilobytes_per_s, - "up_kilobytes_per_s": self._client.up_kilobytes_per_s, + ATTR_CONNECTION_TYPE: self._attr_connection_type, + ATTR_INTERFACE: self._attr_interface, + ATTR_DOWN_KILOBYTES_PER_S: self._client.down_kilobytes_per_s, + ATTR_UP_KILOBYTES_PER_S: self._client.up_kilobytes_per_s, } @property @@ -115,5 +130,28 @@ def device_info(self) -> DeviceInfo: @callback async def async_on_demand_update(self): - """Update state.""" + """Request update from coordinator.""" await self.coordinator.async_request_refresh() + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + if self._update_from_client(): + self.async_write_ha_state() + + def _update_from_client(self) -> None: + """Update data from client.""" + changed = False + if self._client.connection_type is not None: + self._attr_connection_type = self._client.connection_type + changed = True + if self._client.interface is not None: + self._attr_interface = self._client.interface + changed = True + if self._client.ip_address is not None: + self._attr_ip_address = self._client.ip_address + changed = True + if self._client.name is not None: + self._attr_name = self._client.name + changed = True + return changed