Switch homekit to use async_track_state_change_event (#37253)

* Switch homekit to use async_track_state_change_event

Calling async_track_state_change_event directly
is faster than async_track_state_change and has
slightly lower latency triggering state updates
in homekit

* check for deleted entities

* Update additional tests for linked sensor removals

* Ensure removing entities does not result in an exception
This commit is contained in:
J. Nick Koston 2020-07-05 21:05:13 -05:00 committed by GitHub
parent 9b77e16ffc
commit 711b8e10a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 24 deletions

View File

@ -33,7 +33,7 @@ from homeassistant.const import (
) )
from homeassistant.core import callback as ha_callback, split_entity_id from homeassistant.core import callback as ha_callback, split_entity_id
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change, async_track_state_change_event,
track_point_in_utc_time, track_point_in_utc_time,
) )
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
@ -346,10 +346,10 @@ class HomeAccessory(Accessory):
Run inside the Home Assistant event loop. Run inside the Home Assistant event loop.
""" """
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(state)
self._subscriptions.append( self._subscriptions.append(
async_track_state_change( async_track_state_change_event(
self.hass, self.entity_id, self.async_update_state_callback self.hass, [self.entity_id], self.async_update_event_state_callback
) )
) )
@ -364,36 +364,37 @@ class HomeAccessory(Accessory):
ATTR_BATTERY_CHARGING ATTR_BATTERY_CHARGING
) )
self._subscriptions.append( self._subscriptions.append(
async_track_state_change( async_track_state_change_event(
self.hass, self.hass,
self.linked_battery_sensor, [self.linked_battery_sensor],
self.async_update_linked_battery_callback, self.async_update_linked_battery_callback,
) )
) )
else: elif state is not None:
battery_state = state.attributes.get(ATTR_BATTERY_LEVEL) battery_state = state.attributes.get(ATTR_BATTERY_LEVEL)
if self.linked_battery_charging_sensor: if self.linked_battery_charging_sensor:
battery_charging_state = ( state = self.hass.states.get(self.linked_battery_charging_sensor)
self.hass.states.get(self.linked_battery_charging_sensor).state battery_charging_state = state and state.state == STATE_ON
== STATE_ON
)
self._subscriptions.append( self._subscriptions.append(
async_track_state_change( async_track_state_change_event(
self.hass, self.hass,
self.linked_battery_charging_sensor, [self.linked_battery_charging_sensor],
self.async_update_linked_battery_charging_callback, self.async_update_linked_battery_charging_callback,
) )
) )
elif battery_charging_state is None: elif battery_charging_state is None and state is not None:
battery_charging_state = state.attributes.get(ATTR_BATTERY_CHARGING) battery_charging_state = state.attributes.get(ATTR_BATTERY_CHARGING)
if battery_state is not None or battery_charging_state is not None: if battery_state is not None or battery_charging_state is not None:
self.async_update_battery(battery_state, battery_charging_state) self.async_update_battery(battery_state, battery_charging_state)
@ha_callback @ha_callback
def async_update_state_callback( def async_update_event_state_callback(self, event):
self, entity_id=None, old_state=None, new_state=None """Handle state change event listener callback."""
): self.async_update_state_callback(event.data.get("new_state"))
@ha_callback
def async_update_state_callback(self, new_state):
"""Handle state change listener callback.""" """Handle state change listener callback."""
_LOGGER.debug("New_state: %s", new_state) _LOGGER.debug("New_state: %s", new_state)
if new_state is None: if new_state is None:
@ -415,10 +416,11 @@ class HomeAccessory(Accessory):
self.async_update_state(new_state) self.async_update_state(new_state)
@ha_callback @ha_callback
def async_update_linked_battery_callback( def async_update_linked_battery_callback(self, event):
self, entity_id=None, old_state=None, new_state=None
):
"""Handle linked battery sensor state change listener callback.""" """Handle linked battery sensor state change listener callback."""
new_state = event.data.get("new_state")
if new_state is None:
return
if self.linked_battery_charging_sensor: if self.linked_battery_charging_sensor:
battery_charging_state = None battery_charging_state = None
else: else:
@ -426,10 +428,11 @@ class HomeAccessory(Accessory):
self.async_update_battery(new_state.state, battery_charging_state) self.async_update_battery(new_state.state, battery_charging_state)
@ha_callback @ha_callback
def async_update_linked_battery_charging_callback( def async_update_linked_battery_charging_callback(self, event):
self, entity_id=None, old_state=None, new_state=None
):
"""Handle linked battery charging sensor state change listener callback.""" """Handle linked battery charging sensor state change listener callback."""
new_state = event.data.get("new_state")
if new_state is None:
return
self.async_update_battery(None, new_state.state == STATE_ON) self.async_update_battery(None, new_state.state == STATE_ON)
@ha_callback @ha_callback

View File

