Homekit Bugfixes (#14689)

* Fix async bug
* Fix debounce bug
This commit is contained in:
cdce8p 2018-05-30 12:39:27 +02:00 committed by GitHub
parent e746b92e0e
commit f1f4d80f24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 29 additions and 30 deletions

View file

@ -1,6 +1,6 @@
"""Extend the basic Accessory and Bridge functions."""
from datetime import timedelta
from functools import wraps
from functools import partial, wraps
from inspect import getmodule
import logging
@ -27,35 +27,25 @@ _LOGGER = logging.getLogger(__name__)
def debounce(func):
"""Decorator function. Debounce callbacks form HomeKit."""
@ha_callback
def call_later_listener(*args):
def call_later_listener(self, *args):
"""Callback listener called from call_later."""
# pylint: disable=unsubscriptable-object
nonlocal lastargs, remove_listener
hass = lastargs['hass']
hass.async_add_job(func, *lastargs['args'])
lastargs = remove_listener = None
debounce_params = self.debounce.pop(func.__name__, None)
if debounce_params:
self.hass.async_add_job(func, self, *debounce_params[1:])
@wraps(func)
def wrapper(*args):
"""Wrapper starts async timer.
The accessory must have 'self.hass' and 'self.entity_id' as attributes.
"""
# pylint: disable=not-callable
hass = args[0].hass
nonlocal lastargs, remove_listener
if remove_listener:
remove_listener()
lastargs = remove_listener = None
lastargs = {'hass': hass, 'args': [*args]}
def wrapper(self, *args):
"""Wrapper starts async timer."""
debounce_params = self.debounce.pop(func.__name__, None)
if debounce_params:
debounce_params[0]() # remove listener
remove_listener = track_point_in_utc_time(
hass, call_later_listener,
self.hass, partial(call_later_listener, self),
dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT))
logger.debug('%s: Start %s timeout', args[0].entity_id,
self.debounce[func.__name__] = (remove_listener, *args)
logger.debug('%s: Start %s timeout', self.entity_id,
func.__name__.replace('set_', ''))
remove_listener = None
lastargs = None
name = getmodule(func).__name__
logger = logging.getLogger(name)
return wrapper
@ -76,11 +66,15 @@ class HomeAccessory(Accessory):
self.config = config
self.entity_id = entity_id
self.hass = hass
self.debounce = {}
def run(self):
"""Method called by accessory after driver is started."""
async def run(self):
"""Method called by accessory after driver is started.
Run inside the HAP-python event loop.
"""
state = self.hass.states.get(self.entity_id)
self.update_state_callback(new_state=state)
self.hass.add_job(self.update_state_callback, None, None, state)
async_track_state_change(
self.hass, self.entity_id, self.update_state_callback)
@ -127,10 +121,10 @@ class HomeDriver(AccessoryDriver):
def pair(self, client_uuid, client_public):
"""Override super function to dismiss setup message if paired."""
value = super().pair(client_uuid, client_public)
if value:
success = super().pair(client_uuid, client_public)
if success:
dismiss_setup_message(self.hass)
return value
return success
def unpair(self, client_uuid):
"""Override super function to show setup message if unpaired."""

View file

@ -26,7 +26,7 @@ async def test_debounce(hass):
arguments = None
counter = 0
mock = Mock(hass=hass)
mock = Mock(hass=hass, debounce={})
debounce_demo = debounce(demo_func)
assert debounce_demo.__name__ == 'demo_func'
@ -76,6 +76,7 @@ async def test_home_accessory(hass, hk_driver):
with patch('homeassistant.components.homekit.accessories.'
'HomeAccessory.update_state') as mock_update_state:
await hass.async_add_job(acc.run)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
mock_update_state.assert_called_with(state)

View file

@ -35,6 +35,7 @@ async def test_default_thermostat(hass, hk_driver, cls):
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, 'Climate', entity_id, 2, None)
await hass.async_add_job(acc.run)
await hass.async_block_till_done()
assert acc.aid == 2
assert acc.category == 9 # Thermostat
@ -175,6 +176,7 @@ async def test_auto_thermostat(hass, hk_driver, cls):
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, 'Climate', entity_id, 2, None)
await hass.async_add_job(acc.run)
await hass.async_block_till_done()
assert acc.char_cooling_thresh_temp.value == 23.0
assert acc.char_heating_thresh_temp.value == 19.0
@ -254,6 +256,7 @@ async def test_power_state(hass, hk_driver, cls):
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, 'Climate', entity_id, 2, None)
await hass.async_add_job(acc.run)
await hass.async_block_till_done()
assert acc.support_power_state is True
assert acc.char_current_heat_cool.value == 1
@ -306,6 +309,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls):
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, 'Climate', entity_id, 2, None)
await hass.async_add_job(acc.run)
await hass.async_block_till_done()
hass.states.async_set(entity_id, STATE_AUTO,
{ATTR_OPERATION_MODE: STATE_AUTO,