From b315fcab1160e8daa272a8b75719b1382ca68083 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 08:43:22 -1000 Subject: [PATCH] Fix turn on without speed in homekit controller (#47597) --- .../components/homekit_controller/fan.py | 9 +- .../components/homekit_controller/conftest.py | 7 ++ .../components/homekit_controller/test_fan.py | 97 +++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index e2cdf7b3cfd..591050f5fd9 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -80,6 +80,13 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): return features + @property + def speed_count(self): + """Speed count for the fan.""" + return round( + 100 / max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0) + ) + async def async_set_direction(self, direction): """Set the direction of the fan.""" await self.async_put_characteristics( @@ -110,7 +117,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): if not self.is_on: characteristics[self.on_characteristic] = True - if self.supported_features & SUPPORT_SET_SPEED: + if percentage is not None and self.supported_features & SUPPORT_SET_SPEED: characteristics[CharacteristicsTypes.ROTATION_SPEED] = percentage if characteristics: diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 4e095b1d2d9..3cde3912709 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -11,6 +11,13 @@ import homeassistant.util.dt as dt_util from tests.components.light.conftest import mock_light_profiles # noqa: F401 +@pytest.fixture(autouse=True) +def mock_zeroconf(): + """Mock zeroconf.""" + with mock.patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc: + yield mock_zc.return_value + + @pytest.fixture def utcnow(request): """Freeze time at a known point.""" diff --git a/tests/components/homekit_controller/test_fan.py b/tests/components/homekit_controller/test_fan.py index b8d42b21643..d66ce81d534 100644 --- a/tests/components/homekit_controller/test_fan.py +++ b/tests/components/homekit_controller/test_fan.py @@ -50,6 +50,38 @@ def create_fanv2_service(accessory): swing_mode.value = 0 +def create_fanv2_service_with_min_step(accessory): + """Define fan v2 characteristics as per HAP spec.""" + service = accessory.add_service(ServicesTypes.FAN_V2) + + cur_state = service.add_char(CharacteristicsTypes.ACTIVE) + cur_state.value = 0 + + direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION) + direction.value = 0 + + speed = service.add_char(CharacteristicsTypes.ROTATION_SPEED) + speed.value = 0 + speed.minStep = 25 + + swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE) + swing_mode.value = 0 + + +def create_fanv2_service_without_rotation_speed(accessory): + """Define fan v2 characteristics as per HAP spec.""" + service = accessory.add_service(ServicesTypes.FAN_V2) + + cur_state = service.add_char(CharacteristicsTypes.ACTIVE) + cur_state.value = 0 + + direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION) + direction.value = 0 + + swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE) + swing_mode.value = 0 + + async def test_fan_read_state(hass, utcnow): """Test that we can read the state of a HomeKit fan accessory.""" helper = await setup_test_component(hass, create_fan_service) @@ -95,6 +127,29 @@ async def test_turn_on(hass, utcnow): assert helper.characteristics[V1_ROTATION_SPEED].value == 33.0 +async def test_turn_on_off_without_rotation_speed(hass, utcnow): + """Test that we can turn a fan on.""" + helper = await setup_test_component( + hass, create_fanv2_service_without_rotation_speed + ) + + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 1 + + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 0 + + async def test_turn_off(hass, utcnow): """Test that we can turn a fan off.""" helper = await setup_test_component(hass, create_fan_service) @@ -181,6 +236,7 @@ async def test_speed_read(hass, utcnow): state = await helper.poll_and_get_state() assert state.attributes["speed"] == "high" assert state.attributes["percentage"] == 100 + assert state.attributes["percentage_step"] == 1.0 helper.characteristics[V1_ROTATION_SPEED].value = 50 state = await helper.poll_and_get_state() @@ -277,6 +333,24 @@ async def test_v2_turn_on(hass, utcnow): assert helper.characteristics[V2_ACTIVE].value == 1 assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 0 + assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 1 + assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + async def test_v2_turn_off(hass, utcnow): """Test that we can turn a fan off.""" @@ -355,6 +429,29 @@ async def test_v2_set_percentage(hass, utcnow): assert helper.characteristics[V2_ACTIVE].value == 0 +async def test_v2_set_percentage_with_min_step(hass, utcnow): + """Test that we set fan speed by percentage.""" + helper = await setup_test_component(hass, create_fanv2_service_with_min_step) + + helper.characteristics[V2_ACTIVE].value = 1 + + await hass.services.async_call( + "fan", + "set_percentage", + {"entity_id": "fan.testdevice", "percentage": 66}, + blocking=True, + ) + assert helper.characteristics[V2_ROTATION_SPEED].value == 75 + + await hass.services.async_call( + "fan", + "set_percentage", + {"entity_id": "fan.testdevice", "percentage": 0}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 0 + + async def test_v2_speed_read(hass, utcnow): """Test that we can read a fans oscillation.""" helper = await setup_test_component(hass, create_fanv2_service)