From 678f2730023d2065e941d7696bffd6667214d51a Mon Sep 17 00:00:00 2001 From: martinfrancois Date: Sat, 18 Mar 2017 21:34:17 +0100 Subject: [PATCH] Rflink: added support for lights with toggle type (#6521) * added support for lights with toggle type * fixed style errors * introduced tests for the toggle type it's not passing yet because of an assertionerror at line 407 * updated to reflect tristate of "state" * Format code according to pep8 Added line break for import that was too long. * fixed lint, replaced if statement with 'var = bool(test)' * changed implementation of state check according to bug on https://github.com/home-assistant/home-assistant/pull/6521/files/6bceb04ca15666d2a7e7f03548af69e15f5965a6#r106758784 --- homeassistant/components/light/rflink.py | 46 ++++++++++++++++++++++-- homeassistant/components/rflink.py | 6 ++++ tests/components/light/test_rflink.py | 43 ++++++++++++++++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/light/rflink.py b/homeassistant/components/light/rflink.py index 386ced1ee88..0630792602d 100644 --- a/homeassistant/components/light/rflink.py +++ b/homeassistant/components/light/rflink.py @@ -14,8 +14,8 @@ from homeassistant.components.rflink import ( CONF_IGNORE_DEVICES, CONF_SIGNAL_REPETITIONS, DATA_DEVICE_REGISTER, DATA_ENTITY_LOOKUP, DEVICE_DEFAULTS_SCHEMA, DOMAIN, EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, cv, vol) -from homeassistant.const import CONF_NAME, CONF_PLATFORM, CONF_TYPE - +from homeassistant.const import ( + CONF_NAME, CONF_PLATFORM, CONF_TYPE, STATE_UNKNOWN) DEPENDENCIES = ['rflink'] _LOGGER = logging.getLogger(__name__) @@ -23,6 +23,7 @@ _LOGGER = logging.getLogger(__name__) TYPE_DIMMABLE = 'dimmable' TYPE_SWITCHABLE = 'switchable' TYPE_HYBRID = 'hybrid' +TYPE_TOGGLE = 'toggle' PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): DOMAIN, @@ -33,7 +34,8 @@ PLATFORM_SCHEMA = vol.Schema({ cv.string: { vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_TYPE): - vol.Any(TYPE_DIMMABLE, TYPE_SWITCHABLE, TYPE_HYBRID), + vol.Any(TYPE_DIMMABLE, TYPE_SWITCHABLE, + TYPE_HYBRID, TYPE_TOGGLE), vol.Optional(CONF_ALIASSES, default=[]): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, @@ -71,6 +73,9 @@ def entity_class_for_type(entity_type): # sends 'dim' and 'on' command to support both dimmers and on/off # switches. Not compatible with signal repetition. TYPE_HYBRID: HybridRflinkLight, + # sends only 'on' commands for switches which turn on and off + # using the same 'on' command for both. + TYPE_TOGGLE: ToggleRflinkLight, } return entity_device_mapping.get(entity_type, RflinkLight) @@ -213,3 +218,38 @@ class HybridRflinkLight(SwitchableRflinkDevice, Light): def supported_features(self): """Flag supported features.""" return SUPPORT_BRIGHTNESS + + +class ToggleRflinkLight(SwitchableRflinkDevice, Light): + """Rflink light device which sends out only 'on' commands. + + Some switches like for example Livolo light switches use the + same 'on' command to switch on and switch off the lights. + If the light is on and 'on' gets sent, the light will turn off + and if the light is off and 'on' gets sent, the light will turn on. + """ + + @property + def entity_id(self): + """Return entity id.""" + return "light.{}".format(self.name) + + 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 == 'on': + # if the state is unknown or false, it gets set as true + # if the state is true, it gets set as false + self._state = self._state in [STATE_UNKNOWN, False] + + @asyncio.coroutine + def async_turn_on(self, **kwargs): + """Turn the device on.""" + yield from self._async_handle_command('toggle') + + @asyncio.coroutine + def async_turn_off(self, **kwargs): + """Turn the device off.""" + yield from self._async_handle_command('toggle') diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 496ae7fc7d8..df1ca955a4d 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -316,6 +316,12 @@ class RflinkCommand(RflinkDevice): cmd = str(int(args[0] / 17)) self._state = True + elif command == 'toggle': + cmd = 'on' + # if the state is unknown or false, it gets set as true + # if the state is true, it gets set as false + self._state = self._state in [STATE_UNKNOWN, False] + # Send initial command and queue repetitions. # This allows the entity state to be updated quickly and not having to # wait for all repetitions to be sent diff --git a/tests/components/light/test_rflink.py b/tests/components/light/test_rflink.py index 02375f27fc0..2cd6d6fd092 100644 --- a/tests/components/light/test_rflink.py +++ b/tests/components/light/test_rflink.py @@ -342,3 +342,46 @@ def test_signal_repetitions_cancelling(hass, monkeypatch): assert protocol.send_command_ack.call_args_list[1][0][1] == 'on' assert protocol.send_command_ack.call_args_list[2][0][1] == 'on' assert protocol.send_command_ack.call_args_list[3][0][1] == 'on' + + +@asyncio.coroutine +def test_type_toggle(hass, monkeypatch): + """Test toggle type lights (on/on).""" + config = { + 'rflink': { + 'port': '/dev/ttyABC0', + }, + DOMAIN: { + 'platform': 'rflink', + 'devices': { + 'toggle_0_0': { + 'name': 'toggle_test', + 'type': 'toggle', + }, + }, + }, + } + + # setup mocking rflink module + event_callback, _, _, _ = yield from mock_rflink( + hass, config, DOMAIN, monkeypatch) + + assert hass.states.get('light.toggle_test').state == 'off' + + # test sending on command to toggle alias + event_callback({ + 'id': 'toggle_0_0', + 'command': 'on', + }) + yield from hass.async_block_till_done() + + assert hass.states.get('light.toggle_test').state == 'on' + + # test sending group command to group alias + event_callback({ + 'id': 'toggle_0_0', + 'command': 'on', + }) + yield from hass.async_block_till_done() + + assert hass.states.get('light.toggle_test').state == 'off'