22
33from __future__ import annotations
44
5+ from collections .abc import Callable
56from datetime import datetime , timedelta
67import logging
78from typing import TYPE_CHECKING
1819from homeassistant .const import CONF_ACCESS_TOKEN
1920from homeassistant .core import HomeAssistant
2021from homeassistant .exceptions import ConfigEntryAuthFailed , ConfigEntryNotReady
22+ import homeassistant .helpers .device_registry as dr
2123from homeassistant .helpers .update_coordinator import DataUpdateCoordinator , UpdateFailed
2224
23- from .const import CONF_EXPIRATION
25+ from .const import CONF_EXPIRATION , DOMAIN
2426
2527if TYPE_CHECKING :
2628 from . import FytaConfigEntry
@@ -42,6 +44,8 @@ def __init__(self, hass: HomeAssistant, fyta: FytaConnector) -> None:
4244 update_interval = timedelta (minutes = 4 ),
4345 )
4446 self .fyta = fyta
47+ self ._plants_last_update : set [int ] = set ()
48+ self .new_device_callbacks : list [Callable [[int ], None ]] = []
4549
4650 async def _async_update_data (
4751 self ,
@@ -55,9 +59,62 @@ async def _async_update_data(
5559 await self .renew_authentication ()
5660
5761 try :
58- return await self .fyta .update_all_plants ()
62+ data = await self .fyta .update_all_plants ()
5963 except (FytaConnectionError , FytaPlantError ) as err :
6064 raise UpdateFailed (err ) from err
65+ _LOGGER .debug ("Data successfully updated" )
66+
67+ # data must be assigned before _async_add_remove_devices, as it is uses to set-up possible new devices
68+ self .data = data
69+ self ._async_add_remove_devices ()
70+
71+ return data
72+
73+ def _async_add_remove_devices (self ) -> None :
74+ """Add new devices, remove non-existing devices."""
75+ if not self ._plants_last_update :
76+ self ._plants_last_update = set (self .fyta .plant_list .keys ())
77+
78+ if (
79+ current_plants := set (self .fyta .plant_list .keys ())
80+ ) == self ._plants_last_update :
81+ return
82+
83+ _LOGGER .debug (
84+ "Check for new and removed plant(s): old plants: %s; new plants: %s" ,
85+ ", " .join (map (str , self ._plants_last_update )),
86+ ", " .join (map (str , current_plants )),
87+ )
88+
89+ # remove old plants
90+ if removed_plants := self ._plants_last_update - current_plants :
91+ _LOGGER .debug ("Removed plant(s): %s" , ", " .join (map (str , removed_plants )))
92+
93+ device_registry = dr .async_get (self .hass )
94+ for plant_id in removed_plants :
95+ if device := device_registry .async_get_device (
96+ identifiers = {
97+ (
98+ DOMAIN ,
99+ f"{ self .config_entry .entry_id } -{ plant_id } " ,
100+ )
101+ }
102+ ):
103+ device_registry .async_update_device (
104+ device_id = device .id ,
105+ remove_config_entry_id = self .config_entry .entry_id ,
106+ )
107+ _LOGGER .debug ("Device removed from device registry: %s" , device .id )
108+
109+ # add new devices
110+ if new_plants := current_plants - self ._plants_last_update :
111+ _LOGGER .debug ("New plant(s) found: %s" , ", " .join (map (str , new_plants )))
112+ for plant_id in new_plants :
113+ for callback in self .new_device_callbacks :
114+ callback (plant_id )
115+ _LOGGER .debug ("Device added: %s" , plant_id )
116+
117+ self ._plants_last_update = current_plants
61118
62119 async def renew_authentication (self ) -> bool :
63120 """Renew access token for FYTA API."""
0 commit comments