Rflink group commands (#5969)

* Add support for group commands (allon/alloff).
Add 'group_aliasses' config attribute that only respond to group commands.
Add nogroup_aliases that only respond to 'on' 'off' commands.
Allow settings device id group behaviour.

* Fix linting.

* Fix lint.
This commit is contained in:
Johan Bloemberg 2017-03-29 07:04:25 +02:00 committed by Paulus Schoutsen
parent 63c15e997a
commit e1ed076015
4 changed files with 200 additions and 17 deletions

View File

@ -11,11 +11,13 @@ from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
from homeassistant.components.rflink import ( from homeassistant.components.rflink import (
CONF_ALIASSES, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT, CONF_ALIASSES, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT,
CONF_IGNORE_DEVICES, CONF_SIGNAL_REPETITIONS, DATA_DEVICE_REGISTER, CONF_GROUP, CONF_GROUP_ALIASSES, CONF_IGNORE_DEVICES,
DATA_ENTITY_LOOKUP, DEVICE_DEFAULTS_SCHEMA, DOMAIN, CONF_NOGROUP_ALIASSES, CONF_SIGNAL_REPETITIONS, DATA_DEVICE_REGISTER,
EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, cv, vol) DATA_ENTITY_GROUP_LOOKUP, DATA_ENTITY_LOOKUP, DEVICE_DEFAULTS_SCHEMA,
DOMAIN, EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, cv, vol)
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_PLATFORM, CONF_TYPE, STATE_UNKNOWN) CONF_NAME, CONF_PLATFORM, CONF_TYPE, STATE_UNKNOWN)
DEPENDENCIES = ['rflink'] DEPENDENCIES = ['rflink']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -38,8 +40,13 @@ PLATFORM_SCHEMA = vol.Schema({
TYPE_HYBRID, TYPE_TOGGLE), TYPE_HYBRID, TYPE_TOGGLE),
vol.Optional(CONF_ALIASSES, default=[]): vol.Optional(CONF_ALIASSES, default=[]):
vol.All(cv.ensure_list, [cv.string]), vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_GROUP_ALIASSES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_NOGROUP_ALIASSES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int), vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int),
vol.Optional(CONF_GROUP, default=True): cv.boolean,
}, },
}), }),
}) })
@ -110,7 +117,24 @@ def devices_from_config(domain_config, hass=None):
devices.append(device) devices.append(device)
# Register entity (and aliasses) to listen to incoming rflink events # Register entity (and aliasses) to listen to incoming rflink events
for _id in [device_id] + config[CONF_ALIASSES]:
# device id and normal aliasses 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_ALIASSES]:
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
# group_aliasses only respond to group commands
for _id in config[CONF_GROUP_ALIASSES]:
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
# nogroup_aliasses only respond to normal commands
for _id in config[CONF_NOGROUP_ALIASSES]:
hass.data[DATA_ENTITY_LOOKUP][ hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device) EVENT_KEY_COMMAND][_id].append(device)

View File

