From e8710002b119845b6833df4af876135ef7e1f2bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 Apr 2020 21:39:31 -0500 Subject: [PATCH] Convert homekit fans to use service callbacks (#34229) * Convert homekit fans to use service callbacks * Convert homekit fans to use service callbacks Service callbacks allow us ensure that we call fan services in the correct order. * Avoid calling turn_on if a speed is sent and the device is on * Fix test to not leave files behind * Fix test * Update homeassistant/components/homekit/type_fans.py Co-Authored-By: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- homeassistant/components/homekit/type_fans.py | 84 ++--- tests/components/homekit/conftest.py | 4 +- tests/components/homekit/test_type_fans.py | 317 +++++++++++++++++- tests/components/homekit/test_type_lights.py | 59 ++-- .../homekit/test_type_thermostats.py | 94 +++--- 5 files changed, 420 insertions(+), 138 deletions(-) diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index e6d128d1e28..9167c8fcf5d 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -28,7 +28,7 @@ from homeassistant.const import ( ) from . import TYPES -from .accessories import HomeAccessory, debounce +from .accessories import HomeAccessory from .const import ( CHAR_ACTIVE, CHAR_ROTATION_DIRECTION, @@ -51,17 +51,11 @@ class Fan(HomeAccessory): def __init__(self, *args): """Initialize a new Light accessory object.""" super().__init__(*args, category=CATEGORY_FAN) - self._flag = { - CHAR_ACTIVE: False, - CHAR_ROTATION_DIRECTION: False, - CHAR_SWING_MODE: False, - } - self._state = 0 - chars = [] - features = self.hass.states.get(self.entity_id).attributes.get( - ATTR_SUPPORTED_FEATURES - ) + state = self.hass.states.get(self.entity_id) + + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + if features & SUPPORT_DIRECTION: chars.append(CHAR_ROTATION_DIRECTION) if features & SUPPORT_OSCILLATE: @@ -74,9 +68,7 @@ class Fan(HomeAccessory): chars.append(CHAR_ROTATION_SPEED) serv_fan = self.add_preload_service(SERV_FANV2, chars) - self.char_active = serv_fan.configure_char( - CHAR_ACTIVE, value=0, setter_callback=self.set_state - ) + self.char_active = serv_fan.configure_char(CHAR_ACTIVE, value=0) self.char_direction = None self.char_speed = None @@ -84,26 +76,52 @@ class Fan(HomeAccessory): if CHAR_ROTATION_DIRECTION in chars: self.char_direction = serv_fan.configure_char( - CHAR_ROTATION_DIRECTION, value=0, setter_callback=self.set_direction + CHAR_ROTATION_DIRECTION, value=0 ) if CHAR_ROTATION_SPEED in chars: # Initial value is set to 100 because 0 is a special value (off). 100 is # an arbitrary non-zero value. It is updated immediately by update_state # to set to the correct initial value. - self.char_speed = serv_fan.configure_char( - CHAR_ROTATION_SPEED, value=100, setter_callback=self.set_speed - ) + self.char_speed = serv_fan.configure_char(CHAR_ROTATION_SPEED, value=100) if CHAR_SWING_MODE in chars: - self.char_swing = serv_fan.configure_char( - CHAR_SWING_MODE, value=0, setter_callback=self.set_oscillating - ) + self.char_swing = serv_fan.configure_char(CHAR_SWING_MODE, value=0) + self.update_state(state) + serv_fan.setter_callback = self._set_chars + + def _set_chars(self, char_values): + _LOGGER.debug("Fan _set_chars: %s", char_values) + if CHAR_ACTIVE in char_values: + if char_values[CHAR_ACTIVE]: + is_on = False + state = self.hass.states.get(self.entity_id) + if state and state.state == STATE_ON: + is_on = True + # Only set the state to active if we + # did not get a rotation speed or its off + if not is_on or CHAR_ROTATION_SPEED not in char_values: + self.set_state(1) + else: + # Its off, nothing more to do as setting the + # other chars will likely turn it back on which + # is what we want to avoid + self.set_state(0) + return + + if CHAR_SWING_MODE in char_values: + self.set_oscillating(char_values[CHAR_SWING_MODE]) + if CHAR_ROTATION_DIRECTION in char_values: + self.set_direction(char_values[CHAR_ROTATION_DIRECTION]) + + # We always do this LAST to ensure they + # get the speed they asked for + if CHAR_ROTATION_SPEED in char_values: + self.set_speed(char_values[CHAR_ROTATION_SPEED]) def set_state(self, value): """Set state if call came from HomeKit.""" _LOGGER.debug("%s: Set state to %d", self.entity_id, value) - self._flag[CHAR_ACTIVE] = True service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF params = {ATTR_ENTITY_ID: self.entity_id} self.call_service(DOMAIN, service, params) @@ -111,7 +129,6 @@ class Fan(HomeAccessory): def set_direction(self, value): """Set state if call came from HomeKit.""" _LOGGER.debug("%s: Set direction to %d", self.entity_id, value) - self._flag[CHAR_ROTATION_DIRECTION] = True direction = DIRECTION_REVERSE if value == 1 else DIRECTION_FORWARD params = {ATTR_ENTITY_ID: self.entity_id, ATTR_DIRECTION: direction} self.call_service(DOMAIN, SERVICE_SET_DIRECTION, params, direction) @@ -119,12 +136,10 @@ class Fan(HomeAccessory): def set_oscillating(self, value): """Set state if call came from HomeKit.""" _LOGGER.debug("%s: Set oscillating to %d", self.entity_id, value) - self._flag[CHAR_SWING_MODE] = True oscillating = value == 1 params = {ATTR_ENTITY_ID: self.entity_id, ATTR_OSCILLATING: oscillating} self.call_service(DOMAIN, SERVICE_OSCILLATE, params, oscillating) - @debounce def set_speed(self, value): """Set state if call came from HomeKit.""" _LOGGER.debug("%s: Set speed to %d", self.entity_id, value) @@ -138,21 +153,16 @@ class Fan(HomeAccessory): state = new_state.state if state in (STATE_ON, STATE_OFF): self._state = 1 if state == STATE_ON else 0 - if not self._flag[CHAR_ACTIVE] and self.char_active.value != self._state: + if self.char_active.value != self._state: self.char_active.set_value(self._state) - self._flag[CHAR_ACTIVE] = False # Handle Direction if self.char_direction is not None: direction = new_state.attributes.get(ATTR_DIRECTION) - if not self._flag[CHAR_ROTATION_DIRECTION] and direction in ( - DIRECTION_FORWARD, - DIRECTION_REVERSE, - ): + if direction in (DIRECTION_FORWARD, DIRECTION_REVERSE): hk_direction = 1 if direction == DIRECTION_REVERSE else 0 if self.char_direction.value != hk_direction: self.char_direction.set_value(hk_direction) - self._flag[CHAR_ROTATION_DIRECTION] = False # Handle Speed if self.char_speed is not None: @@ -170,17 +180,15 @@ class Fan(HomeAccessory): # Therefore, if the hk_speed_value is 0 and the device is still on, # the rotation speed is mapped to 1 otherwise the update is ignored # in order to avoid this incorrect behavior. - if hk_speed_value == 0: - if state == STATE_ON: - self.char_speed.set_value(1) - else: + if hk_speed_value == 0 and state == STATE_ON: + hk_speed_value = 1 + if self.char_speed.value != hk_speed_value: self.char_speed.set_value(hk_speed_value) # Handle Oscillating if self.char_swing is not None: oscillating = new_state.attributes.get(ATTR_OSCILLATING) - if not self._flag[CHAR_SWING_MODE] and oscillating in (True, False): + if isinstance(oscillating, bool): hk_oscillating = 1 if oscillating else 0 if self.char_swing.value != hk_oscillating: self.char_swing.set_value(hk_oscillating) - self._flag[CHAR_SWING_MODE] = False diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index ef534d0e472..7093bebf9ab 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -15,8 +15,10 @@ def hk_driver(): "pyhap.accessory_driver.AccessoryEncoder" ), patch("pyhap.accessory_driver.HAPServer"), patch( "pyhap.accessory_driver.AccessoryDriver.publish" + ), patch( + "pyhap.accessory_driver.AccessoryDriver.persist" ): - return AccessoryDriver(pincode=b"123-45-678", address="127.0.0.1") + yield AccessoryDriver(pincode=b"123-45-678", address="127.0.0.1") @pytest.fixture diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index 8d51d5a7181..b77b799900a 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -2,6 +2,7 @@ from collections import namedtuple from unittest.mock import Mock +from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE import pytest from homeassistant.components.fan import ( @@ -53,11 +54,12 @@ async def test_fan_basic(hass, hk_driver, cls, events): hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0}) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None) + acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + hk_driver.add_accessory(acc) - assert acc.aid == 2 + assert acc.aid == 1 assert acc.category == 3 # Fan - assert acc.char_active.value == 0 + assert acc.char_active.value == 1 # If there are no speed_list values, then HomeKit speed is unsupported assert acc.char_speed is None @@ -82,7 +84,20 @@ async def test_fan_basic(hass, hk_driver, cls, events): call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") call_turn_off = async_mock_service(hass, DOMAIN, "turn_off") - await hass.async_add_executor_job(acc.char_active.client_update_value, 1) + char_active_iid = acc.char_active.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 1, + }, + ] + }, + "mock_addr", + ) await hass.async_block_till_done() assert call_turn_on assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id @@ -92,7 +107,18 @@ async def test_fan_basic(hass, hk_driver, cls, events): hass.states.async_set(entity_id, STATE_ON) await hass.async_block_till_done() - await hass.async_add_executor_job(acc.char_active.client_update_value, 0) + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 0, + }, + ] + }, + "mock_addr", + ) await hass.async_block_till_done() assert call_turn_off assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id @@ -110,7 +136,8 @@ async def test_fan_direction(hass, hk_driver, cls, events): {ATTR_SUPPORTED_FEATURES: SUPPORT_DIRECTION, ATTR_DIRECTION: DIRECTION_FORWARD}, ) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None) + acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + hk_driver.add_accessory(acc) assert acc.char_direction.value == 0 @@ -125,7 +152,20 @@ async def test_fan_direction(hass, hk_driver, cls, events): # Set from HomeKit call_set_direction = async_mock_service(hass, DOMAIN, "set_direction") - await hass.async_add_executor_job(acc.char_direction.client_update_value, 0) + char_direction_iid = acc.char_direction.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_direction_iid, + HAP_REPR_VALUE: 0, + }, + ] + }, + "mock_addr", + ) await hass.async_block_till_done() assert call_set_direction[0] assert call_set_direction[0].data[ATTR_ENTITY_ID] == entity_id @@ -133,6 +173,18 @@ async def test_fan_direction(hass, hk_driver, cls, events): assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == DIRECTION_FORWARD + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_direction_iid, + HAP_REPR_VALUE: 1, + }, + ] + }, + "mock_addr", + ) await hass.async_add_executor_job(acc.char_direction.client_update_value, 1) await hass.async_block_till_done() assert call_set_direction[1] @@ -152,7 +204,8 @@ async def test_fan_oscillate(hass, hk_driver, cls, events): {ATTR_SUPPORTED_FEATURES: SUPPORT_OSCILLATE, ATTR_OSCILLATING: False}, ) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None) + acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + hk_driver.add_accessory(acc) assert acc.char_swing.value == 0 @@ -167,6 +220,20 @@ async def test_fan_oscillate(hass, hk_driver, cls, events): # Set from HomeKit call_oscillate = async_mock_service(hass, DOMAIN, "oscillate") + char_swing_iid = acc.char_swing.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_swing_iid, + HAP_REPR_VALUE: 0, + }, + ] + }, + "mock_addr", + ) await hass.async_add_executor_job(acc.char_swing.client_update_value, 0) await hass.async_block_till_done() assert call_oscillate[0] @@ -175,6 +242,18 @@ async def test_fan_oscillate(hass, hk_driver, cls, events): assert len(events) == 1 assert events[-1].data[ATTR_VALUE] is False + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_swing_iid, + HAP_REPR_VALUE: 1, + }, + ] + }, + "mock_addr", + ) await hass.async_add_executor_job(acc.char_swing.client_update_value, 1) await hass.async_block_till_done() assert call_oscillate[1] @@ -199,7 +278,8 @@ async def test_fan_speed(hass, hk_driver, cls, events): }, ) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None) + acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the # speed to 100 when turning on a fan on a freshly booted up server. @@ -221,6 +301,20 @@ async def test_fan_speed(hass, hk_driver, cls, events): # Set from HomeKit call_set_speed = async_mock_service(hass, DOMAIN, "set_speed") + char_speed_iid = acc.char_speed.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_speed_iid, + HAP_REPR_VALUE: 42, + }, + ] + }, + "mock_addr", + ) await hass.async_add_executor_job(acc.char_speed.client_update_value, 42) await hass.async_block_till_done() acc.speed_mapping.speed_to_states.assert_called_with(42) @@ -231,6 +325,211 @@ async def test_fan_speed(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] == "ludicrous" +async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): + """Test fan with speed.""" + entity_id = "fan.demo" + speed_list = [SPEED_OFF, SPEED_LOW, SPEED_HIGH] + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_SET_SPEED + | SUPPORT_OSCILLATE + | SUPPORT_DIRECTION, + ATTR_SPEED: SPEED_OFF, + ATTR_OSCILLATING: False, + ATTR_DIRECTION: DIRECTION_FORWARD, + ATTR_SPEED_LIST: speed_list, + }, + ) + await hass.async_block_till_done() + acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + hk_driver.add_accessory(acc) + + # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the + # speed to 100 when turning on a fan on a freshly booted up server. + assert acc.char_speed.value != 0 + await acc.run_handler() + assert ( + acc.speed_mapping.speed_ranges == HomeKitSpeedMapping(speed_list).speed_ranges + ) + + acc.speed_mapping.speed_to_homekit = Mock(return_value=42) + acc.speed_mapping.speed_to_states = Mock(return_value="ludicrous") + + hass.states.async_set( + entity_id, + STATE_OFF, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_SET_SPEED + | SUPPORT_OSCILLATE + | SUPPORT_DIRECTION, + ATTR_SPEED: SPEED_OFF, + ATTR_OSCILLATING: False, + ATTR_DIRECTION: DIRECTION_FORWARD, + ATTR_SPEED_LIST: speed_list, + }, + ) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_OFF + + # Set from HomeKit + call_set_speed = async_mock_service(hass, DOMAIN, "set_speed") + call_oscillate = async_mock_service(hass, DOMAIN, "oscillate") + call_set_direction = async_mock_service(hass, DOMAIN, "set_direction") + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + call_turn_off = async_mock_service(hass, DOMAIN, "turn_off") + + char_active_iid = acc.char_active.to_HAP()[HAP_REPR_IID] + char_direction_iid = acc.char_direction.to_HAP()[HAP_REPR_IID] + char_swing_iid = acc.char_swing.to_HAP()[HAP_REPR_IID] + char_speed_iid = acc.char_speed.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 1, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_speed_iid, + HAP_REPR_VALUE: 42, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_swing_iid, + HAP_REPR_VALUE: 1, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_direction_iid, + HAP_REPR_VALUE: 1, + }, + ] + }, + "mock_addr", + ) + await hass.async_block_till_done() + acc.speed_mapping.speed_to_states.assert_called_with(42) + assert call_turn_on + assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_speed[0] + assert call_set_speed[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_speed[0].data[ATTR_SPEED] == "ludicrous" + assert call_oscillate[0] + assert call_oscillate[0].data[ATTR_ENTITY_ID] == entity_id + assert call_oscillate[0].data[ATTR_OSCILLATING] is True + assert call_set_direction[0] + assert call_set_direction[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_direction[0].data[ATTR_DIRECTION] == DIRECTION_REVERSE + assert len(events) == 4 + + assert events[1].data[ATTR_VALUE] is True + assert events[2].data[ATTR_VALUE] == DIRECTION_REVERSE + assert events[3].data[ATTR_VALUE] == "ludicrous" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_SET_SPEED + | SUPPORT_OSCILLATE + | SUPPORT_DIRECTION, + ATTR_SPEED: SPEED_OFF, + ATTR_OSCILLATING: False, + ATTR_DIRECTION: DIRECTION_FORWARD, + ATTR_SPEED_LIST: speed_list, + }, + ) + await hass.async_block_till_done() + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 1, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_speed_iid, + HAP_REPR_VALUE: 42, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_swing_iid, + HAP_REPR_VALUE: 1, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_direction_iid, + HAP_REPR_VALUE: 1, + }, + ] + }, + "mock_addr", + ) + # Turn on should not be called if its already on + # and we set a fan speed + await hass.async_block_till_done() + acc.speed_mapping.speed_to_states.assert_called_with(42) + assert len(events) == 7 + assert call_set_speed[1] + assert call_set_speed[1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_speed[1].data[ATTR_SPEED] == "ludicrous" + assert call_oscillate[1] + assert call_oscillate[1].data[ATTR_ENTITY_ID] == entity_id + assert call_oscillate[1].data[ATTR_OSCILLATING] is True + assert call_set_direction[1] + assert call_set_direction[1].data[ATTR_ENTITY_ID] == entity_id + assert call_set_direction[1].data[ATTR_DIRECTION] == DIRECTION_REVERSE + + assert events[-3].data[ATTR_VALUE] is True + assert events[-2].data[ATTR_VALUE] == DIRECTION_REVERSE + assert events[-1].data[ATTR_VALUE] == "ludicrous" + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 0, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_speed_iid, + HAP_REPR_VALUE: 42, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_swing_iid, + HAP_REPR_VALUE: 1, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_direction_iid, + HAP_REPR_VALUE: 1, + }, + ] + }, + "mock_addr", + ) + await hass.async_block_till_done() + + assert len(events) == 8 + assert call_turn_off + assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id + assert len(call_set_speed) == 2 + assert len(call_oscillate) == 2 + assert len(call_set_direction) == 2 + + async def test_fan_restore(hass, hk_driver, cls, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 5b5dcf8f3a2..26bb5bfdbad 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -1,8 +1,6 @@ """Test different accessory types: Lights.""" from collections import namedtuple -from asynctest import patch -from pyhap.accessory_driver import AccessoryDriver from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE import pytest @@ -33,15 +31,6 @@ from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce -@pytest.fixture -def driver(): - """Patch AccessoryDriver without zeroconf or HAPServer.""" - with patch("pyhap.accessory_driver.HAPServer"), patch( - "pyhap.accessory_driver.Zeroconf" - ), patch("pyhap.accessory_driver.AccessoryDriver.persist"): - yield AccessoryDriver() - - @pytest.fixture(scope="module") def cls(): """Patch debounce decorator during import of type_lights.""" @@ -55,14 +44,14 @@ def cls(): patcher.stop() -async def test_light_basic(hass, hk_driver, cls, events, driver): +async def test_light_basic(hass, hk_driver, cls, events): """Test light with char state.""" entity_id = "light.demo" hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0}) await hass.async_block_till_done() acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) assert acc.aid == 1 assert acc.category == 5 # Lightbulb @@ -90,7 +79,7 @@ async def test_light_basic(hass, hk_driver, cls, events, driver): char_on_iid = acc.char_on.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ {HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1} @@ -109,7 +98,7 @@ async def test_light_basic(hass, hk_driver, cls, events, driver): hass.states.async_set(entity_id, STATE_ON) await hass.async_block_till_done() - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ {HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 0} @@ -124,7 +113,7 @@ async def test_light_basic(hass, hk_driver, cls, events, driver): assert events[-1].data[ATTR_VALUE] == "Set state to 0" -async def test_light_brightness(hass, hk_driver, cls, events, driver): +async def test_light_brightness(hass, hk_driver, cls, events): """Test light with brightness.""" entity_id = "light.demo" @@ -135,7 +124,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver): ) await hass.async_block_till_done() acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the # brightness to 100 when turning on a light on a freshly booted up server. @@ -155,7 +144,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver): call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") call_turn_off = async_mock_service(hass, DOMAIN, "turn_off") - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ {HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1}, @@ -178,7 +167,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver): == f"Set state to 1, brightness at 20{UNIT_PERCENTAGE}" ) - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ {HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1}, @@ -201,7 +190,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver): == f"Set state to 1, brightness at 40{UNIT_PERCENTAGE}" ) - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ {HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1}, @@ -247,7 +236,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver): assert acc.char_brightness.value == 1 -async def test_light_color_temperature(hass, hk_driver, cls, events, driver): +async def test_light_color_temperature(hass, hk_driver, cls, events): """Test light with color temperature.""" entity_id = "light.demo" @@ -258,7 +247,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events, driver): ) await hass.async_block_till_done() acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) assert acc.char_color_temperature.value == 190 @@ -271,7 +260,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events, driver): char_color_temperature_iid = acc.char_color_temperature.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -313,7 +302,7 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, event assert not hasattr(acc, "char_color_temperature") -async def test_light_rgb_color(hass, hk_driver, cls, events, driver): +async def test_light_rgb_color(hass, hk_driver, cls, events): """Test light with rgb_color.""" entity_id = "light.demo" @@ -324,7 +313,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events, driver): ) await hass.async_block_till_done() acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) assert acc.char_hue.value == 260 assert acc.char_saturation.value == 90 @@ -340,7 +329,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events, driver): char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID] char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -365,7 +354,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events, driver): assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" -async def test_light_restore(hass, hk_driver, cls, events, driver): +async def test_light_restore(hass, hk_driver, cls, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running @@ -386,7 +375,7 @@ async def test_light_restore(hass, hk_driver, cls, events, driver): await hass.async_block_till_done() acc = cls.light(hass, hk_driver, "Light", "light.simple", 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) assert acc.category == 5 # Lightbulb assert acc.chars == [] @@ -398,7 +387,7 @@ async def test_light_restore(hass, hk_driver, cls, events, driver): assert acc.char_on.value == 0 -async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driver): +async def test_light_set_brightness_and_color(hass, hk_driver, cls, events): """Test light with all chars in one go.""" entity_id = "light.demo" @@ -412,7 +401,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driv ) await hass.async_block_till_done() acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the # brightness to 100 when turning on a light on a freshly booted up server. @@ -438,7 +427,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driv # Set from HomeKit call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ {HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1}, @@ -474,9 +463,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driv ) -async def test_light_set_brightness_and_color_temp( - hass, hk_driver, cls, events, driver -): +async def test_light_set_brightness_and_color_temp(hass, hk_driver, cls, events): """Test light with all chars in one go.""" entity_id = "light.demo" @@ -490,7 +477,7 @@ async def test_light_set_brightness_and_color_temp( ) await hass.async_block_till_done() acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the # brightness to 100 when turning on a light on a freshly booted up server. @@ -514,7 +501,7 @@ async def test_light_set_brightness_and_color_temp( # Set from HomeKit call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ {HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1}, diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 93b781170b4..8ee533521e8 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -2,7 +2,6 @@ from collections import namedtuple from unittest.mock import patch -from pyhap.accessory_driver import AccessoryDriver from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE import pytest @@ -62,15 +61,6 @@ from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce -@pytest.fixture -def driver(): - """Patch AccessoryDriver without zeroconf or HAPServer.""" - with patch("pyhap.accessory_driver.HAPServer"), patch( - "pyhap.accessory_driver.Zeroconf" - ), patch("pyhap.accessory_driver.AccessoryDriver.persist"): - yield AccessoryDriver() - - @pytest.fixture(scope="module") def cls(): """Patch debounce decorator during import of type_thermostats.""" @@ -85,7 +75,7 @@ def cls(): patcher.stop() -async def test_thermostat(hass, hk_driver, cls, events, driver): +async def test_thermostat(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = "climate.test" @@ -106,7 +96,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver): ) await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -293,7 +283,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver): char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID] char_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -313,7 +303,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver): assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "TargetTemperature to 19.0°C" - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -328,7 +318,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver): await hass.async_block_till_done() assert not call_set_hvac_mode - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -348,7 +338,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver): assert len(events) == 2 assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 1" - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -369,7 +359,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver): assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 3" -async def test_thermostat_auto(hass, hk_driver, cls, events, driver): +async def test_thermostat_auto(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = "climate.test" @@ -384,7 +374,7 @@ async def test_thermostat_auto(hass, hk_driver, cls, events, driver): ) await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -459,7 +449,7 @@ async def test_thermostat_auto(hass, hk_driver, cls, events, driver): char_heating_thresh_temp_iid = acc.char_heating_thresh_temp.to_HAP()[HAP_REPR_IID] char_cooling_thresh_temp_iid = acc.char_cooling_thresh_temp.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -491,7 +481,7 @@ async def test_thermostat_auto(hass, hk_driver, cls, events, driver): ) -async def test_thermostat_humidity(hass, hk_driver, cls, events, driver): +async def test_thermostat_humidity(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly with humidity.""" entity_id = "climate.test" @@ -499,7 +489,7 @@ async def test_thermostat_humidity(hass, hk_driver, cls, events, driver): hass.states.async_set(entity_id, HVAC_MODE_OFF, {ATTR_SUPPORTED_FEATURES: 4}) await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -528,7 +518,7 @@ async def test_thermostat_humidity(hass, hk_driver, cls, events, driver): char_target_humidity_iid = acc.char_target_humidity.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -550,7 +540,7 @@ async def test_thermostat_humidity(hass, hk_driver, cls, events, driver): assert events[-1].data[ATTR_VALUE] == "35%" -async def test_thermostat_power_state(hass, hk_driver, cls, events, driver): +async def test_thermostat_power_state(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = "climate.test" @@ -574,7 +564,7 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events, driver): ) await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -627,7 +617,7 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events, driver): char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -648,7 +638,7 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events, driver): assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 1" - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -670,7 +660,7 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events, driver): assert acc.char_target_heat_cool.value == 2 -async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver): +async def test_thermostat_fahrenheit(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = "climate.test" @@ -686,7 +676,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver): await hass.async_block_till_done() with patch.object(hass.config.units, CONF_TEMPERATURE_UNIT, new=TEMP_FAHRENHEIT): acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -717,7 +707,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver): char_heating_thresh_temp_iid = acc.char_heating_thresh_temp.to_HAP()[HAP_REPR_IID] char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -738,7 +728,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver): assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "CoolingThresholdTemperature to 23°C" - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -759,7 +749,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver): assert len(events) == 2 assert events[-1].data[ATTR_VALUE] == "HeatingThresholdTemperature to 22°C" - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -779,7 +769,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver): assert events[-1].data[ATTR_VALUE] == "TargetTemperature to 24.0°C" -async def test_thermostat_get_temperature_range(hass, hk_driver, cls, driver): +async def test_thermostat_get_temperature_range(hass, hk_driver, cls): """Test if temperature range is evaluated correctly.""" entity_id = "climate.test" @@ -801,14 +791,14 @@ async def test_thermostat_get_temperature_range(hass, hk_driver, cls, driver): assert acc.get_temperature_range() == (15.5, 21.0) -async def test_thermostat_temperature_step_whole(hass, hk_driver, cls, driver): +async def test_thermostat_temperature_step_whole(hass, hk_driver, cls): """Test climate device with single digit precision.""" entity_id = "climate.test" hass.states.async_set(entity_id, HVAC_MODE_OFF, {ATTR_TARGET_TEMP_STEP: 1}) await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -861,7 +851,7 @@ async def test_thermostat_restore(hass, hk_driver, cls, events): } -async def test_thermostat_hvac_modes(hass, hk_driver, cls, driver): +async def test_thermostat_hvac_modes(hass, hk_driver, cls): """Test if unsupported HVAC modes are deactivated in HomeKit.""" entity_id = "climate.test" @@ -871,7 +861,7 @@ async def test_thermostat_hvac_modes(hass, hk_driver, cls, driver): await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -894,7 +884,7 @@ async def test_thermostat_hvac_modes(hass, hk_driver, cls, driver): assert acc.char_target_heat_cool.value == 1 -async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, driver): +async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls): """Test we get heat cool over auto.""" entity_id = "climate.test" @@ -914,7 +904,7 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, d await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -937,7 +927,7 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, d char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -957,9 +947,7 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, d assert acc.char_target_heat_cool.value == 3 -async def test_thermostat_hvac_modes_with_auto_no_heat_cool( - hass, hk_driver, cls, driver -): +async def test_thermostat_hvac_modes_with_auto_no_heat_cool(hass, hk_driver, cls): """Test we get auto when there is no heat cool.""" entity_id = "climate.test" @@ -972,7 +960,7 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool( await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -995,7 +983,7 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool( char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -1015,7 +1003,7 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool( assert acc.char_target_heat_cool.value == 3 -async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls, driver): +async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls): """Test if unsupported HVAC modes are deactivated in HomeKit.""" entity_id = "climate.test" @@ -1025,7 +1013,7 @@ async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls, driver await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -1048,7 +1036,7 @@ async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls, driver assert acc.char_target_heat_cool.value == 3 -async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls, driver): +async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls): """Test a thermostat that has no off.""" entity_id = "climate.test" @@ -1058,7 +1046,7 @@ async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls, driver): await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -1085,9 +1073,7 @@ async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls, driver): assert acc.char_target_heat_cool.value == 1 -async def test_thermostat_without_target_temp_only_range( - hass, hk_driver, cls, events, driver -): +async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, events): """Test a thermostat that only supports a range.""" entity_id = "climate.test" @@ -1099,7 +1085,7 @@ async def test_thermostat_without_target_temp_only_range( ) await hass.async_block_till_done() acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) - driver.add_accessory(acc) + hk_driver.add_accessory(acc) await acc.run_handler() await hass.async_block_till_done() @@ -1176,7 +1162,7 @@ async def test_thermostat_without_target_temp_only_range( char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { @@ -1222,7 +1208,7 @@ async def test_thermostat_without_target_temp_only_range( char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID] - driver.set_characteristics( + hk_driver.set_characteristics( { HAP_REPR_CHARS: [ {