mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Add support for learning new commands (#23888)
* Add support for learning new commands This update creates a generic service in the 'remote' component to enable remote control platforms to learn new commands. * Update __init__.py with the proposed changes - Add 'supported_features' property and a constant related to the 'learn_command' functionality. - Redefine 'async_learn_command' function as a coroutine. * Update __init__.py * Fix assertion error Adding the 'supported_features' attribute generated an assertion error on the 'Demo Remote' platform. This update fixes this. * Fix duplicated 'hass' object This update fixes a typo that occurred at the last update.
This commit is contained in:
parent
408ae44bdd
commit
0ed9e185b2
@ -24,6 +24,8 @@ ATTR_DEVICE = 'device'
|
||||
ATTR_NUM_REPEATS = 'num_repeats'
|
||||
ATTR_DELAY_SECS = 'delay_secs'
|
||||
ATTR_HOLD_SECS = 'hold_secs'
|
||||
ATTR_ALTERNATIVE = 'alternative'
|
||||
ATTR_TIMEOUT = 'timeout'
|
||||
|
||||
DOMAIN = 'remote'
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
@ -36,12 +38,15 @@ GROUP_NAME_ALL_REMOTES = 'all remotes'
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
||||
SERVICE_SEND_COMMAND = 'send_command'
|
||||
SERVICE_LEARN_COMMAND = 'learn_command'
|
||||
SERVICE_SYNC = 'sync'
|
||||
|
||||
DEFAULT_NUM_REPEATS = 1
|
||||
DEFAULT_DELAY_SECS = 0.4
|
||||
DEFAULT_HOLD_SECS = 0
|
||||
|
||||
SUPPORT_LEARN_COMMAND = 1
|
||||
|
||||
REMOTE_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
|
||||
})
|
||||
@ -59,6 +64,13 @@ REMOTE_SERVICE_SEND_COMMAND_SCHEMA = REMOTE_SERVICE_SCHEMA.extend({
|
||||
vol.Optional(ATTR_HOLD_SECS, default=DEFAULT_HOLD_SECS): vol.Coerce(float),
|
||||
})
|
||||
|
||||
REMOTE_SERVICE_LEARN_COMMAND_SCHEMA = REMOTE_SERVICE_SCHEMA.extend({
|
||||
vol.Optional(ATTR_DEVICE): cv.string,
|
||||
vol.Optional(ATTR_COMMAND): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(ATTR_ALTERNATIVE): cv.boolean,
|
||||
vol.Optional(ATTR_TIMEOUT): cv.positive_int
|
||||
})
|
||||
|
||||
|
||||
@bind_hass
|
||||
def is_on(hass, entity_id=None):
|
||||
@ -93,12 +105,22 @@ async def async_setup(hass, config):
|
||||
'async_send_command'
|
||||
)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_LEARN_COMMAND, REMOTE_SERVICE_LEARN_COMMAND_SCHEMA,
|
||||
'async_learn_command'
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class RemoteDevice(ToggleEntity):
|
||||
"""Representation of a remote."""
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return 0
|
||||
|
||||
def send_command(self, command, **kwargs):
|
||||
"""Send a command to a device."""
|
||||
raise NotImplementedError()
|
||||
@ -108,5 +130,17 @@ class RemoteDevice(ToggleEntity):
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(ft.partial(
|
||||
self.send_command, command, **kwargs))
|
||||
return self.hass.async_add_executor_job(
|
||||
ft.partial(self.send_command, command, **kwargs))
|
||||
|
||||
def learn_command(self, **kwargs):
|
||||
"""Learn a command from a device."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_learn_command(self, **kwargs):
|
||||
"""Learn a command from a device.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_executor_job(
|
||||
ft.partial(self.learn_command, **kwargs))
|
||||
|
@ -25,7 +25,7 @@ turn_off:
|
||||
example: 'remote.family_room'
|
||||
|
||||
send_command:
|
||||
description: Sends a single command to a single device.
|
||||
description: Sends a command or a list of commands to a device.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to send command from.
|
||||
@ -46,6 +46,25 @@ send_command:
|
||||
description: An optional value that specifies that number of seconds you want to have it held before the release is send. If not specified, the release will be send immediately after the press.
|
||||
example: '2.5'
|
||||
|
||||
learn_command:
|
||||
description: Learns a command or a list of commands from a device.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entities to learn command from.
|
||||
example: 'remote.bedroom'
|
||||
device:
|
||||
description: Device ID to learn command from.
|
||||
example: 'television'
|
||||
command:
|
||||
description: A single command or a list of commands to learn.
|
||||
example: 'Turn on'
|
||||
alternative:
|
||||
description: If code must be stored as alternative (useful for discrete remotes).
|
||||
example: 'True'
|
||||
timeout:
|
||||
description: Timeout, in seconds, for the command to be learned.
|
||||
example: '30'
|
||||
|
||||
|
||||
harmony_sync:
|
||||
description: Syncs the remote's configuration.
|
||||
|
@ -48,5 +48,8 @@ class TestDemoRemote(unittest.TestCase):
|
||||
common.send_command(self.hass, 'test', entity_id=ENTITY_ID)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get(ENTITY_ID)
|
||||
assert state.attributes == \
|
||||
{'friendly_name': 'Remote One', 'last_command_sent': 'test'}
|
||||
assert state.attributes == {
|
||||
'friendly_name': 'Remote One',
|
||||
'last_command_sent': 'test',
|
||||
'supported_features': 0
|
||||
}
|
||||
|
@ -4,8 +4,9 @@ All containing methods are legacy helpers that should not be used by new
|
||||
components. Instead call the service directly.
|
||||
"""
|
||||
from homeassistant.components.remote import (
|
||||
ATTR_ACTIVITY, ATTR_COMMAND, ATTR_DELAY_SECS, ATTR_DEVICE,
|
||||
ATTR_NUM_REPEATS, DOMAIN, SERVICE_SEND_COMMAND)
|
||||
ATTR_ACTIVITY, ATTR_ALTERNATIVE, ATTR_COMMAND, ATTR_DELAY_SECS,
|
||||
ATTR_DEVICE, ATTR_NUM_REPEATS, ATTR_TIMEOUT, DOMAIN,
|
||||
SERVICE_LEARN_COMMAND, SERVICE_SEND_COMMAND)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
|
||||
from homeassistant.loader import bind_hass
|
||||
@ -53,3 +54,26 @@ def send_command(hass, command, entity_id=None, device=None,
|
||||
data[ATTR_DELAY_SECS] = delay_secs
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SEND_COMMAND, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def learn_command(hass, entity_id=None, device=None, command=None,
|
||||
alternative=None, timeout=None):
|
||||
"""Learn a command from a device."""
|
||||
data = {}
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
if device:
|
||||
data[ATTR_DEVICE] = device
|
||||
|
||||
if command:
|
||||
data[ATTR_COMMAND] = command
|
||||
|
||||
if alternative:
|
||||
data[ATTR_ALTERNATIVE] = alternative
|
||||
|
||||
if timeout:
|
||||
data[ATTR_TIMEOUT] = timeout
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_LEARN_COMMAND, data)
|
||||
|
@ -13,6 +13,7 @@ from tests.components.remote import common
|
||||
|
||||
TEST_PLATFORM = {remote.DOMAIN: {CONF_PLATFORM: 'test'}}
|
||||
SERVICE_SEND_COMMAND = 'send_command'
|
||||
SERVICE_LEARN_COMMAND = 'learn_command'
|
||||
|
||||
|
||||
class TestRemote(unittest.TestCase):
|
||||
@ -53,7 +54,7 @@ class TestRemote(unittest.TestCase):
|
||||
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert 1 == len(turn_on_calls)
|
||||
assert len(turn_on_calls) == 1
|
||||
call = turn_on_calls[-1]
|
||||
|
||||
assert remote.DOMAIN == call.domain
|
||||
@ -68,12 +69,12 @@ class TestRemote(unittest.TestCase):
|
||||
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert 1 == len(turn_off_calls)
|
||||
assert len(turn_off_calls) == 1
|
||||
call = turn_off_calls[-1]
|
||||
|
||||
assert remote.DOMAIN == call.domain
|
||||
assert SERVICE_TURN_OFF == call.service
|
||||
assert 'entity_id_val' == call.data[ATTR_ENTITY_ID]
|
||||
assert call.domain == remote.DOMAIN
|
||||
assert call.service == SERVICE_TURN_OFF
|
||||
assert call.data[ATTR_ENTITY_ID] == 'entity_id_val'
|
||||
|
||||
def test_send_command(self):
|
||||
"""Test send_command."""
|
||||
@ -87,9 +88,28 @@ class TestRemote(unittest.TestCase):
|
||||
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert 1 == len(send_command_calls)
|
||||
assert len(send_command_calls) == 1
|
||||
call = send_command_calls[-1]
|
||||
|
||||
assert remote.DOMAIN == call.domain
|
||||
assert SERVICE_SEND_COMMAND == call.service
|
||||
assert 'entity_id_val' == call.data[ATTR_ENTITY_ID]
|
||||
assert call.domain == remote.DOMAIN
|
||||
assert call.service == SERVICE_SEND_COMMAND
|
||||
assert call.data[ATTR_ENTITY_ID] == 'entity_id_val'
|
||||
|
||||
def test_learn_command(self):
|
||||
"""Test learn_command."""
|
||||
learn_command_calls = mock_service(
|
||||
self.hass, remote.DOMAIN, SERVICE_LEARN_COMMAND)
|
||||
|
||||
common.learn_command(
|
||||
self.hass, entity_id='entity_id_val',
|
||||
device='test_device', command=['test_command'],
|
||||
alternative=True, timeout=20)
|
||||
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert len(learn_command_calls) == 1
|
||||
call = learn_command_calls[-1]
|
||||
|
||||
assert call.domain == remote.DOMAIN
|
||||
assert call.service == SERVICE_LEARN_COMMAND
|
||||
assert call.data[ATTR_ENTITY_ID] == 'entity_id_val'
|
||||
|
Loading…
x
Reference in New Issue
Block a user