From 9993333bf91d6a9f093d6cc52fc0229b381b86cc Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 20 Jan 2020 00:08:32 -0700 Subject: [PATCH] Add state reproduction to remotes (#30990) --- .../components/remote/reproduce_state.py | 61 +++++++++++++++++++ .../components/remote/test_reproduce_state.py | 52 ++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 homeassistant/components/remote/reproduce_state.py create mode 100644 tests/components/remote/test_reproduce_state.py diff --git a/homeassistant/components/remote/reproduce_state.py b/homeassistant/components/remote/reproduce_state.py new file mode 100644 index 00000000000..1e5dee15683 --- /dev/null +++ b/homeassistant/components/remote/reproduce_state.py @@ -0,0 +1,61 @@ +"""Reproduce an Remote state.""" +import asyncio +import logging +from typing import Iterable, Optional + +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) +from homeassistant.core import Context, State +from homeassistant.helpers.typing import HomeAssistantType + +from . import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +VALID_STATES = {STATE_ON, STATE_OFF} + + +async def _async_reproduce_state( + hass: HomeAssistantType, state: State, context: Optional[Context] = None +) -> None: + """Reproduce a single state.""" + cur_state = hass.states.get(state.entity_id) + + if cur_state is None: + _LOGGER.warning("Unable to find entity %s", state.entity_id) + return + + if state.state not in VALID_STATES: + _LOGGER.warning( + "Invalid state specified for %s: %s", state.entity_id, state.state + ) + return + + # Return if we are already at the right state. + if cur_state.state == state.state: + return + + service_data = {ATTR_ENTITY_ID: state.entity_id} + + if state.state == STATE_ON: + service = SERVICE_TURN_ON + elif state.state == STATE_OFF: + service = SERVICE_TURN_OFF + + await hass.services.async_call( + DOMAIN, service, service_data, context=context, blocking=True + ) + + +async def async_reproduce_states( + hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None +) -> None: + """Reproduce Remote states.""" + await asyncio.gather( + *(_async_reproduce_state(hass, state, context) for state in states) + ) diff --git a/tests/components/remote/test_reproduce_state.py b/tests/components/remote/test_reproduce_state.py new file mode 100644 index 00000000000..ee1574d1741 --- /dev/null +++ b/tests/components/remote/test_reproduce_state.py @@ -0,0 +1,52 @@ +"""Test reproduce state for Remote.""" +from homeassistant.core import State + +from tests.common import async_mock_service + + +async def test_reproducing_states(hass, caplog): + """Test reproducing Remote states.""" + hass.states.async_set("remote.entity_off", "off", {}) + hass.states.async_set("remote.entity_on", "on", {}) + + turn_on_calls = async_mock_service(hass, "remote", "turn_on") + turn_off_calls = async_mock_service(hass, "remote", "turn_off") + + # These calls should do nothing as entities already in desired state + await hass.helpers.state.async_reproduce_state( + [State("remote.entity_off", "off"), State("remote.entity_on", "on")], + blocking=True, + ) + + assert len(turn_on_calls) == 0 + assert len(turn_off_calls) == 0 + + # Test invalid state is handled + await hass.helpers.state.async_reproduce_state( + [State("remote.entity_off", "not_supported")], blocking=True + ) + + assert "not_supported" in caplog.text + assert len(turn_on_calls) == 0 + assert len(turn_off_calls) == 0 + + # Make sure correct services are called + await hass.helpers.state.async_reproduce_state( + [ + State("remote.entity_on", "off"), + State("remote.entity_off", "on", {}), + # Should not raise + State("remote.non_existing", "on"), + ], + blocking=True, + ) + + assert len(turn_on_calls) == 1 + assert turn_on_calls[0].domain == "remote" + assert turn_on_calls[0].data == { + "entity_id": "remote.entity_off", + } + + assert len(turn_off_calls) == 1 + assert turn_off_calls[0].domain == "remote" + assert turn_off_calls[0].data == {"entity_id": "remote.entity_on"}