6
6
from __future__ import annotations
7
7
8
8
import logging
9
+ import re
9
10
from datetime import datetime
11
+ from dataclasses import dataclass , field
10
12
11
13
import homeassistant .helpers .config_validation as cv
12
14
import voluptuous as vol
13
- import re
14
15
15
16
from awesomeversion .awesomeversion import AwesomeVersion
16
17
from homeassistant .config_entries import ConfigEntry
17
18
from homeassistant .core import HomeAssistant , callback
18
19
from homeassistant .const import __version__ as HA_VERSION # noqa: N812
19
20
from homeassistant .helpers .typing import ConfigType
20
21
from homeassistant .helpers import device_registry as dr
22
+ from homeassistant .helpers import entity_registry as er
21
23
from homeassistant .util import dt as dt_util
22
24
23
25
from .config_flow import CONFIG_VERSION
24
26
27
+ from .device import BatteryNotesDevice
25
28
from .discovery import DiscoveryManager
26
29
from .library_updater import (
27
30
LibraryUpdater ,
28
31
)
29
- from .coordinator import BatteryNotesCoordinator
30
32
from .store import (
31
33
async_get_registry ,
32
34
)
37
39
PLATFORMS ,
38
40
CONF_ENABLE_AUTODISCOVERY ,
39
41
CONF_USER_LIBRARY ,
42
+ DATA ,
40
43
DATA_LIBRARY_UPDATER ,
41
44
CONF_SHOW_ALL_DEVICES ,
42
45
CONF_ENABLE_REPLACED ,
46
+ CONF_DEFAULT_BATTERY_LOW_THRESHOLD ,
47
+ CONF_BATTERY_INCREASE_THRESHOLD ,
48
+ CONF_HIDE_BATTERY ,
49
+ DEFAULT_BATTERY_LOW_THRESHOLD ,
50
+ DEFAULT_BATTERY_INCREASE_THRESHOLD ,
43
51
SERVICE_BATTERY_REPLACED ,
44
52
SERVICE_BATTERY_REPLACED_SCHEMA ,
45
- DATA_COORDINATOR ,
53
+ SERVICE_DATA_DATE_TIME_REPLACED ,
54
+ DATA_STORE ,
46
55
ATTR_REMOVE ,
47
56
ATTR_DEVICE_ID ,
48
- ATTR_DATE_TIME_REPLACED ,
49
57
CONF_BATTERY_TYPE ,
50
58
CONF_BATTERY_QUANTITY ,
51
59
)
63
71
vol .Optional (CONF_USER_LIBRARY , default = "" ): cv .string ,
64
72
vol .Optional (CONF_SHOW_ALL_DEVICES , default = False ): cv .boolean ,
65
73
vol .Optional (CONF_ENABLE_REPLACED , default = True ): cv .boolean ,
74
+ vol .Optional (CONF_HIDE_BATTERY , default = False ): cv .boolean ,
75
+ vol .Optional (
76
+ CONF_DEFAULT_BATTERY_LOW_THRESHOLD ,
77
+ default = DEFAULT_BATTERY_LOW_THRESHOLD ,
78
+ ): cv .positive_int ,
79
+ vol .Optional (
80
+ CONF_BATTERY_INCREASE_THRESHOLD ,
81
+ default = DEFAULT_BATTERY_INCREASE_THRESHOLD ,
82
+ ): cv .positive_int ,
66
83
},
67
84
),
68
85
),
69
86
},
70
87
extra = vol .ALLOW_EXTRA ,
71
88
)
72
89
90
+
91
+ @dataclass
92
+ class BatteryNotesData :
93
+ """Class for sharing data within the BatteryNotes integration."""
94
+
95
+ devices : dict [str , BatteryNotesDevice ] = field (default_factory = dict )
96
+ platforms : dict = field (default_factory = dict )
97
+
98
+
73
99
async def async_setup (hass : HomeAssistant , config : ConfigType ) -> bool :
74
100
"""Integration setup."""
75
101
@@ -86,60 +112,96 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
86
112
CONF_ENABLE_AUTODISCOVERY : True ,
87
113
CONF_SHOW_ALL_DEVICES : False ,
88
114
CONF_ENABLE_REPLACED : True ,
115
+ CONF_HIDE_BATTERY : False ,
116
+ CONF_DEFAULT_BATTERY_LOW_THRESHOLD : DEFAULT_BATTERY_LOW_THRESHOLD ,
117
+ CONF_BATTERY_INCREASE_THRESHOLD : DEFAULT_BATTERY_INCREASE_THRESHOLD ,
89
118
}
90
119
91
120
hass .data [DOMAIN ] = {
92
121
DOMAIN_CONFIG : domain_config ,
93
122
}
94
123
95
124
store = await async_get_registry (hass )
125
+ hass .data [DOMAIN ][DATA_STORE ] = store
96
126
97
- coordinator = BatteryNotesCoordinator (hass , store )
98
- hass .data [DOMAIN ][DATA_COORDINATOR ] = coordinator
127
+ hass .data [DOMAIN ][DATA ] = BatteryNotesData ()
99
128
100
129
library_updater = LibraryUpdater (hass )
101
130
102
131
await library_updater .get_library_updates (dt_util .utcnow ())
103
132
104
133
hass .data [DOMAIN ][DATA_LIBRARY_UPDATER ] = library_updater
105
134
106
- await coordinator .async_refresh ()
107
-
108
135
if domain_config .get (CONF_ENABLE_AUTODISCOVERY ):
109
136
discovery_manager = DiscoveryManager (hass , config )
110
137
await discovery_manager .start_discovery ()
111
138
else :
112
139
_LOGGER .debug ("Auto discovery disabled" )
113
140
141
+ # Register custom services
142
+ register_services (hass )
143
+
114
144
return True
115
145
116
- async def async_setup_entry (hass : HomeAssistant , entry : ConfigEntry ) -> bool :
117
- """Set up a config entry."""
118
146
119
- await hass .config_entries .async_forward_entry_setups (entry , PLATFORMS )
147
+ async def async_setup_entry (hass : HomeAssistant , config_entry : ConfigEntry ) -> bool :
148
+ """Set up a config entry."""
120
149
121
- entry . async_on_unload ( entry . add_update_listener ( async_update_options ) )
150
+ device : BatteryNotesDevice = BatteryNotesDevice ( hass , config_entry )
122
151
123
- # Register custom services
124
- register_services ( hass )
152
+ if not await device . async_setup ():
153
+ return False
125
154
126
155
return True
127
156
128
157
158
+ async def async_unload_entry (hass : HomeAssistant , config_entry : ConfigEntry ) -> bool :
159
+ """Unload a config entry."""
160
+ data : BatteryNotesData = hass .data [DOMAIN ][DATA ]
161
+
162
+ device = data .devices .pop (config_entry .entry_id )
163
+ await device .async_unload ()
164
+
165
+ return await hass .config_entries .async_unload_platforms (config_entry , PLATFORMS )
166
+
167
+
129
168
async def async_remove_entry (hass : HomeAssistant , config_entry : ConfigEntry ) -> None :
130
169
"""Device removed, tidy up store."""
131
170
132
171
if "device_id" not in config_entry .data :
133
172
return
134
173
135
- device_id = config_entry .data ["device_id" ]
174
+ device : BatteryNotesDevice = hass .data [DOMAIN ][DATA ].devices [config_entry .entry_id ]
175
+ if not device :
176
+ return
136
177
137
- coordinator : BatteryNotesCoordinator = hass .data [DOMAIN ][DATA_COORDINATOR ]
138
178
data = {ATTR_REMOVE : True }
139
179
140
- coordinator .async_update_device_config (device_id = device_id , data = data )
180
+ device .coordinator .async_update_device_config (
181
+ device_id = device .coordinator .device_id , data = data
182
+ )
183
+
184
+ _LOGGER .debug ("Removed Device %s" , device .coordinator .device_id )
185
+
186
+ # Unhide the battery
187
+ entity_registry = er .async_get (hass )
188
+ if not device .wrapped_battery :
189
+ return
190
+
191
+ if not (
192
+ wrapped_battery_entity_entry := entity_registry .async_get (
193
+ device .wrapped_battery .entity_id
194
+ )
195
+ ):
196
+ return
141
197
142
- _LOGGER .debug ("Removed Device %s" , device_id )
198
+ if wrapped_battery_entity_entry .hidden_by == er .RegistryEntryHider .INTEGRATION :
199
+ entity_registry .async_update_entity (
200
+ device .wrapped_battery .entity_id , hidden_by = None
201
+ )
202
+ _LOGGER .debug (
203
+ "Unhidden Original Battery for device%s" , device .coordinator .device_id
204
+ )
143
205
144
206
145
207
async def async_migrate_entry (hass , config_entry : ConfigEntry ):
@@ -180,43 +242,47 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
180
242
181
243
return True
182
244
245
+
183
246
@callback
184
247
async def async_update_options (hass : HomeAssistant , entry : ConfigEntry ) -> None :
185
248
"""Update options."""
186
249
await hass .config_entries .async_reload (entry .entry_id )
187
250
188
251
189
- async def async_unload_entry (hass : HomeAssistant , entry : ConfigEntry ) -> bool :
190
- """Unload a config entry."""
191
- return await hass .config_entries .async_unload_platforms (entry , PLATFORMS )
192
-
193
-
194
252
@callback
195
253
def register_services (hass ):
196
254
"""Register services used by battery notes component."""
197
255
198
256
async def handle_battery_replaced (call ):
199
257
"""Handle the service call."""
200
258
device_id = call .data .get (ATTR_DEVICE_ID , "" )
201
- datetime_replaced_entry = call .data .get (ATTR_DATE_TIME_REPLACED )
259
+ datetime_replaced_entry = call .data .get (SERVICE_DATA_DATE_TIME_REPLACED )
202
260
203
261
if datetime_replaced_entry :
204
- datetime_replaced = dt_util .as_utc (datetime_replaced_entry ).replace (tzinfo = None )
262
+ datetime_replaced = dt_util .as_utc (datetime_replaced_entry ).replace (
263
+ tzinfo = None
264
+ )
205
265
else :
206
266
datetime_replaced = datetime .utcnow ()
207
267
208
268
device_registry = dr .async_get (hass )
209
269
210
270
device_entry = device_registry .async_get (device_id )
211
271
if not device_entry :
272
+ _LOGGER .error (
273
+ "Device %s not found" ,
274
+ device_id ,
275
+ )
212
276
return
213
277
214
278
for entry_id in device_entry .config_entries :
215
279
if (
216
280
entry := hass .config_entries .async_get_entry (entry_id )
217
281
) and entry .domain == DOMAIN :
282
+ coordinator = (
283
+ hass .data [DOMAIN ][DATA ].devices [entry .entry_id ].coordinator
284
+ )
218
285
219
- coordinator : BatteryNotesCoordinator = hass .data [DOMAIN ][DATA_COORDINATOR ]
220
286
device_entry = {"battery_last_replaced" : datetime_replaced }
221
287
222
288
coordinator .async_update_device_config (
@@ -226,9 +292,19 @@ async def handle_battery_replaced(call):
226
292
await coordinator .async_request_refresh ()
227
293
228
294
_LOGGER .debug (
229
- "Device %s battery replaced on %s" , device_id , str (datetime_replaced )
295
+ "Device %s battery replaced on %s" ,
296
+ device_id ,
297
+ str (datetime_replaced ),
230
298
)
231
299
300
+ # Found and dealt with, exit
301
+ return
302
+
303
+ _LOGGER .error (
304
+ "Device %s not configured in Battery Notes" ,
305
+ device_id ,
306
+ )
307
+
232
308
hass .services .async_register (
233
309
DOMAIN ,
234
310
SERVICE_BATTERY_REPLACED ,
0 commit comments