diff --git a/homeassistant/components/light/switch.py b/homeassistant/components/light/switch.py new file mode 100644 index 00000000000..de6247a2772 --- /dev/null +++ b/homeassistant/components/light/switch.py @@ -0,0 +1,113 @@ +""" +Light support for switch entities. + +For more information about this platform, please refer to the documentation at +https://home-assistant.io/components/light.switch/ +""" +import logging +import voluptuous as vol + +from homeassistant.core import State, callback +from homeassistant.components.light import ( + Light, PLATFORM_SCHEMA) +from homeassistant.components import switch +from homeassistant.const import ( + STATE_ON, + ATTR_ENTITY_ID, + CONF_NAME, + CONF_ENTITY_ID, + STATE_UNAVAILABLE +) +from homeassistant.helpers.typing import HomeAssistantType, ConfigType +from homeassistant.helpers.event import async_track_state_change +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'Light Switch' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ENTITY_ID): cv.entity_domain(switch.DOMAIN) +}) + + +async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, + async_add_entities, + discovery_info=None) -> None: + """Initialize Light Switch platform.""" + async_add_entities([LightSwitch(config.get(CONF_NAME), + config[CONF_ENTITY_ID])], True) + + +class LightSwitch(Light): + """Represents a Switch as a Light.""" + + def __init__(self, name: str, switch_entity_id: str) -> None: + """Initialize Light Switch.""" + self._name = name # type: str + self._switch_entity_id = switch_entity_id # type: str + self._is_on = False # type: bool + self._available = False # type: bool + self._async_unsub_state_changed = None + + @property + def name(self) -> str: + """Return the name of the entity.""" + return self._name + + @property + def is_on(self) -> bool: + """Return true if light switch is on.""" + return self._is_on + + @property + def available(self) -> bool: + """Return true if light switch is on.""" + return self._available + + @property + def should_poll(self) -> bool: + """No polling needed for a light switch.""" + return False + + async def async_turn_on(self, **kwargs): + """Forward the turn_on command to the switch in this light switch.""" + data = {ATTR_ENTITY_ID: self._switch_entity_id} + await self.hass.services.async_call( + switch.DOMAIN, switch.SERVICE_TURN_ON, data, blocking=True) + + async def async_turn_off(self, **kwargs): + """Forward the turn_off command to the switch in this light switch.""" + data = {ATTR_ENTITY_ID: self._switch_entity_id} + await self.hass.services.async_call( + switch.DOMAIN, switch.SERVICE_TURN_OFF, data, blocking=True) + + async def async_update(self): + """Query the switch in this light switch and determine the state.""" + switch_state = self.hass.states.get(self._switch_entity_id) + + if switch_state is None: + self._available = False + return + + self._is_on = switch_state.state == STATE_ON + self._available = switch_state.state != STATE_UNAVAILABLE + + async def async_added_to_hass(self) -> None: + """Register callbacks.""" + @callback + def async_state_changed_listener(entity_id: str, old_state: State, + new_state: State): + """Handle child updates.""" + self.async_schedule_update_ha_state(True) + + self._async_unsub_state_changed = async_track_state_change( + self.hass, self._switch_entity_id, async_state_changed_listener) + + async def async_will_remove_from_hass(self): + """Handle removal from Home Assistant.""" + if self._async_unsub_state_changed is not None: + self._async_unsub_state_changed() + self._async_unsub_state_changed = None + self._available = False diff --git a/tests/components/light/test_switch.py b/tests/components/light/test_switch.py new file mode 100644 index 00000000000..5e6bebb56ef --- /dev/null +++ b/tests/components/light/test_switch.py @@ -0,0 +1,81 @@ +"""The tests for the Light Switch platform.""" + +from homeassistant.setup import async_setup_component +from tests.components.light import common +from tests.components.switch import common as switch_common + + +async def test_default_state(hass): + """Test light switch default state.""" + await async_setup_component(hass, 'light', {'light': { + 'platform': 'switch', 'entity_id': 'switch.test', + 'name': 'Christmas Tree Lights' + }}) + await hass.async_block_till_done() + + state = hass.states.get('light.christmas_tree_lights') + assert state is not None + assert state.state == 'unavailable' + assert state.attributes['supported_features'] == 0 + assert state.attributes.get('brightness') is None + assert state.attributes.get('hs_color') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('effect_list') is None + assert state.attributes.get('effect') is None + + +async def test_light_service_calls(hass): + """Test service calls to light.""" + await async_setup_component(hass, 'switch', {'switch': [ + {'platform': 'demo'} + ]}) + await async_setup_component(hass, 'light', {'light': [ + {'platform': 'switch', 'entity_id': 'switch.decorative_lights'} + ]}) + await hass.async_block_till_done() + + assert hass.states.get('light.light_switch').state == 'on' + + common.async_toggle(hass, 'light.light_switch') + await hass.async_block_till_done() + + assert hass.states.get('switch.decorative_lights').state == 'off' + assert hass.states.get('light.light_switch').state == 'off' + + common.async_turn_on(hass, 'light.light_switch') + await hass.async_block_till_done() + + assert hass.states.get('switch.decorative_lights').state == 'on' + assert hass.states.get('light.light_switch').state == 'on' + + common.async_turn_off(hass, 'light.light_switch') + await hass.async_block_till_done() + + assert hass.states.get('switch.decorative_lights').state == 'off' + assert hass.states.get('light.light_switch').state == 'off' + + +async def test_switch_service_calls(hass): + """Test service calls to switch.""" + await async_setup_component(hass, 'switch', {'switch': [ + {'platform': 'demo'} + ]}) + await async_setup_component(hass, 'light', {'light': [ + {'platform': 'switch', 'entity_id': 'switch.decorative_lights'} + ]}) + await hass.async_block_till_done() + + assert hass.states.get('light.light_switch').state == 'on' + + switch_common.async_turn_off(hass, 'switch.decorative_lights') + await hass.async_block_till_done() + + assert hass.states.get('switch.decorative_lights').state == 'off' + assert hass.states.get('light.light_switch').state == 'off' + + switch_common.async_turn_on(hass, 'switch.decorative_lights') + await hass.async_block_till_done() + + assert hass.states.get('switch.decorative_lights').state == 'on' + assert hass.states.get('light.light_switch').state == 'on'