Skip to content

Commit

Permalink
Merge branch 'feat/allow-unique-id-deconz' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
xaviml committed Jul 5, 2021
2 parents 791dadb + 40f39a5 commit aeb8aab
Show file tree
Hide file tree
Showing 18 changed files with 244 additions and 31 deletions.
3 changes: 2 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ PRERELEASE_NOTE
## :pencil2: Features

- Allow to pass parameters to predefined actions. You can check the parameters for each predefined action, and how to pass parameters in [here](https://xaviml.github.io/controllerx/advanced/predefined-actions). [ #78 ]
- Support for Light Group integration. To know how entity groups work, read [here](https://xaviml.github.io/controllerx/advanced/entity-groups)
- Support for Light Group integration. To know how entity groups work, read [here](https://xaviml.github.io/controllerx/advanced/entity-groups) [ #330 ]
- Add option to read `unique_id` attribute for deCONZ integration. Read more about it [here](https://xaviml.github.io/controllerx/others/integrations#deconz) [ #333 ]

<!--
## :hammer: Fixes
Expand Down
13 changes: 12 additions & 1 deletion apps/controllerx/cx_core/integration/deconz.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from cx_const import DefaultActionsMapping # type:ignore
from cx_core.integration import EventData, Integration

LISTENS_TO_ID = "id"
LISTENS_TO_UNIQUE_ID = "unique_id"


class DeCONZIntegration(Integration):
name = "deconz"
Expand All @@ -12,8 +15,16 @@ def get_default_actions_mapping(self) -> Optional[DefaultActionsMapping]:
return self.controller.get_deconz_actions_mapping()

async def listen_changes(self, controller_id: str) -> None:
listens_to = self.kwargs.get("listen_to", LISTENS_TO_ID)
if listens_to not in (LISTENS_TO_ID, LISTENS_TO_UNIQUE_ID):
raise ValueError(
"`listens_to` for deCONZ integration should either be `id` or `unique_id`"
)
await Hass.listen_event(
self.controller, self.event_callback, "deconz_event", id=controller_id
self.controller,
self.event_callback,
"deconz_event",
**{listens_to: controller_id}
)

async def event_callback(
Expand Down
6 changes: 4 additions & 2 deletions apps/controllerx/cx_core/integration/lutron_caseta.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ def get_default_actions_mapping(self) -> Optional[DefaultActionsMapping]:
async def listen_changes(self, controller_id: str) -> None:
await Hass.listen_event(
self.controller,
self.callback,
self.event_callback,
"lutron_caseta_button_event",
serial=controller_id,
)

async def callback(self, event_name: str, data: EventData, kwargs: dict) -> None:
async def event_callback(
self, event_name: str, data: EventData, kwargs: dict
) -> None:
button = data["button_number"]
action_type = data["action"]
action = f"button_{button}_{action_type}"
Expand Down
6 changes: 4 additions & 2 deletions apps/controllerx/cx_core/integration/zha.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def get_default_actions_mapping(self) -> Optional[DefaultActionsMapping]:

async def listen_changes(self, controller_id: str) -> None:
await Hass.listen_event(
self.controller, self.callback, "zha_event", device_ieee=controller_id
self.controller, self.event_callback, "zha_event", device_ieee=controller_id
)

def get_action(self, data: EventData) -> str:
Expand All @@ -28,7 +28,9 @@ def get_action(self, data: EventData) -> str:
action += "_" + "_".join(args)
return action

async def callback(self, event_name: str, data: EventData, kwargs: dict) -> None:
async def event_callback(
self, event_name: str, data: EventData, kwargs: dict
) -> None:
action = self.controller.get_zha_action(data)
if action is None:
# If there is no action extracted from the controller then
Expand Down
5 changes: 3 additions & 2 deletions docs/others/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Three things to clarify when using the `z2m` integration listening to MQTT:

#### deCONZ

This integration(**`deconz`**) listens to `deconz_event` events and actions gets fired by default with the `event` attribute from the `data` object. However, you can change the attribute to listen to by adding a `type` attribute. This is an example
This integration(**`deconz`**) listens to `deconz_event` events and actions gets fired by default with the `event` attribute from the `data` object. However, you can change the attribute to listen to by adding a `type` attribute. In addition, you can select which attribute to listen to (`id` or `unique_id`) with `listen_to`. This is an example:

```yaml
example_app:
Expand All @@ -50,7 +50,8 @@ example_app:
controller: magic_cube
integration:
name: deconz
type: gesture
listen_to: unique_id # defaults to `id`
type: gesture # defaults to `event`
light: light.example_light
```
Expand Down
2 changes: 1 addition & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def get_classes(file_, package_, class_, instantiate=False):


@contextmanager
def wrap_exetuction(
def wrap_execution(
*, error_expected: bool, exception=Exception
) -> Generator[Optional[ExceptionInfo], None, None]:
if error_expected:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from cx_core.action_type.predefined_action_type import _get_action, _get_arguments
from cx_core.integration import EventData

from tests.test_utils import fake_fn, wrap_exetuction
from tests.test_utils import fake_fn, wrap_execution


@pytest.mark.parametrize(
Expand Down Expand Up @@ -92,7 +92,7 @@ def test_get_arguments_general(
async def test_fn(a: str, b: int = 2):
pass

with wrap_exetuction(error_expected=expected is None, exception=ValueError):
with wrap_execution(error_expected=expected is None, exception=ValueError):
output = _get_arguments(test_fn, action_args, user_args, None)

if expected is not None:
Expand Down Expand Up @@ -131,7 +131,7 @@ def test_get_arguments_with_extra(
async def test_fn(a: str, b: int, extra: Optional[EventData] = None):
pass

with wrap_exetuction(error_expected=expected is None, exception=ValueError):
with wrap_execution(error_expected=expected is None, exception=ValueError):
output = _get_arguments(test_fn, action_args, user_args, extra)

if expected is not None:
Expand Down
4 changes: 2 additions & 2 deletions tests/unit_tests/cx_core/color_helper_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
from cx_core.color_helper import Colors, get_color_wheel

from tests.test_utils import wrap_exetuction
from tests.test_utils import wrap_execution


@pytest.mark.parametrize(
Expand All @@ -14,5 +14,5 @@
],
)
def test_get_color_wheel(colors: Colors, error_expected: bool):
with wrap_exetuction(error_expected=error_expected, exception=ValueError):
with wrap_execution(error_expected=error_expected, exception=ValueError):
colors = get_color_wheel(colors)
10 changes: 5 additions & 5 deletions tests/unit_tests/cx_core/controller_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from cx_core.controller import Controller, action
from pytest_mock.plugin import MockerFixture

from tests.test_utils import IntegrationMock, fake_fn, wrap_exetuction
from tests.test_utils import IntegrationMock, fake_fn, wrap_execution

INTEGRATION_TEST_NAME = "test"
CONTROLLER_NAME = "test_controller"
Expand Down Expand Up @@ -164,7 +164,7 @@ async def test_initialize(
)

# SUT
with wrap_exetuction(error_expected=error_expected, exception=ValueError):
with wrap_execution(error_expected=error_expected, exception=ValueError):
await sut_before_init.initialize()

# Checks
Expand Down Expand Up @@ -214,7 +214,7 @@ async def test_merge_mapping(
)

# SUT
with wrap_exetuction(error_expected=error_expected, exception=ValueError):
with wrap_execution(error_expected=error_expected, exception=ValueError):
await sut_before_init.initialize()

# Checks
Expand Down Expand Up @@ -332,7 +332,7 @@ def test_get_multiple_click_actions(
def test_get_option(
sut: Controller, option: str, options: List[str], error_expected: bool
):
with wrap_exetuction(error_expected=error_expected, exception=ValueError):
with wrap_execution(error_expected=error_expected, exception=ValueError):
sut.get_option(option, options)


Expand Down Expand Up @@ -361,7 +361,7 @@ def test_get_integration(
):
get_integrations_spy = mocker.spy(integration_module, "get_integrations")

with wrap_exetuction(error_expected=error_expected, exception=ValueError):
with wrap_execution(error_expected=error_expected, exception=ValueError):
integration = fake_controller.get_integration(integration_input)

if not error_expected:
Expand Down
38 changes: 38 additions & 0 deletions tests/unit_tests/cx_core/integration/deconz_test.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import Dict, Optional

import pytest
from appdaemon.plugins.hass.hassapi import Hass
from cx_core.controller import Controller
from cx_core.integration.deconz import DeCONZIntegration
from pytest_mock.plugin import MockerFixture

from tests.test_utils import wrap_execution


@pytest.mark.parametrize(
"data, type, expected",
Expand Down Expand Up @@ -36,3 +39,38 @@ async def test_callback(
deconz_integration = DeCONZIntegration(fake_controller, kwargs)
await deconz_integration.event_callback("test", data, {})
handle_action_patch.assert_called_once_with(expected, extra=data)


@pytest.mark.parametrize(
"listen_to, expected_id",
[
("id", "id"),
("unique_id", "unique_id"),
(None, "id"),
("fake", None),
],
)
@pytest.mark.asyncio
async def test_listen_changes(
fake_controller: Controller,
mocker: MockerFixture,
listen_to: Optional[str],
expected_id: Optional[str],
):
kwargs = {}
if listen_to is not None:
kwargs["listen_to"] = listen_to

listen_event_mock = mocker.patch.object(Hass, "listen_event")
deconz_integration = DeCONZIntegration(fake_controller, kwargs)

with wrap_execution(error_expected=expected_id is None, exception=ValueError):
await deconz_integration.listen_changes("controller_id")

if expected_id is not None:
listen_event_mock.assert_called_once_with(
fake_controller,
deconz_integration.event_callback,
"deconz_event",
**{expected_id: "controller_id"}
)
22 changes: 21 additions & 1 deletion tests/unit_tests/cx_core/integration/lutron_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Dict

import pytest
from appdaemon.plugins.hass.hassapi import Hass
from cx_core.controller import Controller
from cx_core.integration.lutron_caseta import LutronIntegration
from pytest_mock.plugin import MockerFixture
Expand Down Expand Up @@ -42,5 +43,24 @@ async def test_callback(
):
handle_action_patch = mocker.patch.object(fake_controller, "handle_action")
lutron_integration = LutronIntegration(fake_controller, {})
await lutron_integration.callback("test", data, {})
await lutron_integration.event_callback("test", data, {})
handle_action_patch.assert_called_once_with(expected, extra=data)


@pytest.mark.asyncio
async def test_listen_changes(
fake_controller: Controller,
mocker: MockerFixture,
):
controller_id = "controller_id"
listen_event_mock = mocker.patch.object(Hass, "listen_event")
lutron_integration = LutronIntegration(fake_controller, {})

await lutron_integration.listen_changes(controller_id)

listen_event_mock.assert_called_once_with(
fake_controller,
lutron_integration.event_callback,
"lutron_caseta_button_event",
serial=controller_id,
)
20 changes: 20 additions & 0 deletions tests/unit_tests/cx_core/integration/mqtt_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Dict

import pytest
from appdaemon.plugins.mqtt.mqttapi import Mqtt
from cx_core.controller import Controller
from cx_core.integration.mqtt import MQTTIntegration
from pytest_mock.plugin import MockerFixture
Expand Down Expand Up @@ -34,3 +35,22 @@ async def test_callback(
handle_action_patch.assert_called_once_with(expected)
else:
handle_action_patch.assert_not_called()


@pytest.mark.asyncio
async def test_listen_changes(
fake_controller: Controller,
mocker: MockerFixture,
):
controller_id = "controller_id"
listen_event_mock = mocker.patch.object(Mqtt, "listen_event")
mqtt_integration = MQTTIntegration(fake_controller, {})

await mqtt_integration.listen_changes(controller_id)

listen_event_mock.assert_called_once_with(
fake_controller,
mqtt_integration.event_callback,
topic=controller_id,
namespace="mqtt",
)
43 changes: 43 additions & 0 deletions tests/unit_tests/cx_core/integration/state_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Optional

import pytest
from appdaemon.plugins.hass.hassapi import Hass
from cx_core.controller import Controller
from cx_core.integration.state import StateIntegration
from pytest_mock.plugin import MockerFixture


@pytest.mark.parametrize("attribute", ["sensor", "entity_id", None])
@pytest.mark.asyncio
async def test_listen_changes(
fake_controller: Controller, mocker: MockerFixture, attribute: Optional[str]
):
kwargs = {}
if attribute is not None:
kwargs["attribute"] = attribute
controller_id = "controller_id"
state_event_mock = mocker.patch.object(Hass, "listen_state")
state_integration = StateIntegration(fake_controller, kwargs)

await state_integration.listen_changes(controller_id)

state_event_mock.assert_called_once_with(
fake_controller,
state_integration.state_callback,
controller_id,
attribute=attribute,
)


@pytest.mark.asyncio
async def test_callback(
fake_controller: Controller,
mocker: MockerFixture,
):

handle_action_patch = mocker.patch.object(fake_controller, "handle_action")
state_integration = StateIntegration(fake_controller, {})

await state_integration.state_callback("test", None, "old_state", "new_state", {})

handle_action_patch.assert_called_once_with("new_state")
Loading

0 comments on commit aeb8aab

Please sign in to comment.