From 766c889e7079d6312e0d10f45ce54322c3193a98 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Nov 2021 00:46:51 +0100 Subject: [PATCH] Add button support to HomeKit (#60165) Co-authored-by: J. Nick Koston --- .../components/homekit/accessories.py | 9 +++- .../components/homekit/config_flow.py | 1 + .../components/homekit/type_switches.py | 5 ++- script/hassfest/dependencies.py | 1 + .../homekit/test_get_accessories.py | 2 + .../components/homekit/test_type_switches.py | 43 +++++++++++++++++++ 6 files changed, 59 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 30ee2e72589..06c70c09f2e 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -195,7 +195,14 @@ def get_accessory(hass, driver, state, aid, config): # noqa: C901 elif state.domain == "remote" and features & SUPPORT_ACTIVITY: a_type = "ActivityRemote" - elif state.domain in ("automation", "input_boolean", "remote", "scene", "script"): + elif state.domain in ( + "automation", + "button", + "input_boolean", + "remote", + "scene", + "script", + ): a_type = "Switch" elif state.domain in ("input_select", "select"): diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 79a0e71f969..dc7f7b9013e 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -76,6 +76,7 @@ SUPPORTED_DOMAINS = [ "alarm_control_panel", "automation", "binary_sensor", + "button", CAMERA_DOMAIN, "climate", "cover", diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 9b9ff1f4df2..ec6813a82f1 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -12,6 +12,7 @@ from pyhap.const import ( CATEGORY_SWITCH, ) +from homeassistant.components import button from homeassistant.components.input_select import ATTR_OPTIONS, SERVICE_SELECT_OPTION from homeassistant.components.switch import DOMAIN from homeassistant.components.vacuum import ( @@ -69,7 +70,7 @@ VALVE_TYPE: dict[str, ValveInfo] = { } -ACTIVATE_ONLY_SWITCH_DOMAINS = {"scene", "script"} +ACTIVATE_ONLY_SWITCH_DOMAINS = {"button", "scene", "script"} ACTIVATE_ONLY_RESET_SECONDS = 10 @@ -149,6 +150,8 @@ class Switch(HomeAccessory): if self._domain == "script": service = self._object_id params = {} + elif self._domain == button.DOMAIN: + service = button.SERVICE_PRESS else: service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 41510a46cb4..9e66e05899c 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -95,6 +95,7 @@ ALLOWED_USED_COMPONENTS = { "alert", "automation", "conversation", + "button", "device_automation", "frontend", "group", diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index be2429c79cf..8cc416adabd 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -30,6 +30,7 @@ from homeassistant.const import ( DEVICE_CLASS_CO2, LIGHT_LUX, PERCENTAGE, + STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) @@ -270,6 +271,7 @@ def test_type_sensors(type_name, entity_id, state, attrs): [ ("Outlet", "switch.test", "on", {}, {CONF_TYPE: TYPE_OUTLET}), ("Switch", "automation.test", "on", {}, {}), + ("Switch", "button.test", STATE_UNKNOWN, {}, {}), ("Switch", "input_boolean.test", "on", {}, {}), ("Switch", "remote.test", "on", {}, {}), ("Switch", "scene.test", "on", {}, {}), diff --git a/tests/components/homekit/test_type_switches.py b/tests/components/homekit/test_type_switches.py index c13f7ea2538..59f871f31ac 100644 --- a/tests/components/homekit/test_type_switches.py +++ b/tests/components/homekit/test_type_switches.py @@ -449,3 +449,46 @@ async def test_input_select_switch(hass, hk_driver, events, domain): assert acc.select_chars["option1"].value is False assert acc.select_chars["option2"].value is False assert acc.select_chars["option3"].value is False + + +async def test_button_switch(hass, hk_driver, events): + """Test switch accessory from a button entity.""" + domain = "button" + entity_id = "button.test" + + hass.states.async_set(entity_id, None) + await hass.async_block_till_done() + acc = Switch(hass, hk_driver, "Switch", entity_id, 2, None) + await acc.run() + await hass.async_block_till_done() + + assert acc.activate_only is True + assert acc.char_on.value is False + + call_press = async_mock_service(hass, domain, "press") + + acc.char_on.client_update_value(True) + await hass.async_block_till_done() + assert acc.char_on.value is True + assert len(call_press) == 1 + assert call_press[0].data[ATTR_ENTITY_ID] == entity_id + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] is None + + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert acc.char_on.value is True + + future = dt_util.utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert acc.char_on.value is False + + assert len(events) == 1 + assert len(call_press) == 1 + + acc.char_on.client_update_value(False) + await hass.async_block_till_done() + assert acc.char_on.value is False + assert len(events) == 1