From 8a3279a5c9ac28a43da2832c8af52da13523c54f Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 7 Sep 2020 08:54:18 -0500 Subject: [PATCH] Add device action support for remotes (#39400) --- .../components/remote/device_action.py | 30 +++ .../components/remote/device_condition.py | 38 +++ .../components/remote/device_trigger.py | 38 +++ homeassistant/components/remote/strings.json | 15 ++ tests/components/remote/test_device_action.py | 143 +++++++++++ .../remote/test_device_condition.py | 235 ++++++++++++++++++ .../components/remote/test_device_trigger.py | 234 +++++++++++++++++ .../custom_components/test/remote.py | 39 +++ 8 files changed, 772 insertions(+) create mode 100644 homeassistant/components/remote/device_action.py create mode 100644 homeassistant/components/remote/device_condition.py create mode 100644 homeassistant/components/remote/device_trigger.py create mode 100644 tests/components/remote/test_device_action.py create mode 100644 tests/components/remote/test_device_condition.py create mode 100644 tests/components/remote/test_device_trigger.py create mode 100644 tests/testing_config/custom_components/test/remote.py diff --git a/homeassistant/components/remote/device_action.py b/homeassistant/components/remote/device_action.py new file mode 100644 index 00000000000..aa819f3eb46 --- /dev/null +++ b/homeassistant/components/remote/device_action.py @@ -0,0 +1,30 @@ +"""Provides device actions for remotes.""" +from typing import List + +import voluptuous as vol + +from homeassistant.components.device_automation import toggle_entity +from homeassistant.const import CONF_DOMAIN +from homeassistant.core import Context, HomeAssistant +from homeassistant.helpers.typing import ConfigType, TemplateVarsType + +from . import DOMAIN + +ACTION_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DOMAIN}) + + +async def async_call_action_from_config( + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context, +) -> None: + """Change state based on configuration.""" + await toggle_entity.async_call_action_from_config( + hass, config, variables, context, DOMAIN + ) + + +async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: + """List device actions.""" + return await toggle_entity.async_get_actions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/remote/device_condition.py b/homeassistant/components/remote/device_condition.py new file mode 100644 index 00000000000..06c7bec89d4 --- /dev/null +++ b/homeassistant/components/remote/device_condition.py @@ -0,0 +1,38 @@ +"""Provides device conditions for remotes.""" +from typing import Dict, List + +import voluptuous as vol + +from homeassistant.components.device_automation import toggle_entity +from homeassistant.const import CONF_DOMAIN +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.condition import ConditionCheckerType +from homeassistant.helpers.typing import ConfigType + +from . import DOMAIN + +CONDITION_SCHEMA = toggle_entity.CONDITION_SCHEMA.extend( + {vol.Required(CONF_DOMAIN): DOMAIN} +) + + +@callback +def async_condition_from_config( + config: ConfigType, config_validation: bool +) -> ConditionCheckerType: + """Evaluate state based on configuration.""" + if config_validation: + config = CONDITION_SCHEMA(config) + return toggle_entity.async_condition_from_config(config) + + +async def async_get_conditions( + hass: HomeAssistant, device_id: str +) -> List[Dict[str, str]]: + """List device conditions.""" + return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) + + +async def async_get_condition_capabilities(hass: HomeAssistant, config: dict) -> dict: + """List condition capabilities.""" + return await toggle_entity.async_get_condition_capabilities(hass, config) diff --git a/homeassistant/components/remote/device_trigger.py b/homeassistant/components/remote/device_trigger.py new file mode 100644 index 00000000000..5919e8c61ba --- /dev/null +++ b/homeassistant/components/remote/device_trigger.py @@ -0,0 +1,38 @@ +"""Provides device triggers for remotes.""" +from typing import List + +import voluptuous as vol + +from homeassistant.components.automation import AutomationActionType +from homeassistant.components.device_automation import toggle_entity +from homeassistant.const import CONF_DOMAIN +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers.typing import ConfigType + +from . import DOMAIN + +TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend( + {vol.Required(CONF_DOMAIN): DOMAIN} +) + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: dict, +) -> CALLBACK_TYPE: + """Listen for state changes based on configuration.""" + return await toggle_entity.async_attach_trigger( + hass, config, action, automation_info + ) + + +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: + """List device triggers.""" + return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) + + +async def async_get_trigger_capabilities(hass: HomeAssistant, config: dict) -> dict: + """List trigger capabilities.""" + return await toggle_entity.async_get_trigger_capabilities(hass, config) diff --git a/homeassistant/components/remote/strings.json b/homeassistant/components/remote/strings.json index eb9edcd450f..bb7df2e89ef 100644 --- a/homeassistant/components/remote/strings.json +++ b/homeassistant/components/remote/strings.json @@ -1,5 +1,20 @@ { "title": "Remote", + "device_automation": { + "action_type": { + "toggle": "Toggle {entity_name}", + "turn_on": "Turn on {entity_name}", + "turn_off": "Turn off {entity_name}" + }, + "condition_type": { + "is_on": "{entity_name} is on", + "is_off": "{entity_name} is off" + }, + "trigger_type": { + "turned_on": "{entity_name} turned on", + "turned_off": "{entity_name} turned off" + } + }, "state": { "_": { "off": "[%key:common::state::off%]", diff --git a/tests/components/remote/test_device_action.py b/tests/components/remote/test_device_action.py new file mode 100644 index 00000000000..89d0f0de6bf --- /dev/null +++ b/tests/components/remote/test_device_action.py @@ -0,0 +1,143 @@ +"""The test for remote device automation.""" +import pytest + +import homeassistant.components.automation as automation +from homeassistant.components.device_automation import ( + _async_get_device_automations as async_get_device_automations, +) +from homeassistant.components.remote import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON +from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component + +from tests.common import ( + MockConfigEntry, + async_mock_service, + mock_device_registry, + mock_registry, +) + + +@pytest.fixture +def device_reg(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +@pytest.fixture +def entity_reg(hass): + """Return an empty, loaded, registry.""" + return mock_registry(hass) + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_get_actions(hass, device_reg, entity_reg): + """Test we get the expected actions from a remote.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) + expected_actions = [ + { + "domain": DOMAIN, + "type": "turn_off", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + }, + { + "domain": DOMAIN, + "type": "turn_on", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + }, + { + "domain": DOMAIN, + "type": "toggle", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + }, + ] + actions = await async_get_device_automations(hass, "action", device_entry.id) + assert actions == expected_actions + + +async def test_action(hass, calls): + """Test for turn_on and turn_off actions.""" + platform = getattr(hass.components, f"test.{DOMAIN}") + + platform.init() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + ent1, ent2, ent3 = platform.ENTITIES + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": {"platform": "event", "event_type": "test_event1"}, + "action": { + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "turn_off", + }, + }, + { + "trigger": {"platform": "event", "event_type": "test_event2"}, + "action": { + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "turn_on", + }, + }, + { + "trigger": {"platform": "event", "event_type": "test_event3"}, + "action": { + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "toggle", + }, + }, + ] + }, + ) + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_ON + assert len(calls) == 0 + + hass.bus.async_fire("test_event1") + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_OFF + + hass.bus.async_fire("test_event1") + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_OFF + + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_ON + + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_ON + + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_OFF + + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_ON diff --git a/tests/components/remote/test_device_condition.py b/tests/components/remote/test_device_condition.py new file mode 100644 index 00000000000..2f01b4ab55f --- /dev/null +++ b/tests/components/remote/test_device_condition.py @@ -0,0 +1,235 @@ +"""The test for remote device automation.""" +from datetime import timedelta + +import pytest + +import homeassistant.components.automation as automation +from homeassistant.components.remote import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON +from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util + +from tests.async_mock import patch +from tests.common import ( + MockConfigEntry, + async_get_device_automation_capabilities, + async_get_device_automations, + async_mock_service, + mock_device_registry, + mock_registry, +) + + +@pytest.fixture +def device_reg(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +@pytest.fixture +def entity_reg(hass): + """Return an empty, loaded, registry.""" + return mock_registry(hass) + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_get_conditions(hass, device_reg, entity_reg): + """Test we get the expected conditions from a remote.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) + expected_conditions = [ + { + "condition": "device", + "domain": DOMAIN, + "type": "is_off", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + }, + { + "condition": "device", + "domain": DOMAIN, + "type": "is_on", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + }, + ] + conditions = await async_get_device_automations(hass, "condition", device_entry.id) + assert conditions == expected_conditions + + +async def test_get_condition_capabilities(hass, device_reg, entity_reg): + """Test we get the expected capabilities from a remote condition.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) + expected_capabilities = { + "extra_fields": [ + {"name": "for", "optional": True, "type": "positive_time_period_dict"} + ] + } + conditions = await async_get_device_automations(hass, "condition", device_entry.id) + for condition in conditions: + capabilities = await async_get_device_automation_capabilities( + hass, "condition", condition + ) + assert capabilities == expected_capabilities + + +async def test_if_state(hass, calls): + """Test for turn_on and turn_off conditions.""" + platform = getattr(hass.components, f"test.{DOMAIN}") + + platform.init() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + ent1, ent2, ent3 = platform.ENTITIES + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": {"platform": "event", "event_type": "test_event1"}, + "condition": [ + { + "condition": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "is_on", + } + ], + "action": { + "service": "test.automation", + "data_template": { + "some": "is_on {{ trigger.%s }}" + % "}} - {{ trigger.".join(("platform", "event.event_type")) + }, + }, + }, + { + "trigger": {"platform": "event", "event_type": "test_event2"}, + "condition": [ + { + "condition": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "is_off", + } + ], + "action": { + "service": "test.automation", + "data_template": { + "some": "is_off {{ trigger.%s }}" + % "}} - {{ trigger.".join(("platform", "event.event_type")) + }, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_ON + assert len(calls) == 0 + + hass.bus.async_fire("test_event1") + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "is_on event - test_event1" + + hass.states.async_set(ent1.entity_id, STATE_OFF) + hass.bus.async_fire("test_event1") + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[1].data["some"] == "is_off event - test_event2" + + +async def test_if_fires_on_for_condition(hass, calls): + """Test for firing if condition is on with delay.""" + point1 = dt_util.utcnow() + point2 = point1 + timedelta(seconds=10) + point3 = point2 + timedelta(seconds=10) + + platform = getattr(hass.components, f"test.{DOMAIN}") + + platform.init() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + ent1, ent2, ent3 = platform.ENTITIES + + with patch("homeassistant.core.dt_util.utcnow") as mock_utcnow: + mock_utcnow.return_value = point1 + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": {"platform": "event", "event_type": "test_event1"}, + "condition": { + "condition": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "is_off", + "for": {"seconds": 5}, + }, + "action": { + "service": "test.automation", + "data_template": { + "some": "is_off {{ trigger.%s }}" + % "}} - {{ trigger.".join( + ("platform", "event.event_type") + ) + }, + }, + } + ] + }, + ) + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_ON + assert len(calls) == 0 + + hass.bus.async_fire("test_event1") + await hass.async_block_till_done() + assert len(calls) == 0 + + # Time travel 10 secs into the future + mock_utcnow.return_value = point2 + hass.bus.async_fire("test_event1") + await hass.async_block_till_done() + assert len(calls) == 0 + + hass.states.async_set(ent1.entity_id, STATE_OFF) + hass.bus.async_fire("test_event1") + await hass.async_block_till_done() + assert len(calls) == 0 + + # Time travel 20 secs into the future + mock_utcnow.return_value = point3 + hass.bus.async_fire("test_event1") + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "is_off event - test_event1" diff --git a/tests/components/remote/test_device_trigger.py b/tests/components/remote/test_device_trigger.py new file mode 100644 index 00000000000..73feb3ea08f --- /dev/null +++ b/tests/components/remote/test_device_trigger.py @@ -0,0 +1,234 @@ +"""The test for remote device automation.""" +from datetime import timedelta + +import pytest + +import homeassistant.components.automation as automation +from homeassistant.components.remote import DOMAIN +from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON +from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util + +from tests.common import ( + MockConfigEntry, + async_fire_time_changed, + async_get_device_automation_capabilities, + async_get_device_automations, + async_mock_service, + mock_device_registry, + mock_registry, +) + + +@pytest.fixture +def device_reg(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +@pytest.fixture +def entity_reg(hass): + """Return an empty, loaded, registry.""" + return mock_registry(hass) + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_get_triggers(hass, device_reg, entity_reg): + """Test we get the expected triggers from a remote.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) + expected_triggers = [ + { + "platform": "device", + "domain": DOMAIN, + "type": "turned_off", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + }, + { + "platform": "device", + "domain": DOMAIN, + "type": "turned_on", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + }, + ] + triggers = await async_get_device_automations(hass, "trigger", device_entry.id) + assert triggers == expected_triggers + + +async def test_get_trigger_capabilities(hass, device_reg, entity_reg): + """Test we get the expected capabilities from a remote trigger.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) + expected_capabilities = { + "extra_fields": [ + {"name": "for", "optional": True, "type": "positive_time_period_dict"} + ] + } + triggers = await async_get_device_automations(hass, "trigger", device_entry.id) + for trigger in triggers: + capabilities = await async_get_device_automation_capabilities( + hass, "trigger", trigger + ) + assert capabilities == expected_capabilities + + +async def test_if_fires_on_state_change(hass, calls): + """Test for turn_on and turn_off triggers firing.""" + platform = getattr(hass.components, f"test.{DOMAIN}") + + platform.init() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + ent1, ent2, ent3 = platform.ENTITIES + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "turned_on", + }, + "action": { + "service": "test.automation", + "data_template": { + "some": "turn_on {{ trigger.%s }}" + % "}} - {{ trigger.".join( + ( + "platform", + "entity_id", + "from_state.state", + "to_state.state", + "for", + ) + ) + }, + }, + }, + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "turned_off", + }, + "action": { + "service": "test.automation", + "data_template": { + "some": "turn_off {{ trigger.%s }}" + % "}} - {{ trigger.".join( + ( + "platform", + "entity_id", + "from_state.state", + "to_state.state", + "for", + ) + ) + }, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_ON + assert len(calls) == 0 + + hass.states.async_set(ent1.entity_id, STATE_OFF) + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "turn_off device - {} - on - off - None".format( + ent1.entity_id + ) + + hass.states.async_set(ent1.entity_id, STATE_ON) + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format( + ent1.entity_id + ) + + +async def test_if_fires_on_state_change_with_for(hass, calls): + """Test for triggers firing with delay.""" + platform = getattr(hass.components, f"test.{DOMAIN}") + + platform.init() + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + ent1, ent2, ent3 = platform.ENTITIES + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": ent1.entity_id, + "type": "turned_off", + "for": {"seconds": 5}, + }, + "action": { + "service": "test.automation", + "data_template": { + "some": "turn_off {{ trigger.%s }}" + % "}} - {{ trigger.".join( + ( + "platform", + "entity_id", + "from_state.state", + "to_state.state", + "for", + ) + ) + }, + }, + } + ] + }, + ) + await hass.async_block_till_done() + assert hass.states.get(ent1.entity_id).state == STATE_ON + assert len(calls) == 0 + + hass.states.async_set(ent1.entity_id, STATE_OFF) + await hass.async_block_till_done() + assert len(calls) == 0 + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert len(calls) == 1 + await hass.async_block_till_done() + assert calls[0].data["some"] == "turn_off device - {} - on - off - 0:00:05".format( + ent1.entity_id + ) diff --git a/tests/testing_config/custom_components/test/remote.py b/tests/testing_config/custom_components/test/remote.py new file mode 100644 index 00000000000..c6f05b156f3 --- /dev/null +++ b/tests/testing_config/custom_components/test/remote.py @@ -0,0 +1,39 @@ +""" +Provide a mock remote platform. + +Call init before using it in your tests to ensure clean test data. +""" +from homeassistant.components.remote import RemoteEntity +from homeassistant.const import STATE_OFF, STATE_ON + +from tests.common import MockToggleEntity + +ENTITIES = [] + + +def init(empty=False): + """Initialize the platform with entities.""" + global ENTITIES + + ENTITIES = ( + [] + if empty + else [ + MockRemote("TV", STATE_ON), + MockRemote("DVD", STATE_OFF), + MockRemote(None, STATE_OFF), + ] + ) + + +async def async_setup_platform( + hass, config, async_add_entities_callback, discovery_info=None +): + """Return mock entities.""" + async_add_entities_callback(ENTITIES) + + +class MockRemote(MockToggleEntity, RemoteEntity): + """Mock remote class.""" + + supported_features = 0