@ -207,6 +207,7 @@ async def test_battery_service(hass, hk_driver, caplog):
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
mock_async_update_state.assert_called_with(state) mock_async_update_state.assert_called_with(state)
assert acc._char_battery.value == 15 assert acc._char_battery.value == 15
assert acc._char_low_battery.value == 1 assert acc._char_low_battery.value == 1
assert acc._char_charging.value == 2 assert acc._char_charging.value == 2
@ -218,6 +219,7 @@ async def test_battery_service(hass, hk_driver, caplog):
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
mock_async_update_state.assert_called_with(state) mock_async_update_state.assert_called_with(state)
assert acc._char_battery.value == 15 assert acc._char_battery.value == 15
assert acc._char_low_battery.value == 1 assert acc._char_low_battery.value == 1
assert acc._char_charging.value == 2 assert acc._char_charging.value == 2
@ -341,7 +343,12 @@ async def test_linked_battery_sensor(hass, hk_driver, caplog):
hass.states.async_set(linked_battery, 100, {ATTR_BATTERY_CHARGING: False}) hass.states.async_set(linked_battery, 100, {ATTR_BATTERY_CHARGING: False})
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) assert acc._char_battery.value == 100
assert acc._char_low_battery.value == 0
assert acc._char_charging.value == 0
hass.states.async_remove(linked_battery)
await hass.async_block_till_done()
assert acc._char_battery.value == 100 assert acc._char_battery.value == 100
assert acc._char_low_battery.value == 0 assert acc._char_low_battery.value == 0
assert acc._char_charging.value == 0 assert acc._char_charging.value == 0
@ -396,6 +403,14 @@ async def test_linked_battery_charging_sensor(hass, hk_driver, caplog):
mock_async_update_state.assert_called_with(state) mock_async_update_state.assert_called_with(state)
assert acc._char_charging.value == 1 assert acc._char_charging.value == 1
with patch(
"homeassistant.components.homekit.accessories.HomeAccessory.async_update_state"
) as mock_async_update_state:
hass.states.async_remove(linked_battery_charging_sensor)
await acc.run_handler()
await hass.async_block_till_done()
assert acc._char_charging.value == 1
async def test_linked_battery_sensor_and_linked_battery_charging_sensor( async def test_linked_battery_sensor_and_linked_battery_charging_sensor(
hass, hk_driver, caplog hass, hk_driver, caplog
@ -439,6 +454,12 @@ async def test_linked_battery_sensor_and_linked_battery_charging_sensor(
assert acc._char_low_battery.value == 0 assert acc._char_low_battery.value == 0
assert acc._char_charging.value == 0 assert acc._char_charging.value == 0
hass.states.async_remove(linked_battery_charging_sensor)
await hass.async_block_till_done()
assert acc._char_battery.value == 50
assert acc._char_low_battery.value == 0
assert acc._char_charging.value == 0
async def test_missing_linked_battery_charging_sensor(hass, hk_driver, caplog): async def test_missing_linked_battery_charging_sensor(hass, hk_driver, caplog):
"""Test battery service with linked_battery_charging_sensor that is mapping to a missing entity.""" """Test battery service with linked_battery_charging_sensor that is mapping to a missing entity."""
@ -457,6 +478,24 @@ async def test_missing_linked_battery_charging_sensor(hass, hk_driver, caplog):
) )
assert acc.linked_battery_charging_sensor is None assert acc.linked_battery_charging_sensor is None
# Make sure we don't throw if the linked_battery_charging_sensor
# is removed
hass.states.async_remove(linked_battery_charging_sensor)
with patch(
"homeassistant.components.homekit.accessories.HomeAccessory.async_update_state"
):
await acc.run_handler()
await hass.async_block_till_done()
# Make sure we don't throw if the entity_id
# is removed
hass.states.async_remove(entity_id)
with patch(
"homeassistant.components.homekit.accessories.HomeAccessory.async_update_state"
):
await acc.run_handler()
await hass.async_block_till_done()
async def test_missing_linked_battery_sensor(hass, hk_driver, caplog): async def test_missing_linked_battery_sensor(hass, hk_driver, caplog):
"""Test battery service with missing linked_battery_sensor.""" """Test battery service with missing linked_battery_sensor."""
@ -488,6 +527,18 @@ async def test_missing_linked_battery_sensor(hass, hk_driver, caplog):
assert acc._char_low_battery is None assert acc._char_low_battery is None
assert acc._char_charging is None assert acc._char_charging is None
with patch(
"homeassistant.components.homekit.accessories.HomeAccessory.async_update_state"
) as mock_async_update_state:
hass.states.async_remove(entity_id)
await acc.run_handler()
await hass.async_block_till_done()
assert not acc.linked_battery_sensor
assert acc._char_battery is None
assert acc._char_low_battery is None
assert acc._char_charging is None
async def test_battery_appears_after_startup(hass, hk_driver, caplog): async def test_battery_appears_after_startup(hass, hk_driver, caplog):
"""Test battery level appears after homekit is started.""" """Test battery level appears after homekit is started."""
@ -514,6 +565,13 @@ async def test_battery_appears_after_startup(hass, hk_driver, caplog):
await hass.async_block_till_done() await hass.async_block_till_done()
assert acc._char_battery is None assert acc._char_battery is None
with patch(
"homeassistant.components.homekit.accessories.HomeAccessory.async_update_state"
):
hass.states.async_remove(entity_id)
await hass.async_block_till_done()
assert acc._char_battery is None
async def test_call_service(hass, hk_driver, events): async def test_call_service(hass, hk_driver, events):
"""Test call_service method.""" """Test call_service method."""