Ensure homekit state changed listeners are unsubscribed on reload (#37200)

* Ensure homekit state changed listeners are unsubscribed on reload

* fix mocking
This commit is contained in:
J. Nick Koston 2020-06-29 11:25:26 -05:00 committed by GitHub
parent 7ef33a7219
commit 0f72008090
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 10 deletions

View file

@ -576,6 +576,8 @@ class HomeKit:
self.status = STATUS_STOPPED self.status = STATUS_STOPPED
_LOGGER.debug("Driver stop for %s", self._name) _LOGGER.debug("Driver stop for %s", self._name)
self.hass.add_job(self.driver.stop) self.hass.add_job(self.driver.stop)
for acc in self.bridge.accessories.values():
acc.async_stop()
@callback @callback
def _async_configure_linked_sensors(self, ent_reg_ent, device_lookup, state): def _async_configure_linked_sensors(self, ent_reg_ent, device_lookup, state):

View file

@ -270,6 +270,7 @@ class HomeAccessory(Accessory):
self.entity_id = entity_id self.entity_id = entity_id
self.hass = hass self.hass = hass
self.debounce = {} self.debounce = {}
self._subscriptions = []
self._char_battery = None self._char_battery = None
self._char_charging = None self._char_charging = None
self._char_low_battery = None self._char_low_battery = None
@ -343,8 +344,10 @@ class HomeAccessory(Accessory):
""" """
state = self.hass.states.get(self.entity_id) state = self.hass.states.get(self.entity_id)
self.async_update_state_callback(None, None, state) self.async_update_state_callback(None, None, state)
async_track_state_change( self._subscriptions.append(
self.hass, self.entity_id, self.async_update_state_callback async_track_state_change(
self.hass, self.entity_id, self.async_update_state_callback
)
) )
battery_charging_state = None battery_charging_state = None
@ -357,10 +360,12 @@ class HomeAccessory(Accessory):
battery_charging_state = linked_battery_sensor_state.attributes.get( battery_charging_state = linked_battery_sensor_state.attributes.get(
ATTR_BATTERY_CHARGING ATTR_BATTERY_CHARGING
) )
async_track_state_change( self._subscriptions.append(
self.hass, async_track_state_change(
self.linked_battery_sensor, self.hass,
self.async_update_linked_battery_callback, self.linked_battery_sensor,
self.async_update_linked_battery_callback,
)
) )
else: else:
battery_state = state.attributes.get(ATTR_BATTERY_LEVEL) battery_state = state.attributes.get(ATTR_BATTERY_LEVEL)
@ -369,10 +374,12 @@ class HomeAccessory(Accessory):
self.hass.states.get(self.linked_battery_charging_sensor).state self.hass.states.get(self.linked_battery_charging_sensor).state
== STATE_ON == STATE_ON
) )
async_track_state_change( self._subscriptions.append(
self.hass, async_track_state_change(
self.linked_battery_charging_sensor, self.hass,
self.async_update_linked_battery_charging_callback, self.linked_battery_charging_sensor,
self.async_update_linked_battery_charging_callback,
)
) )
elif battery_charging_state is None: elif battery_charging_state is None:
battery_charging_state = state.attributes.get(ATTR_BATTERY_CHARGING) battery_charging_state = state.attributes.get(ATTR_BATTERY_CHARGING)
@ -481,6 +488,12 @@ class HomeAccessory(Accessory):
self.hass.bus.async_fire(EVENT_HOMEKIT_CHANGED, event_data) self.hass.bus.async_fire(EVENT_HOMEKIT_CHANGED, event_data)
await self.hass.services.async_call(domain, service, service_data) await self.hass.services.async_call(domain, service, service_data)
@ha_callback
def async_stop(self):
"""Cancel any subscriptions when the bridge is stopped."""
while self._subscriptions:
self._subscriptions.pop(0)()
class HomeBridge(Bridge): class HomeBridge(Bridge):
"""Adapter class for Bridge.""" """Adapter class for Bridge."""

View file

@ -45,6 +45,7 @@ from homeassistant.const import (
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
__version__, __version__,
) )
from homeassistant.helpers.event import TRACK_STATE_CHANGE_CALLBACKS
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from tests.async_mock import Mock, patch from tests.async_mock import Mock, patch
@ -83,6 +84,22 @@ async def test_debounce(hass):
assert counter == 2 assert counter == 2
async def test_accessory_cancels_track_state_change_on_stop(hass, hk_driver):
"""Ensure homekit state changed listeners are unsubscribed on reload."""
entity_id = "sensor.accessory"
hass.states.async_set(entity_id, None)
acc = HomeAccessory(
hass, hk_driver, "Home Accessory", entity_id, 2, {"platform": "isy994"}
)
with patch(
"homeassistant.components.homekit.accessories.HomeAccessory.async_update_state"
):
await acc.run_handler()
assert len(hass.data[TRACK_STATE_CHANGE_CALLBACKS][entity_id]) == 1
acc.async_stop()
assert entity_id not in hass.data[TRACK_STATE_CHANGE_CALLBACKS]
async def test_home_accessory(hass, hk_driver): async def test_home_accessory(hass, hk_driver):
"""Test HomeAccessory class.""" """Test HomeAccessory class."""
entity_id = "sensor.accessory" entity_id = "sensor.accessory"

View file

@ -605,6 +605,8 @@ async def test_homekit_stop(hass):
entry_id=entry.entry_id, entry_id=entry.entry_id,
) )
homekit.driver = Mock() homekit.driver = Mock()
homekit.bridge = Mock()
homekit.bridge.accessories = {}
await async_init_integration(hass) await async_init_integration(hass)