diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 6e67828ac61..3031dbddf0d 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -5,7 +5,7 @@ import logging import voluptuous as vol from zeroconf import InterfaceChoice -from homeassistant.components import cover +from homeassistant.components import cover, vacuum from homeassistant.components.cover import DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE from homeassistant.components.media_player import DEVICE_CLASS_TV from homeassistant.const import ( @@ -268,6 +268,13 @@ def get_accessory(hass, driver, state, aid, config): switch_type = config.get(CONF_TYPE, TYPE_SWITCH) a_type = SWITCH_TYPES[switch_type] + elif state.domain == "vacuum": + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + if features & (vacuum.SUPPORT_START | vacuum.SUPPORT_RETURN_HOME): + a_type = "DockVacuum" + else: + a_type = "Switch" + elif state.domain in ("automation", "input_boolean", "remote", "scene", "script"): a_type = "Switch" diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 5dcac8b7259..a1088f110e5 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -11,6 +11,12 @@ from pyhap.const import ( from homeassistant.components.script import ATTR_CAN_CANCEL from homeassistant.components.switch import DOMAIN +from homeassistant.components.vacuum import ( + DOMAIN as VACUUM_DOMAIN, + SERVICE_RETURN_TO_BASE, + SERVICE_START, + STATE_CLEANING, +) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_TYPE, @@ -146,6 +152,25 @@ class Switch(HomeAccessory): self.char_on.set_value(current_state) +@TYPES.register("DockVacuum") +class DockVacuum(Switch): + """Generate a Switch accessory.""" + + def set_state(self, value): + """Move switch state to value if call came from HomeKit.""" + _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value) + params = {ATTR_ENTITY_ID: self.entity_id} + service = SERVICE_START if value else SERVICE_RETURN_TO_BASE + self.call_service(VACUUM_DOMAIN, service, params) + + def update_state(self, new_state): + """Update switch state after state changed.""" + current_state = new_state.state in (STATE_CLEANING, STATE_ON) + if self.char_on.value is not current_state: + _LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state) + self.char_on.set_value(current_state) + + @TYPES.register("Valve") class Valve(HomeAccessory): """Generate a Valve accessory.""" diff --git a/tests/components/homekit/test_type_switches.py b/tests/components/homekit/test_type_switches.py index f8120d8ebbc..483f08e4db5 100644 --- a/tests/components/homekit/test_type_switches.py +++ b/tests/components/homekit/test_type_switches.py @@ -10,8 +10,20 @@ from homeassistant.components.homekit.const import ( TYPE_SPRINKLER, TYPE_VALVE, ) -from homeassistant.components.homekit.type_switches import Outlet, Switch, Valve +from homeassistant.components.homekit.type_switches import ( + DockVacuum, + Outlet, + Switch, + Valve, +) from homeassistant.components.script import ATTR_CAN_CANCEL +from homeassistant.components.vacuum import ( + DOMAIN as VACUUM_DOMAIN, + SERVICE_RETURN_TO_BASE, + SERVICE_START, + STATE_CLEANING, + STATE_DOCKED, +) from homeassistant.const import ATTR_ENTITY_ID, CONF_TYPE, STATE_OFF, STATE_ON from homeassistant.core import split_entity_id import homeassistant.util.dt as dt_util @@ -182,6 +194,52 @@ async def test_valve_set_state(hass, hk_driver, events): assert events[-1].data[ATTR_VALUE] is None +async def test_vacuum_set_state(hass, hk_driver, events): + """Test if Vacuum accessory and HA are updated accordingly.""" + entity_id = "vacuum.roomba" + + hass.states.async_set(entity_id, None) + await hass.async_block_till_done() + + acc = DockVacuum(hass, hk_driver, "DockVacuum", entity_id, 2, None) + await acc.run_handler() + await hass.async_block_till_done() + assert acc.aid == 2 + assert acc.category == 8 # Switch + + assert acc.char_on.value == 0 + + hass.states.async_set(entity_id, STATE_CLEANING) + await hass.async_block_till_done() + assert acc.char_on.value == 1 + + hass.states.async_set(entity_id, STATE_DOCKED) + await hass.async_block_till_done() + assert acc.char_on.value == 0 + + # Set from HomeKit + call_start = async_mock_service(hass, VACUUM_DOMAIN, SERVICE_START) + call_return_to_base = async_mock_service( + hass, VACUUM_DOMAIN, SERVICE_RETURN_TO_BASE + ) + + await hass.async_add_executor_job(acc.char_on.client_update_value, 1) + await hass.async_block_till_done() + assert acc.char_on.value == 1 + assert call_start + assert call_start[0].data[ATTR_ENTITY_ID] == entity_id + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] is None + + await hass.async_add_executor_job(acc.char_on.client_update_value, 0) + await hass.async_block_till_done() + assert acc.char_on.value == 0 + assert call_return_to_base + assert call_return_to_base[0].data[ATTR_ENTITY_ID] == entity_id + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] is None + + @pytest.mark.parametrize( "entity_id, attrs", [