1- from datetime import timedelta , datetime
2- import re
1+ from datetime import datetime , timedelta
2+ import asyncio
33import logging
4+ import re
5+
46import aiohttp
57from bs4 import BeautifulSoup
68
7- from homeassistant .core import HomeAssistant
9+ from homeassistant .core import HomeAssistant , callback
810from homeassistant .components .sensor import SensorEntity
911from homeassistant .config_entries import ConfigEntry
1012from homeassistant .helpers .entity_platform import AddEntitiesCallback
1113from homeassistant .helpers .update_coordinator import DataUpdateCoordinator , CoordinatorEntity , UpdateFailed
1214from homeassistant .helpers .entity import EntityCategory
1315from homeassistant .helpers .restore_state import RestoreEntity
14-
16+ from homeassistant . helpers . event import async_track_state_change_event
1517
1618from .const import DOMAIN , DEFAULT_INTERVAL , DEFAULT_URL , DEFAULT_WALLBOX_API_ENDPOINT
1719
20+
1821_LOGGER = logging .getLogger (__name__ )
1922
2023
@@ -75,12 +78,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e
7578 groups = entry .options .get ("groups" , [])
7679 _LOGGER .debug ("[Enpal] Configuration - URL: %s, Interval: %s, Groups: %s" , url , interval , groups )
7780
81+ last_successful_data = []
82+
7883 async def async_update_data ():
84+ nonlocal last_successful_data
7985 try :
8086 async with aiohttp .ClientSession () as session :
8187 async with session .get (url , timeout = 30 ) as resp :
8288 if resp .status != 200 :
83- raise UpdateFailed (f"Unexpected status code: { resp .status } " )
89+ raise Exception (f"Unexpected status code: { resp .status } " )
8490 html = await resp .text ()
8591
8692 _LOGGER .debug ("[Enpal] HTML content fetched successfully from %s" , url )
@@ -92,7 +98,6 @@ async def async_update_data():
9298 for card in cards :
9399 group = card .find ("h2" ).text .strip ()
94100 if group not in groups :
95- _LOGGER .debug ("[Enpal] Skipping group not in selected groups: %s" , group )
96101 continue
97102
98103 rows = card .find_all ("tr" )[1 :]
@@ -139,14 +144,19 @@ async def async_update_data():
139144 "enabled" : group in groups ,
140145 "enpal_last_update" : timestamp_iso
141146 }
142- _LOGGER .debug ("[Enpal] Parsed sensor: %s" , sensor_data )
143147 sensors .append (sensor_data )
144148
145149 _LOGGER .info ("[Enpal] Loaded %d sensor(s) from HTML" , len (sensors ))
150+ last_successful_data = sensors
146151 return sensors
152+
147153 except Exception as e :
148- _LOGGER .exception ("[Enpal] Error during HTML parsing or request: %s" , e )
149- raise UpdateFailed (f"Data fetch failed: { e } " )
154+ if last_successful_data :
155+ _LOGGER .warning ("[Enpal] Error during update, using last known good values: %s" , e )
156+ return last_successful_data
157+ else :
158+ _LOGGER .exception ("[Enpal] No previous data available" )
159+ raise UpdateFailed (f"Initial data fetch failed: { e } " )
150160
151161 coordinator = DataUpdateCoordinator (
152162 hass ,
@@ -165,6 +175,10 @@ async def async_update_data():
165175 hass .data [DOMAIN ]["coordinator" ] = coordinator
166176
167177 await coordinator .async_config_entry_first_refresh ()
178+ _LOGGER .info ("[Enpal] Verfügbare Sensoren nach HTML-Parsing:" )
179+ for sensor in coordinator .data :
180+ _LOGGER .info ("[Enpal] Name: %s -> UID: %s" , sensor ["name" ], make_id (sensor ["name" ]))
181+
168182
169183 entities = []
170184 for sensor in coordinator .data :
@@ -173,26 +187,34 @@ async def async_update_data():
173187 entities .append (EnpalSensor (uid , sensor , coordinator ))
174188
175189 entities .append (CumulativeEnergySensor (hass , coordinator , "Inverter Power DC Total (Huawei)" , interval ))
176- entities .append (DailyResetEnergySensor (hass , coordinator , "Inverter: Energy produced today (DC)" ))
190+ entities .append (DailyResetFromEntitySensor (hass , "sensor.inverter_energy_produced_total_dc" ))
191+
177192
178193
179194 if entry .options .get ("use_wallbox_addon" , False ):
180195 wallbox_url = f"{ DEFAULT_WALLBOX_API_ENDPOINT } /status"
181-
182196 _LOGGER .info ("[Enpal] Wallbox add-on enabled, URL: %s" , wallbox_url )
183197
198+ wallbox_data = {}
199+
184200 async def async_wallbox_update ():
201+ nonlocal wallbox_data
185202 try :
186203 async with aiohttp .ClientSession () as session :
187- async with session .get (wallbox_url , timeout = 30 ) as resp :
204+ async with session .get (wallbox_url , timeout = 10 ) as resp :
188205 if resp .status != 200 :
189- raise UpdateFailed (f"Wallbox API Error: { resp .status } " )
206+ raise Exception (f"Wallbox API Error: { resp .status } " )
190207 data = await resp .json ()
191208 _LOGGER .debug ("[Enpal] Wallbox status data: %s" , data )
209+ wallbox_data = data
192210 return data
193211 except Exception as e :
194- _LOGGER .exception ("[Enpal] Error fetching wallbox status: %s" , e )
195- raise UpdateFailed (f"Wallbox update failed: { e } " )
212+ if wallbox_data :
213+ _LOGGER .warning ("[Enpal] Wallbox update failed – using last known data: %s" , e )
214+ return wallbox_data
215+ else :
216+ _LOGGER .warning ("[Enpal] Wallbox update failed – no previous data yet: %s" , e )
217+ raise UpdateFailed (f"Wallbox update failed and no previous data: { e } " )
196218
197219 wallbox_coordinator = DataUpdateCoordinator (
198220 hass ,
@@ -202,12 +224,16 @@ async def async_wallbox_update():
202224 update_interval = timedelta (seconds = interval ),
203225 )
204226
205- await wallbox_coordinator .async_config_entry_first_refresh ()
227+ # KEIN await – Hintergrund-Task starten, damit Home Assistant nicht crasht
228+ hass .async_create_task (wallbox_coordinator .async_refresh ())
206229
207230 entities .extend ([
208231 WallboxModeSensor (wallbox_coordinator ),
209232 WallboxStatusSensor (wallbox_coordinator ),
210233 ])
234+ _LOGGER .debug ("[Enpal] Wallbox-Sensoren hinzugefügt" )
235+
236+
211237
212238 async_add_entities (entities )
213239
@@ -327,19 +353,19 @@ def device_info(self):
327353 }
328354
329355
330- class DailyResetEnergySensor (SensorEntity , RestoreEntity ):
331- def __init__ (self , hass : HomeAssistant , coordinator : DataUpdateCoordinator , sensor_name : str ):
356+
357+
358+ class DailyResetFromEntitySensor (SensorEntity , RestoreEntity ):
359+ def __init__ (self , hass : HomeAssistant , source_entity_id : str ):
332360 self .hass = hass
333- self ._attr_name = "Inverter: Energy produced today (DC)"
361+ self ._attr_name = "Inverter: Energy produced total (DC)"
334362 self ._attr_unique_id = "daily_energy_produced_dc_kwh"
335363 self ._attr_device_class = "energy"
336364 self ._attr_state_class = "total"
337365 self ._attr_native_unit_of_measurement = "kWh"
338366 self ._attr_icon = "mdi:calendar-refresh"
339- self ._coordinator = coordinator
340- self ._source_uid = make_id (sensor_name )
367+ self ._source_entity_id = source_entity_id
341368 self ._today_start_value = None
342- self ._last_total = None
343369 self ._value = 0.0
344370 self ._last_reset = datetime .now ().date ()
345371
@@ -351,59 +377,50 @@ def native_value(self):
351377 def extra_state_attributes (self ):
352378 return {
353379 "last_reset" : self ._last_reset .isoformat (),
354- "start_value" : self ._today_start_value ,
355- "last_total" : self ._last_total ,
380+ "start_value" : self ._today_start_value if self ._today_start_value is not None else "Not set"
356381 }
357382
358383 async def async_added_to_hass (self ):
359- await super ().async_added_to_hass ()
384+ await RestoreEntity .async_added_to_hass (self )
385+ # Statt self.hass.helpers.event.async_track_state_change_event(...),
386+ # verwende den importierten async_track_state_change_event:
387+
388+ async_track_state_change_event (
389+ self .hass , self ._source_entity_id , self ._handle_state_update
390+ )
360391 last_state = await self .async_get_last_state ()
361-
362392 if last_state and last_state .state not in (None , 'unknown' , 'unavailable' ):
363393 try :
364394 self ._value = float (last_state .state )
365- _LOGGER .info ("[Enpal] Restored daily value: %.3f kWh" , self ._value )
366- except ValueError :
367- self ._value = 0.0
368-
369- self ._today_start_value = self ._try_restore_float (last_state .attributes .get ("start_value" ))
370- self ._last_total = self ._try_restore_float (last_state .attributes .get ("last_total" ))
371- last_reset_str = last_state .attributes .get ("last_reset" )
372- if last_reset_str :
373- try :
395+ self ._today_start_value = self ._try_float (last_state .attributes .get ("start_value" ))
396+ last_reset_str = last_state .attributes .get ("last_reset" )
397+ if last_reset_str :
374398 self ._last_reset = datetime .fromisoformat (last_reset_str ).date ()
375- except Exception :
376- self . _last_reset = datetime . now (). date ()
399+ except Exception :
400+ pass
377401
378- self ._coordinator .async_add_listener (self ._handle_coordinator_update )
379-
380- def _try_restore_float (self , value ):
402+ def _try_float (self , val ):
381403 try :
382- return float (value )
404+ return float (val )
383405 except (TypeError , ValueError ):
384406 return None
385407
386- def _handle_coordinator_update (self ):
408+ @callback
409+ def _handle_state_update (self , event ):
387410 today = datetime .now ().date ()
388- for sensor in self ._coordinator .data :
389- if make_id (sensor ["name" ]) == self ._source_uid :
390- try :
391- total_kwh = float (sensor ["value" ])
392- _LOGGER .debug ("[Enpal] Tageswert: Gesamt=%.3f, Start=%.3f" , total_kwh , self ._today_start_value or 0.0 )
393-
394- if self ._last_reset != today or self ._today_start_value is None :
395- _LOGGER .info ("[Enpal] Neuer Tag erkannt – Tagesstartwert: %.3f" , total_kwh )
396- self ._today_start_value = total_kwh
397- self ._last_reset = today
398- self ._value = 0.0
411+ try :
412+ new_total = float (event .data ["new_state" ].state )
413+ except (TypeError , ValueError ):
414+ return
399415
400- self ._value = max (total_kwh - self ._today_start_value , 0 )
401- self ._last_total = total_kwh
402- except Exception as e :
403- _LOGGER .warning ("[Enpal] Fehler bei Tageswert-Berechnung: %s" , e )
404- break
416+ if self ._today_start_value is None or self ._last_reset != today :
417+ self ._today_start_value = new_total
418+ self ._last_reset = today
419+ self ._value = 0.0
420+ else :
421+ self ._value = max (new_total - self ._today_start_value , 0 )
405422
406- self .async_write_ha_state ()
423+ self .async_schedule_update_ha_state ()
407424
408425 @property
409426 def device_info (self ):
@@ -415,6 +432,7 @@ def device_info(self):
415432 }
416433
417434
435+
418436class WallboxCoordinatorEntity (CoordinatorEntity , SensorEntity ):
419437 def __init__ (self , coordinator , name , unique_id , key ):
420438 super ().__init__ (coordinator )
@@ -435,7 +453,9 @@ def device_info(self):
435453
436454 @property
437455 def native_value (self ):
438- return self .coordinator .data .get (self ._key )
456+ if self .coordinator .data :
457+ return self .coordinator .data .get (self ._key )
458+ return None
439459
440460
441461class WallboxModeSensor (WallboxCoordinatorEntity ):
0 commit comments