From df918b1f5eed0c26120c7ddc96f53083d3ce57e8 Mon Sep 17 00:00:00 2001 From: Pascal Osinga Date: Thu, 14 Sep 2017 21:54:18 +0200 Subject: [PATCH 01/11] second try on rflink / cover --- homeassistant/components/cover/rflink.py | 119 +++++++++++++++++++++++ homeassistant/components/rflink.py | 21 +++- tests/components/test_rflink.py | 38 +++++++- 3 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/cover/rflink.py diff --git a/homeassistant/components/cover/rflink.py b/homeassistant/components/cover/rflink.py new file mode 100644 index 00000000000000..d5fe0f616f7f91 --- /dev/null +++ b/homeassistant/components/cover/rflink.py @@ -0,0 +1,119 @@ +""" +Support for Rflink Cover devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/cover.rflink/ +""" +import asyncio +import logging + +import voluptuous as vol + +from homeassistant.components.rflink import ( + DATA_ENTITY_GROUP_LOOKUP, DATA_ENTITY_LOOKUP, + DEVICE_DEFAULTS_SCHEMA, EVENT_KEY_COMMAND, RflinkCommand) +from homeassistant.components.cover import ( + CoverDevice, PLATFORM_SCHEMA) +import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_NAME +DEPENDENCIES = ['rflink'] + +_LOGGER = logging.getLogger(__name__) + + +CONF_ALIASES = 'aliases' +CONF_GROUP_ALIASES = 'group_aliases' +CONF_GROUP = 'group' +CONF_NOGROUP_ALIASES = 'nogroup_aliases' +CONF_DEVICE_DEFAULTS = 'device_defaults' +CONF_DEVICES = 'devices' +CONF_AUTOMATIC_ADD = 'automatic_add' +CONF_FIRE_EVENT = 'fire_event' +CONF_IGNORE_DEVICES = 'ignore_devices' +CONF_RECONNECT_INTERVAL = 'reconnect_interval' +CONF_SIGNAL_REPETITIONS = 'signal_repetitions' +CONF_WAIT_FOR_ACK = 'wait_for_ack' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_DEVICE_DEFAULTS, default=DEVICE_DEFAULTS_SCHEMA({})): + DEVICE_DEFAULTS_SCHEMA, + vol.Optional(CONF_DEVICES, default={}): vol.Schema({ + cv.string: { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ALIASES, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_GROUP_ALIASES, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_NOGROUP_ALIASES, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, + vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int), + vol.Optional(CONF_GROUP, default=True): cv.boolean, + }, + }), +}) + + +def devices_from_config(domain_config, hass=None): + """Parse configuration and add Rflink cover devices.""" + devices = [] + for device_id, config in domain_config[CONF_DEVICES].items(): + device_config = dict(domain_config[CONF_DEVICE_DEFAULTS], **config) + device = RflinkCover(device_id, hass, **device_config) + devices.append(device) + + # Register entity (and aliases) to listen to incoming rflink events + # Device id and normal aliases respond to normal and group command + hass.data[DATA_ENTITY_LOOKUP][ + EVENT_KEY_COMMAND][device_id].append(device) + if config[CONF_GROUP]: + hass.data[DATA_ENTITY_GROUP_LOOKUP][ + EVENT_KEY_COMMAND][device_id].append(device) + for _id in config[CONF_ALIASES]: + hass.data[DATA_ENTITY_LOOKUP][ + EVENT_KEY_COMMAND][_id].append(device) + hass.data[DATA_ENTITY_GROUP_LOOKUP][ + EVENT_KEY_COMMAND][_id].append(device) + return devices + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the Rflink cover platform.""" + async_add_devices(devices_from_config(config, hass)) + + +class RflinkCover(RflinkCommand, CoverDevice): + """Rflink entity which can switch on/stop/off (eg: cover).""" + + 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 + + @property + def should_poll(self): + """No polling available in RFlink cover.""" + return False + + @property + def is_closed(self): + """Return if the cover is closed.""" + return None + + def async_close_cover(self, **kwargs): + """Turn the device close.""" + return self._async_handle_command("close_cover") + + def async_open_cover(self, **kwargs): + """Turn the device open.""" + return self._async_handle_command("open_cover") + + def async_stop_cover(self, **kwargs): + """Turn the device stop.""" + return self._async_handle_command("stop_cover") diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 74e533d70ec2cb..8493972f70cd99 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -11,6 +11,8 @@ import os import async_timeout +import voluptuous as vol + from homeassistant.config import load_yaml_config_file from homeassistant.const import ( ATTR_ENTITY_ID, CONF_COMMAND, CONF_HOST, CONF_PORT, @@ -18,9 +20,8 @@ from homeassistant.core import CoreState, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import get_deprecated from homeassistant.helpers.entity import Entity -import voluptuous as vol +from homeassistant.helpers.deprecation import get_deprecated REQUIREMENTS = ['rflink==0.0.34'] @@ -98,7 +99,8 @@ def identify_event_type(event): return EVENT_KEY_COMMAND elif EVENT_KEY_SENSOR in event: return EVENT_KEY_SENSOR - return 'unknown' + else: + return 'unknown' @asyncio.coroutine @@ -272,7 +274,7 @@ def handle_event(self, event): self._handle_event(event) # Propagate changes through ha - self.async_schedule_update_ha_state() + self.hass.async_add_job(self.async_update_ha_state()) # Put command onto bus for user to subscribe to if self._should_fire_event and identify_event_type( @@ -370,6 +372,17 @@ def _async_handle_command(self, command, *args): # if the state is true, it gets set as false self._state = self._state in [STATE_UNKNOWN, False] + # Cover options for RFlink + elif command == 'close_cover': + cmd = 'DOWN' + + elif command == 'open_cover': + cmd = 'UP' + + elif command == 'stop_cover': + cmd = 'STOP' + self._state = True + # 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/test_rflink.py b/tests/components/test_rflink.py index b4cdd96f817bdc..52ac6643a949e5 100644 --- a/tests/components/test_rflink.py +++ b/tests/components/test_rflink.py @@ -6,7 +6,8 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components.rflink import ( CONF_RECONNECT_INTERVAL, SERVICE_SEND_COMMAND) -from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF +from homeassistant.const import ( + ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_STOP_COVER) from tests.common import assert_setup_component @@ -119,6 +120,39 @@ def test_send_no_wait(hass, monkeypatch): assert protocol.send_command.call_args_list[0][0][1] == 'off' +@asyncio.coroutine +def test_cover_send_no_wait(hass, monkeypatch): + """Test command sending to a cover device without ack.""" + domain = 'cover' + config = { + 'rflink': { + 'port': '/dev/ttyABC0', + 'wait_for_ack': False, + }, + domain: { + 'platform': 'rflink', + 'devices': { + 'RTS_0100F2_0': { + 'name': 'test', + 'aliases': ['test_alias_0_0'], + }, + }, + }, + } + + # setup mocking rflink module + _, _, protocol, _ = yield from mock_rflink( + hass, config, domain, monkeypatch) + + hass.async_add_job( + hass.services.async_call(domain, SERVICE_STOP_COVER, + {'device_id': 'RTS_0100F2_0', + 'command': 'STOP'})) + yield from hass.async_block_till_done() + assert protocol.send_command.call_args_list[0][0][0] == 'RTS_0100F2_0' + assert protocol.send_command.call_args_list[0][0][1] == 'STOP' + + @asyncio.coroutine def test_send_command(hass, monkeypatch): """Test send_command service.""" @@ -266,4 +300,4 @@ def test_error_when_not_connected(hass, monkeypatch): success = yield from hass.services.async_call( domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: 'switch.test'}) - assert not success, 'changing state should not succeed when disconnected' + assert not success, 'changing state should not succeed when disconnected' \ No newline at end of file From 7acd2d06434450a6f47384b4b7fdcb6c67c2d980 Mon Sep 17 00:00:00 2001 From: Pascal Osinga Date: Thu, 14 Sep 2017 21:56:08 +0200 Subject: [PATCH 02/11] no newline at end of file --- tests/components/test_rflink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/test_rflink.py b/tests/components/test_rflink.py index 52ac6643a949e5..c8a78b2048c710 100644 --- a/tests/components/test_rflink.py +++ b/tests/components/test_rflink.py @@ -300,4 +300,4 @@ def test_error_when_not_connected(hass, monkeypatch): success = yield from hass.services.async_call( domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: 'switch.test'}) - assert not success, 'changing state should not succeed when disconnected' \ No newline at end of file + assert not success, 'changing state should not succeed when disconnected' From 07e93cbd2ee1fdb8263ad10992be7d27070eb8cb Mon Sep 17 00:00:00 2001 From: Pascal Osinga Date: Fri, 15 Sep 2017 10:25:03 +0200 Subject: [PATCH 03/11] changed entity --- tests/components/test_rflink.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/components/test_rflink.py b/tests/components/test_rflink.py index c8a78b2048c710..e7907fc6b54726 100644 --- a/tests/components/test_rflink.py +++ b/tests/components/test_rflink.py @@ -146,8 +146,7 @@ def test_cover_send_no_wait(hass, monkeypatch): hass.async_add_job( hass.services.async_call(domain, SERVICE_STOP_COVER, - {'device_id': 'RTS_0100F2_0', - 'command': 'STOP'})) + {ATTR_ENTITY_ID: 'cover.test'})) yield from hass.async_block_till_done() assert protocol.send_command.call_args_list[0][0][0] == 'RTS_0100F2_0' assert protocol.send_command.call_args_list[0][0][1] == 'STOP' From 20342f7a989211d41f0198143209eecc482acc00 Mon Sep 17 00:00:00 2001 From: Pascal Osinga Date: Tue, 19 Sep 2017 20:52:18 +0200 Subject: [PATCH 04/11] fixed comments from pvizeli --- homeassistant/components/rflink.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 8493972f70cd99..31ddca870c7fae 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -11,7 +11,6 @@ import os import async_timeout -import voluptuous as vol from homeassistant.config import load_yaml_config_file from homeassistant.const import ( @@ -20,8 +19,10 @@ from homeassistant.core import CoreState, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.deprecation import get_deprecated +from homeassistant.helpers.entity import Entity +import voluptuous as vol + REQUIREMENTS = ['rflink==0.0.34'] @@ -99,6 +100,7 @@ def identify_event_type(event): return EVENT_KEY_COMMAND elif EVENT_KEY_SENSOR in event: return EVENT_KEY_SENSOR + return: 'unknown' else: return 'unknown' @@ -274,7 +276,7 @@ def handle_event(self, event): self._handle_event(event) # Propagate changes through ha - self.hass.async_add_job(self.async_update_ha_state()) + self.async_schedule_update_ha_state() # Put command onto bus for user to subscribe to if self._should_fire_event and identify_event_type( From 321d0aae0ab2582cd66545015472c0bfc765c03c Mon Sep 17 00:00:00 2001 From: Pascal Osinga Date: Tue, 19 Sep 2017 20:53:14 +0200 Subject: [PATCH 05/11] removed : --- homeassistant/components/rflink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 31ddca870c7fae..6b02c1667e0622 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -100,7 +100,7 @@ def identify_event_type(event): return EVENT_KEY_COMMAND elif EVENT_KEY_SENSOR in event: return EVENT_KEY_SENSOR - return: 'unknown' + return 'unknown' else: return 'unknown' From f0e0334024ac81914bafce651b0a5363d9e87fa5 Mon Sep 17 00:00:00 2001 From: Pascal Osinga Date: Tue, 19 Sep 2017 20:54:48 +0200 Subject: [PATCH 06/11] removed return 'unknown' --- homeassistant/components/rflink.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 6b02c1667e0622..ce536637166091 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -100,7 +100,6 @@ def identify_event_type(event): return EVENT_KEY_COMMAND elif EVENT_KEY_SENSOR in event: return EVENT_KEY_SENSOR - return 'unknown' else: return 'unknown' From 53eaa2135d18d6139d397bc1ed2e7bfc025396d2 Mon Sep 17 00:00:00 2001 From: Pascal Date: Thu, 28 Sep 2017 12:14:17 +0200 Subject: [PATCH 07/11] Fixed comments from Rytilahti --- homeassistant/components/cover/rflink.py | 5 +++-- homeassistant/components/rflink.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cover/rflink.py b/homeassistant/components/cover/rflink.py index d5fe0f616f7f91..8fc321e38f4d88 100644 --- a/homeassistant/components/cover/rflink.py +++ b/homeassistant/components/cover/rflink.py @@ -6,7 +6,6 @@ """ import asyncio import logging - import voluptuous as vol from homeassistant.components.rflink import ( @@ -16,6 +15,8 @@ CoverDevice, PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_NAME + + DEPENDENCIES = ['rflink'] _LOGGER = logging.getLogger(__name__) @@ -104,7 +105,7 @@ def should_poll(self): @property def is_closed(self): """Return if the cover is closed.""" - return None + return False def async_close_cover(self, **kwargs): """Turn the device close.""" diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index ce536637166091..886b658a9068e9 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -376,9 +376,11 @@ def _async_handle_command(self, command, *args): # Cover options for RFlink elif command == 'close_cover': cmd = 'DOWN' + self._state = False elif command == 'open_cover': cmd = 'UP' + self._state = True elif command == 'stop_cover': cmd = 'STOP' From 23bb1b2855e6ca9f0dba0afad2659d0eb33a3952 Mon Sep 17 00:00:00 2001 From: Pascal Date: Thu, 28 Sep 2017 12:15:47 +0200 Subject: [PATCH 08/11] removed newline --- homeassistant/components/rflink.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 886b658a9068e9..fe09439311fb30 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -9,7 +9,6 @@ import functools as ft import logging import os - import async_timeout from homeassistant.config import load_yaml_config_file From 085d9992839ddf17fd151da440aee63d4eb73774 Mon Sep 17 00:00:00 2001 From: Pascal Osinga Date: Thu, 28 Sep 2017 22:17:50 +0200 Subject: [PATCH 09/11] Reverted to None --- homeassistant/components/cover/rflink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/cover/rflink.py b/homeassistant/components/cover/rflink.py index 8fc321e38f4d88..61d54e992a5adb 100644 --- a/homeassistant/components/cover/rflink.py +++ b/homeassistant/components/cover/rflink.py @@ -105,7 +105,7 @@ def should_poll(self): @property def is_closed(self): """Return if the cover is closed.""" - return False + return None def async_close_cover(self, **kwargs): """Turn the device close.""" From 56c65649af480ba089b6ddeac7762678f365553a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 29 Sep 2017 00:27:41 +0200 Subject: [PATCH 10/11] cleanup --- homeassistant/components/cover/rflink.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/cover/rflink.py b/homeassistant/components/cover/rflink.py index 61d54e992a5adb..45a0b27aa0756a 100644 --- a/homeassistant/components/cover/rflink.py +++ b/homeassistant/components/cover/rflink.py @@ -6,6 +6,7 @@ """ import asyncio import logging + import voluptuous as vol from homeassistant.components.rflink import ( @@ -102,11 +103,6 @@ def should_poll(self): """No polling available in RFlink cover.""" return False - @property - def is_closed(self): - """Return if the cover is closed.""" - return None - def async_close_cover(self, **kwargs): """Turn the device close.""" return self._async_handle_command("close_cover") From 89a85521a56cb5e5d8fd8b2c6bd4ab264ed50478 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 29 Sep 2017 00:29:36 +0200 Subject: [PATCH 11/11] Cleanup --- homeassistant/components/rflink.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index fe09439311fb30..5045017790ec26 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -9,6 +9,7 @@ import functools as ft import logging import os + import async_timeout from homeassistant.config import load_yaml_config_file @@ -99,8 +100,7 @@ def identify_event_type(event): return EVENT_KEY_COMMAND elif EVENT_KEY_SENSOR in event: return EVENT_KEY_SENSOR - else: - return 'unknown' + return 'unknown' @asyncio.coroutine