Skip to content

Commit

Permalink
feat(integration): add shelly and shellyforhass integrations
Browse files Browse the repository at this point in the history
related to #441
  • Loading branch information
xaviml committed May 3, 2022
1 parent 0118e9f commit 9762d92
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 3 deletions.
3 changes: 1 addition & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"python.analysis.completeFunctionParens": true
}
}
16 changes: 16 additions & 0 deletions apps/controllerx/cx_core/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,5 +578,21 @@ def get_homematic_actions_mapping(self) -> Optional[DefaultActionsMapping]:
"""
return None

def get_shelly_actions_mapping(self) -> Optional[DefaultActionsMapping]:
"""
Controllers can implement this function. It should return a dict
with the command that a controller can take and the functions as values.
This is used for Shelly support.
"""
return None

def get_shellyforhass_actions_mapping(self) -> Optional[DefaultActionsMapping]:
"""
Controllers can implement this function. It should return a dict
with the command that a controller can take and the functions as values.
This is used for Shelly for HASS support.
"""
return None

def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
return {}
25 changes: 25 additions & 0 deletions apps/controllerx/cx_core/integration/shelly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Any, Dict, Optional

from appdaemon.plugins.hass.hassapi import Hass
from cx_const import DefaultActionsMapping
from cx_core.integration import EventData, Integration


class ShellyIntegration(Integration):
name = "shelly"

def get_default_actions_mapping(self) -> Optional[DefaultActionsMapping]:
return self.controller.get_shelly_actions_mapping()

async def listen_changes(self, controller_id: str) -> None:
await Hass.listen_event(
self.controller, self.event_callback, "shelly.click", device=controller_id
)

async def event_callback(
self, event_name: str, data: EventData, kwargs: Dict[str, Any]
) -> None:
click_type = data["click_type"]
channel = data["channel"]
action = f"{click_type}_{channel}"
await self.controller.handle_action(action, extra=data)
27 changes: 27 additions & 0 deletions apps/controllerx/cx_core/integration/shellyforhass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Any, Dict, Optional

from appdaemon.plugins.hass.hassapi import Hass
from cx_const import DefaultActionsMapping
from cx_core.integration import EventData, Integration


class ShellyForHASSIntegration(Integration):
name = "shellyforhass"

def get_default_actions_mapping(self) -> Optional[DefaultActionsMapping]:
return self.controller.get_shellyforhass_actions_mapping()

async def listen_changes(self, controller_id: str) -> None:
await Hass.listen_event(
self.controller,
self.event_callback,
"shellyforhass.click",
entity_id=controller_id,
)

async def event_callback(
self, event_name: str, data: EventData, kwargs: Dict[str, Any]
) -> None:
click_type = data["click_type"]
action = f"{click_type}"
await self.controller.handle_action(action, extra=data)
10 changes: 9 additions & 1 deletion docs/others/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,12 @@ This integration(**`lutron_caseta`**) listens to `lutron_caseta_button_event` ev

#### Homematic

This integration ([**`homematic`**](https://www.home-assistant.io/integrations/homematic/)) listens to `homematic.keypress` events. It created an action like `<action_type>_<channel>`. It does not have any additional arguments.
This integration ([**`homematic`**](https://www.home-assistant.io/integrations/homematic)) listens to `homematic.keypress` events. It creates an action like `<action_type>_<channel>`. It does not have any additional arguments.

#### Shelly

This integration ([**`shelly`**](https://www.home-assistant.io/integrations/shelly)) listens to `shelly.click` events. It creates an action like `<click_type>_<channel>`. It does not have any additional arguments.

#### Shelly for HASS

This integration ([**`shellyforhass`**](https://github.com/StyraHem/ShellyForHASS)) listens to `shellyforhass.click` events. It creates an action like `<action_type>`. It does not have any additional arguments.
2 changes: 2 additions & 0 deletions tests/unit_tests/cx_core/integration/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ def test_get_integrations(fake_controller: Controller) -> None:
"mqtt",
"lutron_caseta",
"homematic",
"shelly",
"shellyforhass",
}
62 changes: 62 additions & 0 deletions tests/unit_tests/cx_core/integration/shelly_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import Any, Dict

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


@pytest.mark.parametrize(
"data, expected",
[
(
{
"device_id": "e09c64a22553484d804353ef97f6fcd6",
"device": "shellybutton1-A4C12A45174",
"channel": 1,
"click_type": "single",
"generation": 1,
},
"single_1",
),
(
{
"device_id": "e09d64a22553384d8043532f97f6fcd6",
"device": "shellybutton1-A4C13B45274",
"channel": 3,
"click_type": "btn_down",
"generation": 1,
},
"btn_down_3",
),
],
)
async def test_callback(
fake_controller: Controller,
mocker: MockerFixture,
data: Dict[str, Any],
expected: str,
) -> None:
handle_action_patch = mocker.patch.object(fake_controller, "handle_action")
shelly_integration = ShellyIntegration(fake_controller, {})
await shelly_integration.event_callback("test", data, {})
handle_action_patch.assert_called_once_with(expected, extra=data)


async def test_listen_changes(
fake_controller: Controller,
mocker: MockerFixture,
) -> None:
controller_id = "controller_id"
listen_event_mock = mocker.patch.object(Hass, "listen_event")
shelly_integration = ShellyIntegration(fake_controller, {})

await shelly_integration.listen_changes(controller_id)

listen_event_mock.assert_called_once_with(
fake_controller,
shelly_integration.event_callback,
"shelly.click",
device=controller_id,
)
56 changes: 56 additions & 0 deletions tests/unit_tests/cx_core/integration/shellyforhass_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from typing import Any, Dict

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


@pytest.mark.parametrize(
"data, expected",
[
(
{
"entity_id": "binary_sensor.shelly_shbtn_1_xxxxxx_switch",
"click_type": "single",
},
"single",
),
(
{
"entity_id": "binary_sensor.shelly_shbtn_1_xxxxxx_switch",
"click_type": "double",
},
"double",
),
],
)
async def test_callback(
fake_controller: Controller,
mocker: MockerFixture,
data: Dict[str, Any],
expected: str,
) -> None:
handle_action_patch = mocker.patch.object(fake_controller, "handle_action")
shellyforhass_integration = ShellyForHASSIntegration(fake_controller, {})
await shellyforhass_integration.event_callback("test", data, {})
handle_action_patch.assert_called_once_with(expected, extra=data)


async def test_listen_changes(
fake_controller: Controller,
mocker: MockerFixture,
) -> None:
controller_id = "controller_id"
listen_event_mock = mocker.patch.object(Hass, "listen_event")
shellyforhass_integration = ShellyForHASSIntegration(fake_controller, {})

await shellyforhass_integration.listen_changes(controller_id)

listen_event_mock.assert_called_once_with(
fake_controller,
shellyforhass_integration.event_callback,
"shellyforhass.click",
entity_id=controller_id,
)
2 changes: 2 additions & 0 deletions tests/unit_tests/cx_devices/devices_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def test_devices(device_class: Type[Controller]) -> None:
device.get_zha_actions_mapping,
device.get_lutron_caseta_actions_mapping,
device.get_homematic_actions_mapping,
device.get_shelly_actions_mapping,
device.get_shellyforhass_actions_mapping,
]
for func in integration_mappings_funcs:
mappings = func()
Expand Down

0 comments on commit 9762d92

Please sign in to comment.