Add timeout / debounce (for brightness and others)#13534
Add timeout / debounce (for brightness and others)#13534pvizeli merged 3 commits intohome-assistant:devfrom cdce8p:homekit-add_timeout
Conversation
There was a problem hiding this comment.
global is not allow. use hass.data
|
@pvizeli You are right in it being an API throttle, since the In general I'm not that big of a fan adding this either, but I acknowledge that without it, there might be issue (like #13493). Under the current circumstances I think that is the way to move foreword. |
|
The algorithm you're looking for is called debounce: call a function X time after the last call has been called. Calls within the X time will reset the timer. You should not use Timer, as that uses threads. Instead, use our Let's not make it user configurable, just set a reasonable timeout like 0.5s? |
|
The timeout function is now async and |
|
use our helper.events.call_later they return a cancelable object. |
|
@pvizeli Would this be the way to go? def add_timeout(func):
@callback
def call_later_listener(hass, acc, value, *args):
nonlocal remove_listener
remove_listener = None
hass.async_run_job(func, acc, value)
@wraps(func)
def wrapper(*args):
hass = args[0].hass
nonlocal remove_listener
if remove_listener:
remove_listener()
remove_listener = async_call_later(
hass, TIMEOUT, partial(call_later_listener, hass, *args))
logger.debug('%s: Start %s timeout', args[0].entity_id,
func.__name__.replace('set_', ''))
remove_listener = None
name = getmodule(func).__name__
logger = logging.getLogger(name)
return wrapperI added the function |
|
Would recommend adding an optional adjustable timeout between 0 and 1 so users can tweak latency given their system/configuration. |
There was a problem hiding this comment.
You are not allowed to call async methods from a sync context.
There was a problem hiding this comment.
Instead of using partial, why not store the arguments like you store the unsubscribe listener function
nonlocal lastargs
lastargs = args
There was a problem hiding this comment.
You've annotated this method with @callback, which means that it will be run inside the event loop, so this should be hass.async_add_job(func, *lastargs)
|
I extracted the style changes into #13654 and will rebase this PR once that one is merged. |
* Decorator for setter methods to limit service calls to HA * Changed to async * Use async_call_later * Use lastargs, async_add_job
| # pylint: disable=unsubscriptable-object | ||
| nonlocal lastargs, remove_listener | ||
| hass = lastargs[0] | ||
| hass.async_add_job(func, *lastargs[1:]) |
There was a problem hiding this comment.
Isn't this weird. it used to take hass but now we don't pass that on?
There was a problem hiding this comment.
Oh I see now how you're storing this. Can we store it in a dictionary? That makes things a lot more readable.
|
|
||
| from tests.common import get_test_home_assistant | ||
|
|
||
| patch('homeassistant.components.homekit.accessories.debounce', |
There was a problem hiding this comment.
This is bad as now loading this file impacts all other tests that use homekit.
Instead, you should use a pytest fixture. But to be able to leverage pytest fixtures, you will need to rewrite your tests to be just methods. Something like this: (written from top of my head, didn't test)
import pytest
@pytest.fixture(autouse=True)
def mock_debounce():
with patch('…'):
yield
@pytest.fixture
def mock_call_service(hass):
events = []
@callback
def record_event(event):
…
hass.bus.listen(…)
return events
def test_light_basic(hass, mock_call_service):
# hass is a new test instance, will be automatically stopped for you
# mock_call_service is equivalent to self.events
# Instead of self.assertEqual, just use assert
assert acc.aid == 2|
|
||
| from tests.common import get_test_home_assistant | ||
|
|
||
| patch('homeassistant.components.homekit.accessories.debounce', |
|
@balloob Instead of using |
|
The problem with class based tests is that we end up mocking more on each test than we need. Then tests will start failing randomly and we had more hacks on hacks. The function based one is what all new tests for Home Assistant are based on. They also allow async tests. I am not against using unittest.TestCase, but it will just become harder and harder in the future to write tests, especially performant ones. |
|
I think you can change the test later in a PR. But with pytest, you can write faster, shorter and more readable tests very easy :) |
|
I will lock into it :) |
Description:
This PR fixes an issue, where for continuous characteristics slowly changing the value in the
Homeapp caused multiple service calls, instead of just one.Since this timeout feature introduces latency, I decided to add a configuration parameter for it, so users can decide how long the timeout should be (float, between 0 and 5s).The Timeout is set to 0.5s.In addition I did some style cleanup.
Related issue: fixes #13493
Discussion belove: #12819
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5050Example entry for
configuration.yaml(if applicable):Checklist:
tox. Your PR cannot be merged unless tests passDocumentation added/updated in home-assistant.github.iocc: @maxclaey @DaveOke