Skip to content

Commit 2e48a85

Browse files
committed
Unsubscribe Alarmo handlers cleanly on unload
1 parent 5a484f5 commit 2e48a85

6 files changed

Lines changed: 77 additions & 35 deletions

File tree

custom_components/alarmo/__init__.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -511,23 +511,25 @@ def async_update_sensor_group_config(
511511

512512
async_dispatcher_send(self.hass, "alarmo_sensors_updated")
513513

514-
async def async_unload(self):
515-
"""Remove all alarmo objects."""
516-
# remove alarm_control_panel entities
517-
areas = list(self.hass.data[const.DOMAIN]["areas"].keys())
518-
for area in areas:
519-
await self.async_remove_entity(area)
520-
if self.hass.data[const.DOMAIN]["master"]:
521-
await self.async_remove_entity("master")
522-
523-
del self.hass.data[const.DOMAIN]["sensor_handler"]
524-
del self.hass.data[const.DOMAIN]["automation_handler"]
525-
del self.hass.data[const.DOMAIN]["mqtt_handler"]
526-
del self.hass.data[const.DOMAIN]["event_handler"]
527-
528-
# remove subscriptions for coordinator
529-
while len(self._subscriptions):
530-
self._subscriptions.pop()()
514+
async def async_unload(self):
515+
"""Remove all alarmo objects."""
516+
# remove alarm_control_panel entities
517+
areas = list(self.hass.data[const.DOMAIN]["areas"].keys())
518+
for area in areas:
519+
await self.async_remove_entity(area)
520+
if self.hass.data[const.DOMAIN]["master"]:
521+
await self.async_remove_entity("master")
522+
523+
for key in ["sensor_handler", "automation_handler", "mqtt_handler", "event_handler"]:
524+
handler = self.hass.data[const.DOMAIN].get(key)
525+
if handler and hasattr(handler, "async_unload"):
526+
handler.async_unload()
527+
if key in self.hass.data[const.DOMAIN]:
528+
del self.hass.data[const.DOMAIN][key]
529+
530+
# remove subscriptions for coordinator
531+
while len(self._subscriptions):
532+
self._subscriptions.pop()()
531533

532534
async def async_delete_config(self):
533535
"""Wipe alarmo storage."""

custom_components/alarmo/alarm_control_panel.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
102102
return True
103103

104104

105-
async def async_setup_entry(hass, config_entry, async_add_devices):
106-
"""Set up the Alarmo entities."""
107-
105+
async def async_setup_entry(hass, config_entry, async_add_devices):
106+
"""Set up the Alarmo entities."""
107+
108108
@callback
109109
def async_add_alarm_entity(config: dict):
110110
"""Add each entity as Alarm Control Panel."""
111+
if not hass.config_entries.async_get_entry(config_entry.entry_id):
112+
_LOGGER.debug(
113+
"Skipping area registration for unloaded config entry %s",
114+
config_entry.entry_id,
115+
)
116+
return
111117
unique_id = _build_unique_id(hass, config["area_id"])
112118
entity_registry = er.async_get(hass)
113119
existing_entity_id = entity_registry.async_get_entity_id(
@@ -146,6 +152,12 @@ def async_add_alarm_entity(config: dict):
146152
@callback
147153
def async_add_alarm_master(config: dict):
148154
"""Add each entity as Alarm Control Panel."""
155+
if not hass.config_entries.async_get_entry(config_entry.entry_id):
156+
_LOGGER.debug(
157+
"Skipping master registration for unloaded config entry %s",
158+
config_entry.entry_id,
159+
)
160+
return
149161
unique_id = _build_unique_id(hass)
150162
entity_registry = er.async_get(hass)
151163
existing_entity_id = entity_registry.async_get_entity_id(
@@ -1310,9 +1322,11 @@ def async_alarm_state_changed(area_id: str, old_state: str, new_state: str):
13101322
return
13111323
self.async_update_state()
13121324

1313-
async_dispatcher_connect(
1314-
self.hass, "alarmo_state_updated", async_alarm_state_changed
1315-
)
1325+
self.async_on_remove(
1326+
async_dispatcher_connect(
1327+
self.hass, "alarmo_state_updated", async_alarm_state_changed
1328+
)
1329+
)
13161330

13171331
@callback
13181332
def async_handle_event(event: str, area_id: str, args: dict = {}):
@@ -1352,7 +1366,9 @@ def async_handle_event(event: str, area_id: str, args: dict = {}):
13521366
if event == const.EVENT_READY_TO_ARM_MODES_CHANGED:
13531367
self.update_ready_to_arm_modes()
13541368

1355-
async_dispatcher_connect(self.hass, "alarmo_event", async_handle_event)
1369+
self.async_on_remove(
1370+
async_dispatcher_connect(self.hass, "alarmo_event", async_handle_event)
1371+
)
13561372

13571373
state = await self.async_get_last_state()
13581374
if state and state.state:

custom_components/alarmo/automations.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,14 @@ async def async_handle_event(event: str, area_id: str, args: dict = {}):
172172
async_dispatcher_connect(self.hass, "alarmo_event", async_handle_event)
173173
)
174174

175-
def __del__(self):
176-
"""Prepare for removal."""
177-
while len(self._subscriptions):
178-
self._subscriptions.pop()()
175+
def __del__(self):
176+
"""Prepare for removal."""
177+
self.async_unload()
178+
179+
def async_unload(self):
180+
"""Unload dispatcher subscriptions."""
181+
while len(self._subscriptions):
182+
self._subscriptions.pop()()
179183

180184
async def async_execute_automation(
181185
self, automation_id: str, alarm_entity: AlarmoBaseEntity

custom_components/alarmo/event.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ def __init__(self, hass):
1818

1919
def __del__(self):
2020
"""Class destructor."""
21+
self.async_unload()
22+
23+
def async_unload(self):
24+
"""Unload dispatcher subscription."""
2125
self._subscription()
2226

2327
@callback

custom_components/alarmo/mqtt.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ def async_handle_event(event: str, area_id: str, args: dict = {}):
173173

174174
def __del__(self):
175175
"""Prepare for removal."""
176+
self.async_unload()
177+
178+
def async_unload(self):
179+
"""Unload subscriptions and subscribed topics."""
176180
while len(self._subscribed_topics):
177181
self._subscribed_topics.pop()()
178182
while len(self._subscriptions):

custom_components/alarmo/sensors.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,25 @@ def handle_startup(_event):
193193
else:
194194
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, handle_startup)
195195

196-
def __del__(self):
197-
"""Prepare for removal."""
198-
if self._state_listener:
199-
self._state_listener()
200-
self._state_listener = None
201-
while len(self._subscriptions):
202-
self._subscriptions.pop()()
196+
def __del__(self):
197+
"""Prepare for removal."""
198+
self.async_unload()
199+
200+
def async_unload(self):
201+
"""Unload listeners and timers for the sensor handler."""
202+
if self._state_listener:
203+
self._state_listener()
204+
self._state_listener = None
205+
while len(self._subscriptions):
206+
self._subscriptions.pop()()
207+
while len(self._arm_timers):
208+
_key, unsub = self._arm_timers.popitem()
209+
if unsub:
210+
unsub()
211+
while len(self._delay_on_timers):
212+
_key, unsub = self._delay_on_timers.popitem()
213+
if unsub:
214+
unsub()
203215

204216
def async_watch_sensor_states(
205217
self,

0 commit comments

Comments
 (0)