@ -20,7 +20,7 @@ from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['rflink==0.0.28'] REQUIREMENTS = ['rflink==0.0.31']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,6 +28,9 @@ ATTR_EVENT = 'event'
ATTR_STATE = 'state' ATTR_STATE = 'state'
CONF_ALIASSES = 'aliasses' CONF_ALIASSES = 'aliasses'
CONF_GROUP_ALIASSES = 'group_aliasses'
CONF_GROUP = 'group'
CONF_NOGROUP_ALIASSES = 'nogroup_aliasses'
CONF_DEVICE_DEFAULTS = 'device_defaults' CONF_DEVICE_DEFAULTS = 'device_defaults'
CONF_DEVICES = 'devices' CONF_DEVICES = 'devices'
CONF_FIRE_EVENT = 'fire_event' CONF_FIRE_EVENT = 'fire_event'
@ -38,6 +41,7 @@ CONF_WAIT_FOR_ACK = 'wait_for_ack'
DATA_DEVICE_REGISTER = 'rflink_device_register' DATA_DEVICE_REGISTER = 'rflink_device_register'
DATA_ENTITY_LOOKUP = 'rflink_entity_lookup' DATA_ENTITY_LOOKUP = 'rflink_entity_lookup'
DATA_ENTITY_GROUP_LOOKUP = 'rflink_entity_group_only_lookup'
DEFAULT_RECONNECT_INTERVAL = 10 DEFAULT_RECONNECT_INTERVAL = 10
DEFAULT_SIGNAL_REPETITIONS = 1 DEFAULT_SIGNAL_REPETITIONS = 1
CONNECTION_TIMEOUT = 10 CONNECTION_TIMEOUT = 10
@ -48,6 +52,8 @@ EVENT_KEY_ID = 'id'
EVENT_KEY_SENSOR = 'sensor' EVENT_KEY_SENSOR = 'sensor'
EVENT_KEY_UNIT = 'unit' EVENT_KEY_UNIT = 'unit'
RFLINK_GROUP_COMMANDS = ['allon', 'alloff']
DOMAIN = 'rflink' DOMAIN = 'rflink'
DEVICE_DEFAULTS_SCHEMA = vol.Schema({ DEVICE_DEFAULTS_SCHEMA = vol.Schema({
@ -94,6 +100,10 @@ def async_setup(hass, config):
EVENT_KEY_COMMAND: defaultdict(list), EVENT_KEY_COMMAND: defaultdict(list),
EVENT_KEY_SENSOR: defaultdict(list), EVENT_KEY_SENSOR: defaultdict(list),
} }
hass.data[DATA_ENTITY_GROUP_LOOKUP] = {
EVENT_KEY_COMMAND: defaultdict(list),
EVENT_KEY_SENSOR: defaultdict(list),
}
# Allow platform to specify function to register new unknown devices # Allow platform to specify function to register new unknown devices
hass.data[DATA_DEVICE_REGISTER] = {} hass.data[DATA_DEVICE_REGISTER] = {}
@ -116,7 +126,14 @@ def async_setup(hass, config):
# Lookup entities who registered this device id as device id or alias # Lookup entities who registered this device id as device id or alias
event_id = event.get('id', None) event_id = event.get('id', None)
entities = hass.data[DATA_ENTITY_LOOKUP][event_type][event_id]
is_group_event = (event_type == EVENT_KEY_COMMAND and
event[EVENT_KEY_COMMAND] in RFLINK_GROUP_COMMANDS)
if is_group_event:
entities = hass.data[DATA_ENTITY_GROUP_LOOKUP][event_type].get(
event_id, [])
else:
entities = hass.data[DATA_ENTITY_LOOKUP][event_type][event_id]
if entities: if entities:
# Propagate event to every entity matching the device id # Propagate event to every entity matching the device id
@ -202,8 +219,8 @@ class RflinkDevice(Entity):
platform = None platform = None
_state = STATE_UNKNOWN _state = STATE_UNKNOWN
def __init__(self, device_id, hass, name=None, def __init__(self, device_id, hass, name=None, aliasses=None, group=True,
aliasses=None, fire_event=False, group_aliasses=None, nogroup_aliasses=None, fire_event=False,
signal_repetitions=DEFAULT_SIGNAL_REPETITIONS): signal_repetitions=DEFAULT_SIGNAL_REPETITIONS):
"""Initialize the device.""" """Initialize the device."""
self.hass = hass self.hass = hass
@ -215,12 +232,6 @@ class RflinkDevice(Entity):
else: else:
self._name = device_id self._name = device_id
# Generate list of device_ids to match against
if aliasses:
self._aliasses = aliasses
else:
self._aliasses = []
self._should_fire_event = fire_event self._should_fire_event = fire_event
self._signal_repetitions = signal_repetitions self._signal_repetitions = signal_repetitions
@ -375,9 +386,9 @@ class SwitchableRflinkDevice(RflinkCommand):
self.cancel_queued_send_commands() self.cancel_queued_send_commands()
command = event['command'] command = event['command']
if command == 'on': if command in ['on', 'allon']:
self._state = True self._state = True
elif command == 'off': elif command in ['off', 'alloff']:
self._state = False self._state = False
def async_turn_on(self, **kwargs): def async_turn_on(self, **kwargs):

View File

@ -659,7 +659,7 @@ qnapstats==0.2.3
radiotherm==1.2 radiotherm==1.2
# homeassistant.components.rflink # homeassistant.components.rflink
rflink==0.0.28 rflink==0.0.31
# homeassistant.components.sensor.ring # homeassistant.components.sensor.ring
ring_doorbell==0.1.0 ring_doorbell==0.1.0

View File

@ -81,6 +81,25 @@ def test_default_setup(hass, monkeypatch):
assert hass.states.get('light.test').state == 'off' assert hass.states.get('light.test').state == 'off'
# should repond to group command
event_callback({
'id': 'protocol_0_0',
'command': 'allon',
})
yield from hass.async_block_till_done()
light_after_first_command = hass.states.get('light.test')
assert light_after_first_command.state == 'on'
# should repond to group command
event_callback({
'id': 'protocol_0_0',
'command': 'alloff',
})
yield from hass.async_block_till_done()
assert hass.states.get('light.test').state == 'off'
# test following aliasses # test following aliasses
# mock incoming command event for this device alias # mock incoming command event for this device alias
event_callback({ event_callback({
@ -385,3 +404,132 @@ def test_type_toggle(hass, monkeypatch):
yield from hass.async_block_till_done() yield from hass.async_block_till_done()
assert hass.states.get('light.toggle_test').state == 'off' assert hass.states.get('light.toggle_test').state == 'off'
@asyncio.coroutine
def test_group_alias(hass, monkeypatch):
"""Group aliases should only respond to group commands (allon/alloff)."""
config = {
'rflink': {
'port': '/dev/ttyABC0',
},
DOMAIN: {
'platform': 'rflink',
'devices': {
'protocol_0_0': {
'name': 'test',
'group_aliasses': ['test_group_0_0'],
},
},
},
}
# setup mocking rflink module
event_callback, _, _, _ = yield from mock_rflink(
hass, config, DOMAIN, monkeypatch)
assert hass.states.get('light.test').state == 'off'
# test sending group command to group alias
event_callback({
'id': 'test_group_0_0',
'command': 'allon',
})
yield from hass.async_block_till_done()
assert hass.states.get('light.test').state == 'on'
# test sending group command to group alias
event_callback({
'id': 'test_group_0_0',
'command': 'off',
})
yield from hass.async_block_till_done()
assert hass.states.get('light.test').state == 'on'
@asyncio.coroutine
def test_nogroup_alias(hass, monkeypatch):
"""Non group aliases should not respond to group commands."""
config = {
'rflink': {
'port': '/dev/ttyABC0',
},
DOMAIN: {
'platform': 'rflink',
'devices': {
'protocol_0_0': {
'name': 'test',
'nogroup_aliasses': ['test_nogroup_0_0'],
},
},
},
}
# setup mocking rflink module
event_callback, _, _, _ = yield from mock_rflink(
hass, config, DOMAIN, monkeypatch)
assert hass.states.get('light.test').state == 'off'
# test sending group command to nogroup alias
event_callback({
'id': 'test_nogroup_0_0',
'command': 'allon',
})
yield from hass.async_block_till_done()
# should not affect state
assert hass.states.get('light.test').state == 'off'
# test sending group command to nogroup alias
event_callback({
'id': 'test_nogroup_0_0',
'command': 'on',
})
yield from hass.async_block_till_done()
# should affect state
assert hass.states.get('light.test').state == 'on'
@asyncio.coroutine
def test_nogroup_device_id(hass, monkeypatch):
"""Device id that do not respond to group commands (allon/alloff)."""
config = {
'rflink': {
'port': '/dev/ttyABC0',
},
DOMAIN: {
'platform': 'rflink',
'devices': {
'test_nogroup_0_0': {
'name': 'test',
'group': False,
},
},
},
}
# setup mocking rflink module
event_callback, _, _, _ = yield from mock_rflink(
hass, config, DOMAIN, monkeypatch)
assert hass.states.get('light.test').state == 'off'
# test sending group command to nogroup
event_callback({
'id': 'test_nogroup_0_0',
'command': 'allon',
})
yield from hass.async_block_till_done()
# should not affect state
assert hass.states.get('light.test').state == 'off'
# test sending group command to nogroup
event_callback({
'id': 'test_nogroup_0_0',
'command': 'on',
})
yield from hass.async_block_till_done()
# should affect state
assert hass.states.get('light.test').state == 'on'