Skip to content

Commit

Permalink
RFLink: Add send_command service (#8876)
Browse files Browse the repository at this point in the history
Add an optional extended description…
  • Loading branch information
leppa authored and pvizeli committed Aug 7, 2017
1 parent 3aceca9 commit 62e8627
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 5 deletions.
38 changes: 36 additions & 2 deletions homeassistant/components/rflink.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
from collections import defaultdict
import functools as ft
import logging
import os

import async_timeout
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP,
STATE_UNKNOWN)
ATTR_ENTITY_ID, CONF_COMMAND, CONF_HOST, CONF_PORT,
EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN)
from homeassistant.core import CoreState, callback
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
Expand All @@ -35,6 +37,7 @@
CONF_NOGROUP_ALIASES = 'nogroup_aliases'
CONF_NOGROUP_ALIASSES = 'nogroup_aliasses'
CONF_DEVICE_DEFAULTS = 'device_defaults'
CONF_DEVICE_ID = 'device_id'
CONF_DEVICES = 'devices'
CONF_AUTOMATIC_ADD = 'automatic_add'
CONF_FIRE_EVENT = 'fire_event'
Expand All @@ -60,6 +63,8 @@

DOMAIN = 'rflink'

SERVICE_SEND_COMMAND = 'send_command'

DEVICE_DEFAULTS_SCHEMA = vol.Schema({
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_SIGNAL_REPETITIONS,
Expand All @@ -78,6 +83,11 @@
}),
}, extra=vol.ALLOW_EXTRA)

SEND_COMMAND_SCHEMA = vol.Schema({
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(CONF_COMMAND): cv.string,
})


def identify_event_type(event):
"""Look at event to determine type of device.
Expand Down Expand Up @@ -111,6 +121,24 @@ def async_setup(hass, config):
# Allow platform to specify function to register new unknown devices
hass.data[DATA_DEVICE_REGISTER] = {}

@asyncio.coroutine
def async_send_command(call):
"""Send Rflink command."""
_LOGGER.debug('Rflink command for %s', str(call.data))
if not (yield from RflinkCommand.send_command(
call.data.get(CONF_DEVICE_ID),
call.data.get(CONF_COMMAND))):
_LOGGER.error('Failed Rflink command for %s', str(call.data))

descriptions = yield from hass.async_add_job(
load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml')
)

hass.services.async_register(
DOMAIN, SERVICE_SEND_COMMAND, async_send_command,
descriptions[DOMAIN][SERVICE_SEND_COMMAND], SEND_COMMAND_SCHEMA)

@callback
def event_callback(event):
"""Handle incoming Rflink events.
Expand Down Expand Up @@ -312,6 +340,12 @@ def is_connected(cls):
"""Return connection status."""
return bool(cls._protocol)

@classmethod
@asyncio.coroutine
def send_command(cls, device_id, action):
"""Send device command to Rflink and wait for acknowledgement."""
return (yield from cls._protocol.send_command_ack(device_id, action))

@asyncio.coroutine
def _async_handle_command(self, command, *args):
"""Do bookkeeping for command, send it to rflink and update state."""
Expand Down
13 changes: 13 additions & 0 deletions homeassistant/components/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,16 @@ knx:
data:
description: KNX data to send
example: 1

rflink:
send_command:
description: Send device command through RFLink

fields:
device_id:
description: RFLink device ID
example: 'newkaku_0000c6c2_1'

command:
description: The command to be sent
example: 'on'
60 changes: 57 additions & 3 deletions tests/components/test_rflink.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
from unittest.mock import Mock

from homeassistant.bootstrap import async_setup_component
from homeassistant.components.rflink import CONF_RECONNECT_INTERVAL
from homeassistant.components.rflink import (
CONF_RECONNECT_INTERVAL, SERVICE_SEND_COMMAND)
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF
from tests.common import assert_setup_component


@asyncio.coroutine
def mock_rflink(hass, config, domain, monkeypatch, failures=None):
def mock_rflink(hass, config, domain, monkeypatch, failures=None,
platform_count=1):
"""Create mock Rflink asyncio protocol, test component setup."""
transport, protocol = (Mock(), Mock())

Expand Down Expand Up @@ -45,7 +47,7 @@ def create_rflink_connection(*args, **kwargs):
mock_create)

# verify instanstiation of component with given config
with assert_setup_component(1, domain):
with assert_setup_component(platform_count, domain):
yield from async_setup_component(hass, domain, config)

# hook into mock config for injecting events
Expand Down Expand Up @@ -117,6 +119,58 @@ def test_send_no_wait(hass, monkeypatch):
assert protocol.send_command.call_args_list[0][0][1] == 'off'


@asyncio.coroutine
def test_send_command(hass, monkeypatch):
"""Test send_command service."""
domain = 'rflink'
config = {
'rflink': {
'port': '/dev/ttyABC0',
},
}

# setup mocking rflink module
_, _, protocol, _ = yield from mock_rflink(
hass, config, domain, monkeypatch, platform_count=5)

hass.async_add_job(
hass.services.async_call(domain, SERVICE_SEND_COMMAND,
{'device_id': 'newkaku_0000c6c2_1',
'command': 'on'}))
yield from hass.async_block_till_done()
assert (protocol.send_command_ack.call_args_list[0][0][0]
== 'newkaku_0000c6c2_1')
assert protocol.send_command_ack.call_args_list[0][0][1] == 'on'


@asyncio.coroutine
def test_send_command_invalid_arguments(hass, monkeypatch):
"""Test send_command service."""
domain = 'rflink'
config = {
'rflink': {
'port': '/dev/ttyABC0',
},
}

# setup mocking rflink module
_, _, protocol, _ = yield from mock_rflink(
hass, config, domain, monkeypatch, platform_count=5)

# one argument missing
hass.async_add_job(
hass.services.async_call(domain, SERVICE_SEND_COMMAND,
{'command': 'on'}))
hass.async_add_job(
hass.services.async_call(domain, SERVICE_SEND_COMMAND,
{'device_id': 'newkaku_0000c6c2_1'}))
# no arguments
hass.async_add_job(
hass.services.async_call(domain, SERVICE_SEND_COMMAND, {}))
yield from hass.async_block_till_done()
assert protocol.send_command_ack.call_args_list == []


@asyncio.coroutine
def test_reconnecting_after_disconnect(hass, monkeypatch):
"""An unexpected disconnect should cause a reconnect."""
Expand Down

0 comments on commit 62e8627

Please sign in to comment.