11"""The unofficial EcoFlow BLE devices integration"""
22
3- from __future__ import annotations
4-
53import logging
4+ from functools import partial
65
6+ import homeassistant .helpers .issue_registry as ir
77from homeassistant .components import bluetooth
88from homeassistant .config_entries import ConfigEntry
99from homeassistant .const import CONF_ADDRESS , CONF_TYPE , Platform
1010from homeassistant .core import HomeAssistant
11- from homeassistant .exceptions import ConfigEntryNotReady
11+ from homeassistant .exceptions import (
12+ ConfigEntryError ,
13+ ConfigEntryNotReady ,
14+ )
1215from homeassistant .helpers .device_registry import DeviceInfo
1316
1417from . import eflib
1518from .config_flow import ConfLogOptions , LogOptions
16- from .const import CONF_UPDATE_PERIOD , CONF_USER_ID , DOMAIN , MANUFACTURER
19+ from .const import (
20+ CONF_CONNECTION_TIMEOUT ,
21+ CONF_UPDATE_PERIOD ,
22+ CONF_USER_ID ,
23+ DEFAULT_CONNECTION_TIMEOUT ,
24+ DEFAULT_UPDATE_PERIOD ,
25+ DOMAIN ,
26+ MANUFACTURER ,
27+ )
28+ from .eflib .connection import (
29+ AuthFailedError ,
30+ BleakError ,
31+ ConnectionTimeout ,
32+ MaxConnectionAttemptsReached ,
33+ )
1734
1835PLATFORMS : list [Platform ] = [
1936 Platform .SENSOR ,
2744
2845_LOGGER = logging .getLogger (__name__ )
2946
30-
31- class _ConfigNotReady (ConfigEntryNotReady ):
32- def __init__ (
33- self ,
34- translation_key : str | None = None ,
35- translation_placeholders : dict [str , str ] | None = None ,
36- ) -> None :
37- super ().__init__ (
38- translation_domain = DOMAIN ,
39- translation_key = translation_key ,
40- translation_placeholders = translation_placeholders ,
41- )
47+ ConfigEntryNotReady = partial (ConfigEntryNotReady , translation_domain = DOMAIN )
48+ ConfigEntryError = partial (ConfigEntryError , translation_domain = DOMAIN )
4249
4350
4451async def async_setup_entry (hass : HomeAssistant , entry : DeviceConfigEntry ) -> bool :
@@ -49,45 +56,91 @@ async def async_setup_entry(hass: HomeAssistant, entry: DeviceConfigEntry) -> bo
4956 address = entry .data .get (CONF_ADDRESS )
5057 user_id = entry .data .get (CONF_USER_ID )
5158 merged_options = entry .data | entry .options
52- update_period = merged_options .get (CONF_UPDATE_PERIOD , 0 )
59+ update_period = merged_options .get (CONF_UPDATE_PERIOD , DEFAULT_UPDATE_PERIOD )
60+ timeout = merged_options .get (CONF_CONNECTION_TIMEOUT , DEFAULT_CONNECTION_TIMEOUT )
5361
5462 if address is None or user_id is None :
5563 return False
5664
5765 if not bluetooth .async_address_present (hass , address ):
58- raise _ConfigNotReady ( "device_not_present" )
66+ raise ConfigEntryNotReady ( translation_key = "device_not_present" )
5967
6068 _LOGGER .debug ("Connecting Device" )
61- discovery_info = bluetooth .async_last_service_info (hass , address , connectable = True )
62- device = eflib .NewDevice (discovery_info .device , discovery_info .advertisement )
69+ device : eflib .DeviceBase | None = getattr (entry , "runtime_data" , None )
6370 if device is None :
64- raise _ConfigNotReady ("unable_to_create_device" )
71+ discovery_info = bluetooth .async_last_service_info (
72+ hass , address , connectable = True
73+ )
74+ device = eflib .NewDevice (discovery_info .device , discovery_info .advertisement )
75+ if device is None :
76+ raise ConfigEntryNotReady (translation_key = "unable_to_create_device" )
6577
66- await (
67- device .with_update_period (update_period )
68- .with_logging_options (ConfLogOptions .from_config (merged_options ))
69- .connect (user_id )
70- )
71- entry .runtime_data = device
78+ entry .runtime_data = device
7279
73- timeout = 30
74- state = await device .wait_until_connected_or_error (timeout = timeout )
80+ issue_id = f"{ entry .entry_id } _max_connection_attempts"
7581
76- if state .connection_error ():
77- raise _ConfigNotReady (
78- "could_not_connect" , translation_placeholders = {"time" : str (timeout )}
82+ try :
83+ await (
84+ device .with_update_period (update_period )
85+ .with_logging_options (ConfLogOptions .from_config (merged_options ))
86+ .with_disabled_reconnect ()
87+ .connect (user_id , timeout = timeout )
7988 )
80- if state .is_error ():
81- raise _ConfigNotReady ("error_after_connected" )
82- if not state .authenticated ():
83- raise _ConfigNotReady ("could_not_authenticate" )
89+ state = await device .wait_until_authenticated_or_error (raise_on_error = True )
90+ except (ConnectionTimeout , BleakError , TimeoutError ) as e :
91+ raise ConfigEntryNotReady (
92+ translation_key = "could_not_connect" ,
93+ translation_placeholders = {"time" : str (timeout )},
94+ ) from e
95+ except AuthFailedError as e :
96+ raise ConfigEntryNotReady (translation_key = "authentication_failed" ) from e
97+ except MaxConnectionAttemptsReached as e :
98+ await device .disconnect ()
99+ ir .async_create_issue (
100+ hass ,
101+ DOMAIN ,
102+ issue_id ,
103+ is_fixable = False ,
104+ severity = ir .IssueSeverity .ERROR ,
105+ translation_key = "max_connection_attempts_reached" ,
106+ translation_placeholders = {
107+ "device_name" : device .name ,
108+ "attempts" : str (e .attempts ),
109+ },
110+ )
111+ raise ConfigEntryError (
112+ translation_key = "could_not_connect_no_retry" ,
113+ translation_placeholders = {"attempts" : str (e .attempts )},
114+ ) from e
115+ except Exception as e :
116+ _LOGGER .exception ("Unknown error" )
117+ await device .disconnect ()
118+ raise ConfigEntryNotReady (
119+ translation_key = "unknown_error" , translation_placeholders = {"error" : str (e )}
120+ ) from e
121+ else :
122+ if not state .authenticated ():
123+ await device .disconnect ()
124+ raise ConfigEntryNotReady (
125+ translation_key = "failed_after_successful_connection" ,
126+ translation_placeholders = {"last_state" : state },
127+ )
128+ ir .async_delete_issue (hass , DOMAIN , issue_id )
84129
85130 _LOGGER .debug ("Creating entities" )
86131 await hass .config_entries .async_forward_entry_setups (entry , PLATFORMS )
87132
88133 _LOGGER .debug ("Setup done" )
89134 entry .async_on_unload (entry .add_update_listener (_update_listener ))
90135
136+ def _on_disconnect (exc : Exception | type [Exception ] | None ):
137+ async def _disconnect_and_reload ():
138+ hass .config_entries .async_schedule_reload (entry .entry_id )
139+
140+ hass .async_create_task (_disconnect_and_reload ())
141+
142+ entry .async_on_unload (device .on_disconnect (_on_disconnect ))
143+
91144 return True
92145
93146
@@ -112,7 +165,7 @@ def device_info(entry: ConfigEntry) -> DeviceInfo:
112165async def _update_listener (hass : HomeAssistant , entry : DeviceConfigEntry ):
113166 device = entry .runtime_data
114167 merged_options = entry .data | entry .options
115- update_period = merged_options .get (CONF_UPDATE_PERIOD , 0 )
116- device .with_update_period (update_period ).with_logging_options (
168+ update_period = merged_options .get (CONF_UPDATE_PERIOD , DEFAULT_UPDATE_PERIOD )
169+ device .with_update_period (period = update_period ).with_logging_options (
117170 ConfLogOptions .from_config (merged_options )
118171 )
0 commit comments