3636
3737PLATFORMS : list [Platform ] = [Platform .SENSOR , Platform .BINARY_SENSOR ]
3838
39+
3940async def async_setup (hass : HomeAssistant , config : dict ) -> bool :
4041 """Set up the Lumentree integration."""
4142 # Config flow is handled automatically by Home Assistant
4243 # when config_flow: true is set in manifest.json
4344 return True
4445
46+
4547async def async_setup_entry (hass : HomeAssistant , entry : ConfigEntry ) -> bool :
4648 """Set up Lumentree from a config entry."""
4749 _LOGGER .info (f"Setting up Lumentree: { entry .title } ({ entry .entry_id } )" )
@@ -74,7 +76,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
7476 if "_error" in device_api_info :
7577 _LOGGER .error (f"API error getting device info: { device_api_info ['_error' ]} " )
7678 raise ConfigEntryNotReady (f"API error: { device_api_info ['_error' ]} " )
77- hass .data [DOMAIN ][entry .entry_id ][' device_api_info' ] = device_api_info
79+ hass .data [DOMAIN ][entry .entry_id ][" device_api_info" ] = device_api_info
7880 _LOGGER .info (
7981 f"Stored API info: Model={ device_api_info .get ('deviceType' )} , "
8082 f"ID={ device_api_info .get ('deviceId' )} "
@@ -95,12 +97,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
9597 monthly_coord = MonthlyStatsCoordinator (hass , aggregator , device_sn , entry .entry_id )
9698 yearly_coord = YearlyStatsCoordinator (hass , aggregator , device_sn , entry .entry_id )
9799 total_coord = TotalStatsCoordinator (hass , aggregator , device_sn , entry .entry_id )
98- hass .data [DOMAIN ][entry .entry_id ].update ({
99- "daily_coordinator" : daily_coord ,
100- "monthly_coordinator" : monthly_coord ,
101- "yearly_coordinator" : yearly_coord ,
102- "total_coordinator" : total_coord ,
103- })
100+ hass .data [DOMAIN ][entry .entry_id ].update (
101+ {
102+ "daily_coordinator" : daily_coord ,
103+ "monthly_coordinator" : monthly_coord ,
104+ "yearly_coordinator" : yearly_coord ,
105+ "total_coordinator" : total_coord ,
106+ }
107+ )
104108 _LOGGER .info (f"Created total coordinator for { device_sn } " )
105109
106110 # Prime daily coordinator first (non-blocking), then stats coordinators after delay
@@ -205,7 +209,9 @@ async def _async_stop_mqtt(event: Event) -> None:
205209 await client_to_stop .disconnect ()
206210
207211 entry .async_on_unload (_cancel_timer_on_unload )
208- entry .async_on_unload (hass .bus .async_listen_once (EVENT_HOMEASSISTANT_STOP , _async_stop_mqtt ))
212+ entry .async_on_unload (
213+ hass .bus .async_listen_once (EVENT_HOMEASSISTANT_STOP , _async_stop_mqtt )
214+ )
209215
210216 # Services: backfill_now, recompute_month_year, purge_cache, backfill_all, backfill_gaps,
211217 # mark_empty_dates, mark_coverage_range
@@ -218,9 +224,7 @@ def _required_year(call) -> int:
218224 """
219225 raw = call .data .get ("year" )
220226 if raw is None :
221- raise HomeAssistantError (
222- "The 'year' field is required for this service."
223- )
227+ raise HomeAssistantError ("The 'year' field is required for this service." )
224228 try :
225229 return int (raw )
226230 except (TypeError , ValueError ) as err :
@@ -294,19 +298,25 @@ async def _svc_purge_and_backfill(call):
294298 max_years = int (max_years_val ) if max_years_val is not None else 5
295299 optimize_cache = call .data .get ("optimize_cache" , True )
296300
297- _LOGGER .warning (f"Purging ALL cache for device { device_id } and starting fresh smart backfill..." )
301+ _LOGGER .warning (
302+ f"Purging ALL cache for device { device_id } and starting fresh smart backfill..."
303+ )
298304 result = await hass .async_add_executor_job (cache_io .purge_device , device_id )
299305 _LOGGER .info (f"Purge result: { result } " )
300306
301307 _LOGGER .info (f"Starting smart backfill for { max_years } years..." )
302- stats = await aggregator .smart_backfill (max_years = max_years , optimize_cache = optimize_cache )
308+ stats = await aggregator .smart_backfill (
309+ max_years = max_years , optimize_cache = optimize_cache
310+ )
303311 _LOGGER .info (f"Purge and smart backfill completed: { stats } " )
304312
305313 async def _svc_smart_backfill (call ):
306314 """Smart backfill using getYearData/getMonthData APIs."""
307315 max_years = int (call .data .get ("max_years" , 10 ))
308316 optimize_cache = call .data .get ("optimize_cache" , True )
309- stats = await aggregator .smart_backfill (max_years = max_years , optimize_cache = optimize_cache )
317+ stats = await aggregator .smart_backfill (
318+ max_years = max_years , optimize_cache = optimize_cache
319+ )
310320 _LOGGER .info (f"Smart backfill completed: { stats } " )
311321
312322 async def _svc_backfill_all (call ):
@@ -323,7 +333,9 @@ async def _svc_backfill_gaps(call):
323333 async def _svc_backfill_empty_dates (call ):
324334 max_years = int (call .data .get ("max_years" , 5 ))
325335 max_days_per_run = int (call .data .get ("max_days_per_run" , 100 ))
326- result = await aggregator .backfill_empty_dates (max_years = max_years , max_days_per_run = max_days_per_run )
336+ result = await aggregator .backfill_empty_dates (
337+ max_years = max_years , max_days_per_run = max_days_per_run
338+ )
327339 _LOGGER .info (
328340 f"backfill_empty_dates service completed: "
329341 f"recovered={ result .get ('recovered' , 0 )} , "
@@ -336,7 +348,9 @@ async def _svc_enable_purge_on_startup(call):
336348 new_options = entry .options .copy ()
337349 new_options ["purge_and_backfill_on_startup" ] = True
338350 hass .config_entries .async_update_entry (entry , options = new_options )
339- _LOGGER .warning ("purge_and_backfill_on_startup has been enabled. It will run on next restart and auto-disable after completion." )
351+ _LOGGER .warning (
352+ "purge_and_backfill_on_startup has been enabled. It will run on next restart and auto-disable after completion."
353+ )
340354
341355 async def _svc_disable_purge_on_startup (call ):
342356 """Disable purge and backfill on startup."""
@@ -386,8 +400,12 @@ def _mark() -> None:
386400 hass .services .async_register (DOMAIN , "backfill_empty_dates" , _svc_backfill_empty_dates )
387401 hass .services .async_register (DOMAIN , "mark_empty_dates" , _svc_mark_empty_dates )
388402 hass .services .async_register (DOMAIN , "mark_coverage_range" , _svc_mark_coverage_range )
389- hass .services .async_register (DOMAIN , "enable_purge_on_startup" , _svc_enable_purge_on_startup )
390- hass .services .async_register (DOMAIN , "disable_purge_on_startup" , _svc_disable_purge_on_startup )
403+ hass .services .async_register (
404+ DOMAIN , "enable_purge_on_startup" , _svc_enable_purge_on_startup
405+ )
406+ hass .services .async_register (
407+ DOMAIN , "disable_purge_on_startup" , _svc_disable_purge_on_startup
408+ )
391409
392410 # Auto backfill: first-run (background) and nightly delta
393411 # Check if we need to purge and backfill on startup (from entry options or default False)
@@ -396,27 +414,35 @@ def _mark() -> None:
396414 async def _first_run_backfill () -> None :
397415 try :
398416 if should_purge_on_startup :
399- _LOGGER .warning ("PURGE_AND_BACKFILL_ON_STARTUP is enabled - purging all cache..." )
417+ _LOGGER .warning (
418+ "PURGE_AND_BACKFILL_ON_STARTUP is enabled - purging all cache..."
419+ )
400420 result = await hass .async_add_executor_job (cache_io .purge_device , device_id )
401421 _LOGGER .info (f"Purge result: { result } " )
402422 _LOGGER .warning ("Starting smart backfill for 5 years..." )
403423 # Use smart backfill for faster performance
404424 stats = await aggregator .smart_backfill (max_years = 5 , optimize_cache = True )
405425 _LOGGER .info (f"Smart backfill completed: { stats } " )
406426 else :
407- _LOGGER .info ("Auto backfill: starting smart backfill for last 5 years (background)" )
427+ _LOGGER .info (
428+ "Auto backfill: starting smart backfill for last 5 years (background)"
429+ )
408430 # Use smart backfill - much faster than daily backfill
409431 stats = await aggregator .smart_backfill (max_years = 5 , optimize_cache = True )
410432 _LOGGER .info (f"Auto backfill: completed 5-year history - { stats } " )
411433
412434 # Auto-disable purge_and_backfill_on_startup after successful backfill
413435 if should_purge_on_startup :
414- _LOGGER .info ("Backfill completed successfully. Auto-disabling PURGE_AND_BACKFILL_ON_STARTUP..." )
436+ _LOGGER .info (
437+ "Backfill completed successfully. Auto-disabling PURGE_AND_BACKFILL_ON_STARTUP..."
438+ )
415439 # Update entry options to disable the flag
416440 new_options = entry .options .copy ()
417441 new_options ["purge_and_backfill_on_startup" ] = False
418442 hass .config_entries .async_update_entry (entry , options = new_options )
419- _LOGGER .info ("PURGE_AND_BACKFILL_ON_STARTUP has been automatically set to False" )
443+ _LOGGER .info (
444+ "PURGE_AND_BACKFILL_ON_STARTUP has been automatically set to False"
445+ )
420446 except Exception as err :
421447 _LOGGER .error (f"Auto backfill initial failed: { err } " )
422448
@@ -439,7 +465,9 @@ async def _nightly_delta(now=None):
439465 hass .async_create_task (_first_run_backfill ())
440466
441467 # Schedule nightly job every 24h — store before risky step so we can clean up on failure
442- remove_nightly = async_track_time_interval (hass , _nightly_delta , datetime .timedelta (hours = 24 ))
468+ remove_nightly = async_track_time_interval (
469+ hass , _nightly_delta , datetime .timedelta (hours = 24 )
470+ )
443471 hass .data [DOMAIN ][entry .entry_id ]["remove_nightly" ] = remove_nightly
444472
445473 await hass .config_entries .async_forward_entry_setups (entry , PLATFORMS )
@@ -475,6 +503,7 @@ async def _nightly_delta(now=None):
475503 hass .data [DOMAIN ].pop (entry .entry_id , None )
476504 return False
477505
506+
478507async def async_unload_entry (hass : HomeAssistant , entry : ConfigEntry ) -> bool :
479508 """Unload a config entry."""
480509 device_sn = entry .data .get (CONF_DEVICE_SN , "unknown" )
@@ -508,7 +537,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
508537 _LOGGER .warning (f"Error cancelling nightly timer { device_sn } : { timer_err } " )
509538
510539 # Cleanup coordinators (they should be cleaned up by platform unload, but ensure cleanup)
511- for coord_key in ["daily_coordinator" , "monthly_coordinator" , "yearly_coordinator" , "total_coordinator" ]:
540+ for coord_key in [
541+ "daily_coordinator" ,
542+ "monthly_coordinator" ,
543+ "yearly_coordinator" ,
544+ "total_coordinator" ,
545+ ]:
512546 coord = entry_data .get (coord_key )
513547 if coord and hasattr (coord , "async_shutdown" ):
514548 try :
0 commit comments