Skip to content

Commit

Permalink
fix(multiple-click): create own run_in function
Browse files Browse the repository at this point in the history
The AppDaemon run_in function does not accept microseconds, just integer seconds. So a new run_in function was created with asyncio functionality.
  • Loading branch information
xaviml committed Oct 5, 2020
1 parent 5158154 commit f1f7195
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 11 deletions.
33 changes: 25 additions & 8 deletions apps/controllerx/cx_core/controller.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import abc
import asyncio
import time
from asyncio.futures import Future
from collections import defaultdict
from functools import wraps
from typing import (
Expand Down Expand Up @@ -31,7 +33,7 @@

DEFAULT_DELAY = 350 # In milliseconds
DEFAULT_ACTION_DELTA = 300 # In milliseconds
DEFAULT_MULTIPLE_CLICK_DELAY = 1000 # In milliseconds
DEFAULT_MULTIPLE_CLICK_DELAY = 500 # In milliseconds
MULTIPLE_CLICK_TOKEN = "$"

T = TypeVar("T")
Expand All @@ -49,6 +51,21 @@ async def _action_impl(
return _action_impl


def run_in(fn: Callable, delay: float, **kwargs) -> Future:
"""
It runs the function (fn) to running event loop in `delay` seconds.
This function has been created because the default run_in function
from AppDaemon does not accept microseconds.
"""

async def inner() -> None:
await asyncio.sleep(delay)
await fn(kwargs)

task = asyncio.ensure_future(inner())
return task


class Controller(Hass, Mqtt, abc.ABC):
"""
This is the parent Controller, all controllers must extend from this class.
Expand Down Expand Up @@ -111,8 +128,8 @@ async def initialize(self) -> None:
self.action_delay_handles: DefaultDict[
ActionEvent, Optional[float]
] = defaultdict(lambda: None)
self.multiple_click_action_delay_handles: DefaultDict[
ActionEvent, Optional[float]
self.multiple_click_action_delay_tasks: DefaultDict[
ActionEvent, Optional[Future]
] = defaultdict(lambda: None)

# Filter the actions
Expand Down Expand Up @@ -237,20 +254,20 @@ async def handle_action(self, action_key: str) -> None:
if now - previous_call_time > self.multiple_click_delay:
pass

previous_handle = self.multiple_click_action_delay_handles[action_key]
if previous_handle is not None:
await self.cancel_timer(previous_handle)
previous_task = self.multiple_click_action_delay_tasks[action_key]
if previous_task is not None:
previous_task.cancel()

self.click_counter[action_key] += 1
click_count = self.click_counter[action_key]

new_handle = await self.run_in(
new_task = run_in(
self.multiple_click_call_action,
self.multiple_click_delay / 1000,
action_key=action_key,
click_count=click_count,
)
self.multiple_click_action_delay_handles[action_key] = new_handle
self.multiple_click_action_delay_tasks[action_key] = new_task
else:
self.log(
f"🎮 Button event triggered, but not registered: `{action_key}`",
Expand Down
2 changes: 1 addition & 1 deletion docs/others/multiple-clicks.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ example_app:
name: z2m
listen_to: mqtt
light: light.my_light
multiple_click_delay: 1000 # default
multiple_click_delay: 500 # default
mapping:
brightness_up_click: "on"
toggle: click_color_down
Expand Down
2 changes: 1 addition & 1 deletion docs/start/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ These are the generic app parameters for all type of controllers. You can see th
| `actions` | list | All actions | This is a list of actions to be included and controlled by the app. To see which actions has each controller check the individual controller pages in [here](/controllerx/controllers). This attribute cannot be used together with `excluded_actions`. |
| `excluded_actions` | list | Empty list | This is a list of actions to be excluded. To see which actions has each controller check the individual controller pages in [here](/controllerx/controllers). This attribute cannot be used together with `actions`. |
| `action_delta` | int | 300 | This is the threshold time between the previous action and the next one (being the same action). If the time difference between the two actions is less than this attribute, then the action won't be called. I recommend changing this if you see the same action being called twice. |
| `multiple_click_delay` | int | 1000 | Indicates the delay (in milliseconds) when a multiple click action should be trigger. The higher the number, the more time there can be between clicks, but there will be more delay for the action to be triggered. |
| `multiple_click_delay` | int | 500 | Indicates the delay (in milliseconds) when a multiple click action should be trigger. The higher the number, the more time there can be between clicks, but there will be more delay for the action to be triggered. |
| `action_delay` | dict | - | This can be used to set a delay to each action. By default, the delay for all actions is 0. The key for the map is the action and the value is the delay in seconds. |
| `mapping` | dict | - | This can be used to replace the behaviour of the controller and manually select what each button should be doing. By default it will ignore this parameter. Read more about it in [here](/controllerx/others/custom-controllers). The functionality included in this attribute will remove the default mapping. |
| `merge_mapping` | dict | - | This can be used to merge the default mapping from the controller and manually select what each button should be doing. By default it will ignore this parameter. Read more about it in [here](/controllerx/others/custom-controllers). The functionality included in this attribute is added on top of the default mapping. |
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ def hass_mock(monkeypatch, mocker):

@pytest.fixture(autouse=True)
def fake_controller(hass_mock):
c = Controller()
c = Controller() # type: ignore
c.args = {}
return c

0 comments on commit f1f7195

Please sign in to comment.