RFLink: Add send_command service (#8876)
Add an optional extended description…
This commit is contained in:
parent
3aceca9d8a
commit
62e86270e6
3 changed files with 106 additions and 5 deletions
|
@ -8,11 +8,13 @@ import asyncio
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import functools as ft
|
import functools as ft
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
from homeassistant.config import load_yaml_config_file
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP,
|
ATTR_ENTITY_ID, CONF_COMMAND, CONF_HOST, CONF_PORT,
|
||||||
STATE_UNKNOWN)
|
EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN)
|
||||||
from homeassistant.core import CoreState, callback
|
from homeassistant.core import CoreState, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
@ -35,6 +37,7 @@ CONF_GROUP = 'group'
|
||||||
CONF_NOGROUP_ALIASES = 'nogroup_aliases'
|
CONF_NOGROUP_ALIASES = 'nogroup_aliases'
|
||||||
CONF_NOGROUP_ALIASSES = 'nogroup_aliasses'
|
CONF_NOGROUP_ALIASSES = 'nogroup_aliasses'
|
||||||
CONF_DEVICE_DEFAULTS = 'device_defaults'
|
CONF_DEVICE_DEFAULTS = 'device_defaults'
|
||||||
|
CONF_DEVICE_ID = 'device_id'
|
||||||
CONF_DEVICES = 'devices'
|
CONF_DEVICES = 'devices'
|
||||||
CONF_AUTOMATIC_ADD = 'automatic_add'
|
CONF_AUTOMATIC_ADD = 'automatic_add'
|
||||||
CONF_FIRE_EVENT = 'fire_event'
|
CONF_FIRE_EVENT = 'fire_event'
|
||||||
|
@ -60,6 +63,8 @@ RFLINK_GROUP_COMMANDS = ['allon', 'alloff']
|
||||||
|
|
||||||
DOMAIN = 'rflink'
|
DOMAIN = 'rflink'
|
||||||
|
|
||||||
|
SERVICE_SEND_COMMAND = 'send_command'
|
||||||
|
|
||||||
DEVICE_DEFAULTS_SCHEMA = vol.Schema({
|
DEVICE_DEFAULTS_SCHEMA = vol.Schema({
|
||||||
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
|
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
|
||||||
vol.Optional(CONF_SIGNAL_REPETITIONS,
|
vol.Optional(CONF_SIGNAL_REPETITIONS,
|
||||||
|
@ -78,6 +83,11 @@ CONFIG_SCHEMA = vol.Schema({
|
||||||
}),
|
}),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, 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):
|
def identify_event_type(event):
|
||||||
"""Look at event to determine type of device.
|
"""Look at event to determine type of device.
|
||||||
|
@ -111,6 +121,24 @@ def async_setup(hass, config):
|
||||||
# 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] = {}
|
||||||
|
|
||||||
|
@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
|
@callback
|
||||||
def event_callback(event):
|
def event_callback(event):
|
||||||
"""Handle incoming Rflink events.
|
"""Handle incoming Rflink events.
|
||||||
|
@ -312,6 +340,12 @@ class RflinkCommand(RflinkDevice):
|
||||||
"""Return connection status."""
|
"""Return connection status."""
|
||||||
return bool(cls._protocol)
|
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
|
@asyncio.coroutine
|
||||||
def _async_handle_command(self, command, *args):
|
def _async_handle_command(self, command, *args):
|
||||||
"""Do bookkeeping for command, send it to rflink and update state."""
|
"""Do bookkeeping for command, send it to rflink and update state."""
|
||||||
|
|
|
@ -533,3 +533,16 @@ knx:
|
||||||
data:
|
data:
|
||||||
description: KNX data to send
|
description: KNX data to send
|
||||||
example: 1
|
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'
|
||||||
|
|
|
@ -4,13 +4,15 @@ import asyncio
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from homeassistant.bootstrap import async_setup_component
|
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 homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF
|
||||||
from tests.common import assert_setup_component
|
from tests.common import assert_setup_component
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@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."""
|
"""Create mock Rflink asyncio protocol, test component setup."""
|
||||||
transport, protocol = (Mock(), Mock())
|
transport, protocol = (Mock(), Mock())
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ def mock_rflink(hass, config, domain, monkeypatch, failures=None):
|
||||||
mock_create)
|
mock_create)
|
||||||
|
|
||||||
# verify instanstiation of component with given config
|
# 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)
|
yield from async_setup_component(hass, domain, config)
|
||||||
|
|
||||||
# hook into mock config for injecting events
|
# hook into mock config for injecting events
|
||||||
|
@ -117,6 +119,58 @@ def test_send_no_wait(hass, monkeypatch):
|
||||||
assert protocol.send_command.call_args_list[0][0][1] == 'off'
|
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
|
@asyncio.coroutine
|
||||||
def test_reconnecting_after_disconnect(hass, monkeypatch):
|
def test_reconnecting_after_disconnect(hass, monkeypatch):
|
||||||
"""An unexpected disconnect should cause a reconnect."""
|
"""An unexpected disconnect should cause a reconnect."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue