55from collections .abc import Callable
66from datetime import timedelta
77import logging
8- from urllib .parse import ParseResult , urlparse
98
10- from aiohttp import CookieJar
119from solarlog_cli .solarlog_connector import SolarLogConnector
1210from solarlog_cli .solarlog_exceptions import (
1311 SolarLogAuthenticationError ,
1412 SolarLogConnectionError ,
1513 SolarLogUpdateError ,
1614)
17- from solarlog_cli .solarlog_models import SolarlogData
15+ from solarlog_cli .solarlog_models import EnergyData , InverterData , SolarlogData
1816
1917from homeassistant .config_entries import ConfigEntry
20- from homeassistant .const import CONF_HOST
2118from homeassistant .core import HomeAssistant
2219from homeassistant .exceptions import ConfigEntryAuthFailed , ConfigEntryNotReady
2320from homeassistant .helpers import device_registry as dr
24- from homeassistant .helpers .aiohttp_client import async_create_clientsession
2521from homeassistant .helpers .update_coordinator import DataUpdateCoordinator , UpdateFailed
2622from homeassistant .util import slugify
2723
2824from .const import DOMAIN
25+ from .models import SolarlogIntegrationData
2926
3027_LOGGER = logging .getLogger (__name__ )
3128
32- type SolarlogConfigEntry = ConfigEntry [SolarLogCoordinator ]
29+ type SolarlogConfigEntry = ConfigEntry [SolarlogIntegrationData ]
3330
3431
35- class SolarLogCoordinator (DataUpdateCoordinator [SolarlogData ]):
36- """Get and update the latest data."""
32+ class SolarLogBasicDataCoordinator (DataUpdateCoordinator [SolarlogData ]):
33+ """Get and update the basic solarlog data."""
3734
3835 config_entry : SolarlogConfigEntry
3936
40- def __init__ (self , hass : HomeAssistant , config_entry : SolarlogConfigEntry ) -> None :
37+ def __init__ (
38+ self ,
39+ hass : HomeAssistant ,
40+ config_entry : SolarlogConfigEntry ,
41+ api : SolarLogConnector ,
42+ ) -> None :
4143 """Initialize the data object."""
4244 super ().__init__ (
4345 hass ,
@@ -47,27 +49,8 @@ def __init__(self, hass: HomeAssistant, config_entry: SolarlogConfigEntry) -> No
4749 update_interval = timedelta (seconds = 60 ),
4850 )
4951
50- self .new_device_callbacks : list [Callable [[int ], None ]] = []
51- self ._devices_last_update : set [tuple [int , str ]] = set ()
52-
53- host_entry = config_entry .data [CONF_HOST ]
54- password = config_entry .data .get ("password" , "" )
55-
56- url = urlparse (host_entry , "http" )
57- netloc = url .netloc or url .path
58- path = url .path if url .netloc else ""
59- url = ParseResult ("http" , netloc , path , * url [3 :])
6052 self .unique_id = config_entry .entry_id
61- self .host = url .geturl ()
62-
63- self .solarlog = SolarLogConnector (
64- self .host ,
65- tz = hass .config .time_zone ,
66- password = password ,
67- session = async_create_clientsession (
68- hass , cookie_jar = CookieJar (quote_cookie = False )
69- ),
70- )
53+ self .solarlog = api
7154
7255 async def _async_setup (self ) -> None :
7356 """Do initialization logic."""
@@ -82,13 +65,10 @@ async def _async_setup(self) -> None:
8265
8366 async def _async_update_data (self ) -> SolarlogData :
8467 """Update the data from the SolarLog device."""
85- _LOGGER .debug ("Start data update" )
68+ _LOGGER .debug ("Start basic data update" )
8669
8770 try :
88- data = await self .solarlog .update_data ()
89- if self .solarlog .extended_data :
90- await self .solarlog .update_device_list ()
91- data .inverter_data = await self .solarlog .update_inverter_data ()
71+ data = await self .solarlog .update_basic_data ()
9272 except SolarLogConnectionError as ex :
9373 raise ConfigEntryNotReady (
9474 translation_domain = DOMAIN ,
@@ -112,26 +92,94 @@ async def _async_update_data(self) -> SolarlogData:
11292 translation_key = "update_failed" ,
11393 ) from ex
11494
115- _LOGGER .debug ("Data successfully updated" )
116-
117- if self .solarlog .extended_data :
118- self ._async_add_remove_devices (data )
119- _LOGGER .debug ("Add_remove_devices finished" )
95+ _LOGGER .debug ("Basic data successfully updated" )
12096
12197 return data
12298
123- def _async_add_remove_devices (self , data : SolarlogData ) -> None :
99+ async def renew_authentication (self ) -> bool :
100+ """Renew access token for SolarLog API."""
101+ logged_in = False
102+ try :
103+ logged_in = await self .solarlog .login ()
104+ except SolarLogAuthenticationError as ex :
105+ raise ConfigEntryAuthFailed (
106+ translation_domain = DOMAIN ,
107+ translation_key = "auth_failed" ,
108+ ) from ex
109+ except (SolarLogConnectionError , SolarLogUpdateError ) as ex :
110+ raise ConfigEntryNotReady (
111+ translation_domain = DOMAIN ,
112+ translation_key = "config_entry_not_ready" ,
113+ ) from ex
114+
115+ _LOGGER .debug ("Credentials successfully updated? %s" , logged_in )
116+
117+ return logged_in
118+
119+
120+ class SolarLogDeviceDataCoordinator (DataUpdateCoordinator [dict [int , InverterData ]]):
121+ """Get and update the device data of solarlog."""
122+
123+ config_entry : SolarlogConfigEntry
124+
125+ def __init__ (
126+ self ,
127+ hass : HomeAssistant ,
128+ config_entry : SolarlogConfigEntry ,
129+ api : SolarLogConnector ,
130+ ) -> None :
131+ """Initialize the data object."""
132+ super ().__init__ (
133+ hass ,
134+ _LOGGER ,
135+ config_entry = config_entry ,
136+ name = "SolarLogDevices" ,
137+ update_interval = timedelta (seconds = 60 ),
138+ )
139+
140+ self .new_device_callbacks : list [Callable [[int ], None ]] = []
141+ self ._devices_last_update : set [tuple [int , str ]] = set ()
142+ self .solarlog = api
143+
144+ async def _async_update_data (self ) -> dict [int , InverterData ]:
145+ """Update the data from the SolarLog device."""
146+ _LOGGER .debug ("Start device data update" )
147+
148+ try :
149+ await self .solarlog .update_device_list ()
150+ inverter_data = await self .solarlog .update_inverter_data ()
151+ except SolarLogAuthenticationError as ex :
152+ raise ConfigEntryAuthFailed (
153+ translation_domain = DOMAIN ,
154+ translation_key = "auth_failed" ,
155+ ) from ex
156+ except (SolarLogConnectionError , SolarLogUpdateError ) as ex :
157+ raise UpdateFailed (
158+ translation_domain = DOMAIN ,
159+ translation_key = "update_failed" ,
160+ ) from ex
161+
162+ _LOGGER .debug ("Device data successfully updated" )
163+
164+ self .data = inverter_data
165+
166+ self ._async_add_remove_devices (inverter_data )
167+
168+ return inverter_data
169+
170+ def _async_add_remove_devices (self , inverter_data : dict [int , InverterData ]) -> None :
124171 """Add new devices, remove non-existing devices."""
172+
125173 if (
126174 current_devices := {
127- (k , self .solarlog .device_name (k )) for k in data . inverter_data
175+ (k , self .solarlog .device_name (k )) for k in inverter_data
128176 }
129177 ) == self ._devices_last_update :
130178 return
131179
132180 # remove old devices
133181 if removed_devices := self ._devices_last_update - current_devices :
134- _LOGGER .debug ("Removed device(s): %s" , ", " .join (map (str , removed_devices )))
182+ _LOGGER .info ("Removed device(s): %s" , ", " .join (map (str , removed_devices )))
135183 device_registry = dr .async_get (self .hass )
136184
137185 for removed_device in removed_devices :
@@ -144,41 +192,76 @@ def _async_add_remove_devices(self, data: SolarlogData) -> None:
144192 identifiers = {
145193 (
146194 DOMAIN ,
147- f"{ self .unique_id } _{ slugify (device_name )} " ,
195+ f"{ self .config_entry . entry_id } _{ slugify (device_name )} " ,
148196 )
149197 }
150198 ):
151199 device_registry .async_update_device (
152200 device_id = device .id ,
153- remove_config_entry_id = self .unique_id ,
201+ remove_config_entry_id = self .config_entry . entry_id ,
154202 )
155- _LOGGER .debug ("Device removed from device registry: %s" , device .id )
203+ _LOGGER .info ("Device removed from device registry: %s" , device .id )
156204
157205 # add new devices
158206 if new_devices := current_devices - self ._devices_last_update :
159- _LOGGER .debug ("New device(s) found: %s" , ", " .join (map (str , new_devices )))
207+ _LOGGER .info ("New device(s) found: %s" , ", " .join (map (str , new_devices )))
160208 for device_id in new_devices :
161209 for callback in self .new_device_callbacks :
162210 callback (device_id [0 ])
163211
164212 self ._devices_last_update = current_devices
165213
166- async def renew_authentication (self ) -> bool :
167- """Renew access token for SolarLog API."""
168- logged_in = False
214+
215+ class SolarLogLongtimeDataCoordinator (DataUpdateCoordinator [EnergyData ]):
216+ """Get and update the solarlog longtime energy data."""
217+
218+ config_entry : SolarlogConfigEntry
219+
220+ def __init__ (
221+ self ,
222+ hass : HomeAssistant ,
223+ config_entry : SolarlogConfigEntry ,
224+ api : SolarLogConnector ,
225+ timeout : float ,
226+ ) -> None :
227+ """Initialize the data object."""
228+ super ().__init__ (
229+ hass ,
230+ _LOGGER ,
231+ config_entry = config_entry ,
232+ name = "SolarLogLongtimeEnergy" ,
233+ update_interval = timedelta (seconds = timeout * 2 ),
234+ )
235+
236+ self .solarlog = api
237+ self .connection_timeout = timeout
238+
239+ async def _async_update_data (self ) -> EnergyData :
240+ """Update the energy data from the SolarLog device."""
241+ _LOGGER .debug (
242+ "Start energy data update with timeout=%s" , self .connection_timeout
243+ )
244+
169245 try :
170- logged_in = await self .solarlog .login ()
246+ energy_data : EnergyData | None = await self .solarlog .update_energy_data (
247+ timeout = self .connection_timeout
248+ )
171249 except SolarLogAuthenticationError as ex :
172250 raise ConfigEntryAuthFailed (
173251 translation_domain = DOMAIN ,
174252 translation_key = "auth_failed" ,
175253 ) from ex
176254 except (SolarLogConnectionError , SolarLogUpdateError ) as ex :
177- raise ConfigEntryNotReady (
255+ raise UpdateFailed (
178256 translation_domain = DOMAIN ,
179- translation_key = "config_entry_not_ready " ,
257+ translation_key = "update_failed " ,
180258 ) from ex
181259
182- _LOGGER .debug ("Credentials successfully updated? %s" , logged_in )
260+ if energy_data is None :
261+ energy_data = EnergyData (None , None )
183262
184- return logged_in
263+ self .config_entry .runtime_data .basic_data_coordinator .data .self_consumption_year = energy_data .self_consumption
264+
265+ _LOGGER .debug ("Energy data successfully updated" )
266+
267+ return energy_data
0 commit comments