Make Rflink handle set_level command for dimmable devices (#46499)
* Added handle_event for set_level command in dimmable devices * refactor common code for dimmable devices * Force tests Silly change to force tests execution * fix super() * add rflink dim utils
This commit is contained in:
parent
87499989a0
commit
668d018e9c
5 changed files with 167 additions and 53 deletions
|
@ -28,6 +28,8 @@ from homeassistant.helpers.dispatcher import (
|
|||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .utils import brightness_to_rflink
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_EVENT = "event"
|
||||
|
@ -509,7 +511,7 @@ class RflinkCommand(RflinkDevice):
|
|||
|
||||
elif command == "dim":
|
||||
# convert brightness to rflink dim level
|
||||
cmd = str(int(args[0] / 17))
|
||||
cmd = str(brightness_to_rflink(args[0]))
|
||||
self._state = True
|
||||
|
||||
elif command == "toggle":
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Support for Rflink lights."""
|
||||
import logging
|
||||
import re
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -27,6 +28,7 @@ from . import (
|
|||
EVENT_KEY_ID,
|
||||
SwitchableRflinkDevice,
|
||||
)
|
||||
from .utils import brightness_to_rflink, rflink_to_brightness
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -183,30 +185,39 @@ class DimmableRflinkLight(SwitchableRflinkDevice, LightEntity):
|
|||
"""Turn the device on."""
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
# rflink only support 16 brightness levels
|
||||
self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17
|
||||
self._brightness = rflink_to_brightness(
|
||||
brightness_to_rflink(kwargs[ATTR_BRIGHTNESS])
|
||||
)
|
||||
|
||||
# Turn on light at the requested dim level
|
||||
await self._async_handle_command("dim", self._brightness)
|
||||
|
||||
def _handle_event(self, event):
|
||||
"""Adjust state if Rflink picks up a remote command for this device."""
|
||||
self.cancel_queued_send_commands()
|
||||
|
||||
command = event["command"]
|
||||
if command in ["on", "allon"]:
|
||||
self._state = True
|
||||
elif command in ["off", "alloff"]:
|
||||
self._state = False
|
||||
# dimmable device accept 'set_level=(0-15)' commands
|
||||
elif re.search("^set_level=(0?[0-9]|1[0-5])$", command, re.IGNORECASE):
|
||||
self._brightness = rflink_to_brightness(int(command.split("=")[1]))
|
||||
self._state = True
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
if self._brightness is None:
|
||||
return {}
|
||||
return {ATTR_BRIGHTNESS: self._brightness}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_BRIGHTNESS
|
||||
|
||||
|
||||
class HybridRflinkLight(SwitchableRflinkDevice, LightEntity):
|
||||
class HybridRflinkLight(DimmableRflinkLight, LightEntity):
|
||||
"""Rflink light device that sends out both dim and on/off commands.
|
||||
|
||||
Used for protocols which support lights that are not exclusively on/off
|
||||
|
@ -221,52 +232,14 @@ class HybridRflinkLight(SwitchableRflinkDevice, LightEntity):
|
|||
Which results in a nice house disco :)
|
||||
"""
|
||||
|
||||
_brightness = 255
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Restore RFLink light brightness attribute."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
old_state = await self.async_get_last_state()
|
||||
if (
|
||||
old_state is not None
|
||||
and old_state.attributes.get(ATTR_BRIGHTNESS) is not None
|
||||
):
|
||||
# restore also brightness in dimmables devices
|
||||
self._brightness = int(old_state.attributes[ATTR_BRIGHTNESS])
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the device on and set dim level."""
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
# rflink only support 16 brightness levels
|
||||
self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17
|
||||
|
||||
# if receiver supports dimming this will turn on the light
|
||||
# at the requested dim level
|
||||
await self._async_handle_command("dim", self._brightness)
|
||||
|
||||
await super().async_turn_on(**kwargs)
|
||||
# if the receiving device does not support dimlevel this
|
||||
# will ensure it is turned on when full brightness is set
|
||||
if self._brightness == 255:
|
||||
if self.brightness == 255:
|
||||
await self._async_handle_command("turn_on")
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
if self._brightness is None:
|
||||
return {}
|
||||
return {ATTR_BRIGHTNESS: self._brightness}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_BRIGHTNESS
|
||||
|
||||
|
||||
class ToggleRflinkLight(SwitchableRflinkDevice, LightEntity):
|
||||
"""Rflink light device which sends out only 'on' commands.
|
||||
|
|
11
homeassistant/components/rflink/utils.py
Normal file
11
homeassistant/components/rflink/utils.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
"""RFLink integration utils."""
|
||||
|
||||
|
||||
def brightness_to_rflink(brightness: int) -> int:
|
||||
"""Convert 0-255 brightness to RFLink dim level (0-15)."""
|
||||
return int(brightness / 17)
|
||||
|
||||
|
||||
def rflink_to_brightness(dim_level: int) -> int:
|
||||
"""Convert RFLink dim level (0-15) to 0-255 brightness."""
|
||||
return int(dim_level * 17)
|
|
@ -340,6 +340,93 @@ async def test_type_toggle(hass, monkeypatch):
|
|||
assert hass.states.get(f"{DOMAIN}.toggle_test").state == "off"
|
||||
|
||||
|
||||
async def test_set_level_command(hass, monkeypatch):
|
||||
"""Test 'set_level=XX' events."""
|
||||
config = {
|
||||
"rflink": {"port": "/dev/ttyABC0"},
|
||||
DOMAIN: {
|
||||
"platform": "rflink",
|
||||
"devices": {
|
||||
"newkaku_12345678_0": {"name": "l1"},
|
||||
"test_no_dimmable": {"name": "l2"},
|
||||
"test_dimmable": {"name": "l3", "type": "dimmable"},
|
||||
"test_hybrid": {"name": "l4", "type": "hybrid"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# setup mocking rflink module
|
||||
event_callback, _, _, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch)
|
||||
|
||||
# test sending command to a newkaku device
|
||||
event_callback({"id": "newkaku_12345678_0", "command": "set_level=10"})
|
||||
await hass.async_block_till_done()
|
||||
# should affect state
|
||||
state = hass.states.get(f"{DOMAIN}.l1")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 170
|
||||
# turn off
|
||||
event_callback({"id": "newkaku_12345678_0", "command": "off"})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(f"{DOMAIN}.l1")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
# off light shouldn't have brightness
|
||||
assert not state.attributes.get(ATTR_BRIGHTNESS)
|
||||
# turn on
|
||||
event_callback({"id": "newkaku_12345678_0", "command": "on"})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(f"{DOMAIN}.l1")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 170
|
||||
|
||||
# test sending command to a no dimmable device
|
||||
event_callback({"id": "test_no_dimmable", "command": "set_level=10"})
|
||||
await hass.async_block_till_done()
|
||||
# should NOT affect state
|
||||
state = hass.states.get(f"{DOMAIN}.l2")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert not state.attributes.get(ATTR_BRIGHTNESS)
|
||||
|
||||
# test sending command to a dimmable device
|
||||
event_callback({"id": "test_dimmable", "command": "set_level=5"})
|
||||
await hass.async_block_till_done()
|
||||
# should affect state
|
||||
state = hass.states.get(f"{DOMAIN}.l3")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 85
|
||||
|
||||
# test sending command to a hybrid device
|
||||
event_callback({"id": "test_hybrid", "command": "set_level=15"})
|
||||
await hass.async_block_till_done()
|
||||
# should affect state
|
||||
state = hass.states.get(f"{DOMAIN}.l4")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 255
|
||||
|
||||
event_callback({"id": "test_hybrid", "command": "off"})
|
||||
await hass.async_block_till_done()
|
||||
# should affect state
|
||||
state = hass.states.get(f"{DOMAIN}.l4")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
# off light shouldn't have brightness
|
||||
assert not state.attributes.get(ATTR_BRIGHTNESS)
|
||||
|
||||
event_callback({"id": "test_hybrid", "command": "set_level=0"})
|
||||
await hass.async_block_till_done()
|
||||
# should affect state
|
||||
state = hass.states.get(f"{DOMAIN}.l4")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 0
|
||||
|
||||
|
||||
async def test_group_alias(hass, monkeypatch):
|
||||
"""Group aliases should only respond to group commands (allon/alloff)."""
|
||||
config = {
|
||||
|
@ -347,7 +434,12 @@ async def test_group_alias(hass, monkeypatch):
|
|||
DOMAIN: {
|
||||
"platform": "rflink",
|
||||
"devices": {
|
||||
"protocol_0_0": {"name": "test", "group_aliases": ["test_group_0_0"]}
|
||||
"protocol_0_0": {"name": "test", "group_aliases": ["test_group_0_0"]},
|
||||
"protocol_0_1": {
|
||||
"name": "test2",
|
||||
"type": "dimmable",
|
||||
"group_aliases": ["test_group_0_0"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -362,12 +454,14 @@ async def test_group_alias(hass, monkeypatch):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(f"{DOMAIN}.test").state == "on"
|
||||
assert hass.states.get(f"{DOMAIN}.test2").state == "on"
|
||||
|
||||
# test sending group command to group alias
|
||||
event_callback({"id": "test_group_0_0", "command": "off"})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(f"{DOMAIN}.test").state == "on"
|
||||
assert hass.states.get(f"{DOMAIN}.test2").state == "on"
|
||||
|
||||
|
||||
async def test_nogroup_alias(hass, monkeypatch):
|
||||
|
@ -396,7 +490,7 @@ async def test_nogroup_alias(hass, monkeypatch):
|
|||
# should not affect state
|
||||
assert hass.states.get(f"{DOMAIN}.test").state == "off"
|
||||
|
||||
# test sending group command to nogroup alias
|
||||
# test sending group commands to nogroup alias
|
||||
event_callback({"id": "test_nogroup_0_0", "command": "on"})
|
||||
await hass.async_block_till_done()
|
||||
# should affect state
|
||||
|
@ -501,7 +595,8 @@ async def test_restore_state(hass, monkeypatch):
|
|||
state = hass.states.get(f"{DOMAIN}.l4")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 255
|
||||
# off light shouldn't have brightness
|
||||
assert not state.attributes.get(ATTR_BRIGHTNESS)
|
||||
assert state.attributes["assumed_state"]
|
||||
|
||||
# test coverage for dimmable light
|
||||
|
|
33
tests/components/rflink/test_utils.py
Normal file
33
tests/components/rflink/test_utils.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
"""Test for RFLink utils methods."""
|
||||
from homeassistant.components.rflink.utils import (
|
||||
brightness_to_rflink,
|
||||
rflink_to_brightness,
|
||||
)
|
||||
|
||||
|
||||
async def test_utils(hass, monkeypatch):
|
||||
"""Test all utils methods."""
|
||||
# test brightness_to_rflink
|
||||
assert brightness_to_rflink(0) == 0
|
||||
assert brightness_to_rflink(17) == 1
|
||||
assert brightness_to_rflink(34) == 2
|
||||
assert brightness_to_rflink(85) == 5
|
||||
assert brightness_to_rflink(170) == 10
|
||||
assert brightness_to_rflink(255) == 15
|
||||
|
||||
assert brightness_to_rflink(10) == 0
|
||||
assert brightness_to_rflink(20) == 1
|
||||
assert brightness_to_rflink(30) == 1
|
||||
assert brightness_to_rflink(40) == 2
|
||||
assert brightness_to_rflink(50) == 2
|
||||
assert brightness_to_rflink(60) == 3
|
||||
assert brightness_to_rflink(70) == 4
|
||||
assert brightness_to_rflink(80) == 4
|
||||
|
||||
# test rflink_to_brightness
|
||||
assert rflink_to_brightness(0) == 0
|
||||
assert rflink_to_brightness(1) == 17
|
||||
assert rflink_to_brightness(5) == 85
|
||||
assert rflink_to_brightness(10) == 170
|
||||
assert rflink_to_brightness(12) == 204
|
||||
assert rflink_to_brightness(15) == 255
|
Loading…
Add table
Add a link
Reference in a new issue