Skip to content

Commit

Permalink
fix(light): add transition just if the light supports it
Browse files Browse the repository at this point in the history
  • Loading branch information
xaviml committed Mar 26, 2020
1 parent fa851b7 commit 33a5c1b
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 107 deletions.
16 changes: 9 additions & 7 deletions apps/controllerx/core/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import abc
import time
from collections import defaultdict

from appdaemon.utils import sync_wrapper

import version
from core import integration as integration_module
from core.stepper import Stepper
import version


DEFAULT_DELAY = 350 # In milliseconds
DEFAULT_ACTION_DELTA = 300 # In milliseconds
Expand All @@ -36,7 +38,7 @@ class Controller(hass.Hass, abc.ABC):
This is the parent Controller, all controllers must extend from this class.
"""

def initialize(self):
async def initialize(self):
self.log(f"ControllerX {version.__version__}")
self.check_ad_version()

Expand Down Expand Up @@ -172,10 +174,10 @@ class TypeController(Controller, abc.ABC):
def get_domain(self):
pass

def check_domain(self, entity):
async def check_domain(self, entity):
domain = self.get_domain()
if entity.startswith("group."):
entities = self.get_state(entity, attribute="entity_id")
entities = await self.get_state(entity, attribute="entity_id")
same_domain = all([elem.startswith(domain + ".") for elem in entities])
if not same_domain:
raise ValueError(
Expand All @@ -195,10 +197,10 @@ async def get_entity_state(self, entity, attribute=None):


class ReleaseHoldController(Controller, abc.ABC):
def initialize(self):
async def initialize(self):
self.on_hold = False
self.delay = self.args.get("delay", self.default_delay())
super().initialize()
await super().initialize()

@action
async def release(self):
Expand Down
38 changes: 20 additions & 18 deletions apps/controllerx/core/type/light_controller.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
from collections import defaultdict

from const import Light
Expand Down Expand Up @@ -72,9 +73,9 @@ class LightController(TypeController, ReleaseHoldController):
index_color = 0
value_attribute = None

def initialize(self):
async def initialize(self):
self.light = self.get_light(self.args["light"])
self.check_domain(self.light["name"])
await self.check_domain(self.light["name"])
manual_steps = self.args.get("manual_steps", DEFAULT_MANUAL_STEPS)
automatic_steps = self.args.get("automatic_steps", DEFAULT_AUTOMATIC_STEPS)
self.min_brightness = self.args.get("min_brightness", DEFAULT_MIN_BRIGHTNESS)
Expand Down Expand Up @@ -104,7 +105,13 @@ def initialize(self):
self.smooth_power_on = self.args.get(
"smooth_power_on", self.supports_smooth_power_on()
)
super().initialize()

bitfield = await self.get_entity_state(
self.light["name"], attribute="supported_features"
)

self.supported_features = light_features.decode(bitfield)
await super().initialize()

def get_domain(self):
return "light"
Expand Down Expand Up @@ -254,29 +261,24 @@ def get_light(self, light):
else:
return {"name": light["name"], "color_mode": "auto"}

@action
async def on(self, **attributes):
async def call_light_service(self, service, **attributes):
if "transition" not in attributes:
attributes["transition"] = self.transition / 1000
self.call_service(
"homeassistant/turn_on", entity_id=self.light["name"], **attributes
)
if light_features.SUPPORT_TRANSITION not in self.supported_features:
del attributes["transition"]
self.call_service(service, entity_id=self.light["name"], **attributes)

@action
async def on(self, **attributes):
await self.call_light_service("light/turn_on", **attributes)

@action
async def off(self, **attributes):
if "transition" not in attributes:
attributes["transition"] = self.transition / 1000
self.call_service(
"homeassistant/turn_off", entity_id=self.light["name"], **attributes
)
await self.call_light_service("light/turn_off", **attributes)

@action
async def toggle(self, **attributes):
if "transition" not in attributes:
attributes["transition"] = self.transition / 1000
self.call_service(
"homeassistant/toggle", entity_id=self.light["name"], **attributes
)
await self.call_light_service("light/toggle", **attributes)

@action
async def set_value(self, attribute, fraction):
Expand Down
6 changes: 3 additions & 3 deletions apps/controllerx/core/type/media_player_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@


class MediaPlayerController(TypeController, ReleaseHoldController):
def initialize(self):
async def initialize(self):
self.media_player = self.args["media_player"]
self.check_domain(self.media_player)
await self.check_domain(self.media_player)
volume_steps = self.args.get("volume_steps", DEFAULT_VOLUME_STEPS)
self.volume_stepper = MinMaxStepper(0, 1, volume_steps)
self.volume_level = 0
super().initialize()
await super().initialize()

def get_domain(self):
return "media_player"
Expand Down
2 changes: 1 addition & 1 deletion apps/controllerx/devices/trust.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def get_z2m_actions_mapping(self):
}


class ZYCT202MediaPlayerController(MediaPlayer):
class ZYCT202MediaPlayerController(MediaPlayerController):
def get_z2m_actions_mapping(self):
return {
"on": MediaPlayer.PLAY_PAUSE,
Expand Down
55 changes: 38 additions & 17 deletions tests/core/controller_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ async def fake_action(self):
),
],
)
def test_initialize(
@pytest.mark.asyncio
async def test_initialize(
sut,
mocker,
monkeypatch,
Expand All @@ -88,7 +89,7 @@ def test_initialize(
get_actions_mapping = mocker.spy(sut, "get_actions_mapping")

# SUT
sut.initialize()
await sut.initialize()

# Checks
check_ad_version.assert_called_once()
Expand Down Expand Up @@ -128,28 +129,39 @@ def test_get_option(sut, option, options, expect_an_error):


@pytest.mark.parametrize(
"integration_input, integration_name_expected, args_expected",
"integration_input, integration_name_expected, args_expected, error_expected",
[
("z2m", "z2m", {}),
({"name": "zha"}, "zha", {}),
("z2m", "z2m", {}, False),
({"name": "zha"}, "zha", {}, False),
(
{"name": "deconz", "attr1": "value1", "attr2": "value2"},
"deconz",
{"attr1": "value1", "attr2": "value2"},
False,
),
({"test": "no name"}, "z2m", {}, True),
],
)
def test_get_integration(
sut, mocker, integration_input, integration_name_expected, args_expected
sut,
mocker,
integration_input,
integration_name_expected,
args_expected,
error_expected,
):
get_integrations_spy = mocker.spy(integration_module, "get_integrations")

# SUT
integration = sut.get_integration(integration_input)
if error_expected:
with pytest.raises(ValueError) as e:
integration = sut.get_integration(integration_input)
else:
integration = sut.get_integration(integration_input)

# Checks
get_integrations_spy.assert_called_once_with(sut, args_expected)
assert integration.name == integration_name_expected
# Checks
get_integrations_spy.assert_called_once_with(sut, args_expected)
assert integration.name == integration_name_expected


def test_check_ad_version_throwing_error(sut, monkeypatch):
Expand Down Expand Up @@ -226,13 +238,22 @@ def fake_action():


@pytest.mark.parametrize(
"test_input,expected",
"test_input, expected, error_expected",
[
(fake_action, (fake_action,)),
((fake_action,), (fake_action,)),
((fake_action, "test"), (fake_action, "test")),
(fake_action, (fake_action,), False),
((fake_action,), (fake_action,), False),
((fake_action, "test"), (fake_action, "test"), False),
("not-list-or-function", (), True),
],
)
def test_get_action(sut, test_input, expected):
output = sut.get_action(test_input)
assert output == expected
def test_get_action(sut, test_input, expected, error_expected):
if error_expected:
with pytest.raises(ValueError) as e:
output = sut.get_action(test_input)
assert (
str(e.value)
== "The action value from the action mapping should be a list or a function"
)
else:
output = sut.get_action(test_input)
assert output == expected
18 changes: 14 additions & 4 deletions tests/core/custom_controller_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
CustomMediaPlayerController,
Controller,
)
from tests.utils import hass_mock
from tests.utils import hass_mock, fake_async_function


@pytest.mark.parametrize(
Expand Down Expand Up @@ -55,7 +55,14 @@
)
@pytest.mark.asyncio
async def test_custom_light_controller(
hass_mock, mocker, custom_cls, mapping, action_input, mock_function, expected_calls
hass_mock,
monkeypatch,
mocker,
custom_cls,
mapping,
action_input,
mock_function,
expected_calls,
):
sut = custom_cls()
sut.args = {
Expand All @@ -66,7 +73,10 @@ async def test_custom_light_controller(
"mapping": mapping,
}
mocked = mocker.patch.object(sut, mock_function)
sut.initialize()

monkeypatch.setattr(sut, "get_entity_state", fake_async_function("0"))

await sut.initialize()
sut.action_delta = 0
await sut.handle_action(action_input)

Expand Down Expand Up @@ -132,7 +142,7 @@ async def fake_call_service(self, service, **data):

monkeypatch.setattr(Controller, "call_service", fake_call_service)

sut.initialize()
await sut.initialize()
sut.action_delta = 0
await sut.handle_action("action")

Expand Down
9 changes: 5 additions & 4 deletions tests/core/release_hold_controller_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from core import integration as integration_module
from core.controller import Controller, ReleaseHoldController

from tests.utils import hass_mock
from tests.utils import hass_mock, fake_async_function


class FakeReleaseHoldController(ReleaseHoldController):
Expand All @@ -19,12 +19,13 @@ def sut(hass_mock):
return c


def test_initialize(sut, monkeypatch):
monkeypatch.setattr(Controller, "initialize", lambda self: None)
@pytest.mark.asyncio
async def test_initialize(sut, monkeypatch):
monkeypatch.setattr(Controller, "initialize", fake_async_function())
monkeypatch.setattr(sut, "default_delay", lambda: 500)
monkeypatch.setattr(sut, "sleep", lambda time: None)
# SUT
sut.initialize()
await sut.initialize()

assert sut.delay == 500

Expand Down
Loading

0 comments on commit 33a5c1b

Please sign in to comment.