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:
javicalle 2021-03-21 08:43:38 +01:00 committed by GitHub
parent 87499989a0
commit 668d018e9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 167 additions and 53 deletions

View file

@ -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":

View file

@ -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.

View 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)

View file

@ -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

View 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