From a97fb8fd10af84df15bb952342c1bb050dee6a67 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Wed, 17 Apr 2019 18:03:32 +0100 Subject: [PATCH] Support fetching/setting humidity of HomeKit controller thermostats (#23040) * Add support for homekit humidity control * Add tests --- .../components/homekit_controller/climate.py | 32 ++++++++++- .../specific_devices/test_ecobee3.py | 6 +- .../homekit_controller/test_climate.py | 57 +++++++++++++++++-- 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index d8bf1359689..2cbd8f6d700 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -4,7 +4,7 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE) + SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY) from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS from . import KNOWN_DEVICES, HomeKitEntity @@ -41,6 +41,8 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): self._valid_modes = [] self._current_temp = None self._target_temp = None + self._current_humidity = None + self._target_humidity = None super().__init__(*args) def get_characteristic_types(self): @@ -52,6 +54,8 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): CharacteristicsTypes.HEATING_COOLING_TARGET, CharacteristicsTypes.TEMPERATURE_CURRENT, CharacteristicsTypes.TEMPERATURE_TARGET, + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT, + CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET, ] def _setup_heating_cooling_target(self, characteristic): @@ -82,6 +86,9 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): def _setup_temperature_target(self, characteristic): self._features |= SUPPORT_TARGET_TEMPERATURE + def _setup_relative_humidity_target(self, characteristic): + self._features |= SUPPORT_TARGET_HUMIDITY + def _update_heating_cooling_current(self, value): self._state = MODE_HOMEKIT_TO_HASS.get(value) @@ -94,6 +101,12 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): def _update_temperature_target(self, value): self._target_temp = value + def _update_relative_humidity_current(self, value): + self._current_humidity = value + + def _update_relative_humidity_target(self, value): + self._target_humidity = value + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) @@ -103,6 +116,13 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): 'value': temp}] await self._accessory.put_characteristics(characteristics) + async def async_set_humidity(self, humidity): + """Set new target humidity.""" + characteristics = [{'aid': self._aid, + 'iid': self._chars['relative-humidity.target'], + 'value': humidity}] + await self._accessory.put_characteristics(characteristics) + async def async_set_operation_mode(self, operation_mode): """Set new target operation mode.""" characteristics = [{'aid': self._aid, @@ -132,6 +152,16 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): """Return the temperature we try to reach.""" return self._target_temp + @property + def current_humidity(self): + """Return the current humidity.""" + return self._current_humidity + + @property + def target_humidity(self): + """Return the humidity we try to reach.""" + return self._target_humidity + @property def current_operation(self): """Return current operation ie. heat, cool, idle.""" diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index ad61d9db4d3..0831cd5b780 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -5,7 +5,8 @@ https://github.com/home-assistant/home-assistant/issues/15336 """ from homeassistant.components.climate.const import ( - SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) + SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY, + SUPPORT_OPERATION_MODE) from tests.components.homekit_controller.common import ( device_config_changed, setup_accessories_from_file, setup_test_accessories, Helper @@ -26,7 +27,8 @@ async def test_ecobee3_setup(hass): climate_state = await climate_helper.poll_and_get_state() assert climate_state.attributes['friendly_name'] == 'HomeW' assert climate_state.attributes['supported_features'] == ( - SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE + SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY | + SUPPORT_OPERATION_MODE ) occ1 = entity_registry.async_get('binary_sensor.kitchen') diff --git a/tests/components/homekit_controller/test_climate.py b/tests/components/homekit_controller/test_climate.py index 18e54644b5a..4c0a5debb65 100644 --- a/tests/components/homekit_controller/test_climate.py +++ b/tests/components/homekit_controller/test_climate.py @@ -1,6 +1,7 @@ """Basic checks for HomeKitclimate.""" from homeassistant.components.climate.const import ( - DOMAIN, SERVICE_SET_OPERATION_MODE, SERVICE_SET_TEMPERATURE) + DOMAIN, SERVICE_SET_OPERATION_MODE, SERVICE_SET_TEMPERATURE, + SERVICE_SET_HUMIDITY) from tests.components.homekit_controller.common import ( FakeService, setup_test_component) @@ -9,6 +10,33 @@ HEATING_COOLING_TARGET = ('thermostat', 'heating-cooling.target') HEATING_COOLING_CURRENT = ('thermostat', 'heating-cooling.current') TEMPERATURE_TARGET = ('thermostat', 'temperature.target') TEMPERATURE_CURRENT = ('thermostat', 'temperature.current') +HUMIDITY_TARGET = ('thermostat', 'relative-humidity.target') +HUMIDITY_CURRENT = ('thermostat', 'relative-humidity.current') + + +def create_thermostat_service(): + """Define thermostat characteristics.""" + service = FakeService('public.hap.service.thermostat') + + char = service.add_characteristic('heating-cooling.target') + char.value = 0 + + char = service.add_characteristic('heating-cooling.current') + char.value = 0 + + char = service.add_characteristic('temperature.target') + char.value = 0 + + char = service.add_characteristic('temperature.current') + char.value = 0 + + char = service.add_characteristic('relative-humidity.target') + char.value = 0 + + char = service.add_characteristic('relative-humidity.current') + char.value = 0 + + return service async def test_climate_respect_supported_op_modes_1(hass, utcnow): @@ -77,28 +105,49 @@ async def test_climate_change_thermostat_temperature(hass, utcnow): assert helper.characteristics[TEMPERATURE_TARGET].value == 25 +async def test_climate_change_thermostat_humidity(hass, utcnow): + """Test that we can turn a HomeKit thermostat on and off again.""" + helper = await setup_test_component(hass, [create_thermostat_service()]) + + await hass.services.async_call(DOMAIN, SERVICE_SET_HUMIDITY, { + 'entity_id': 'climate.testdevice', + 'humidity': 50, + }, blocking=True) + assert helper.characteristics[HUMIDITY_TARGET].value == 50 + + await hass.services.async_call(DOMAIN, SERVICE_SET_HUMIDITY, { + 'entity_id': 'climate.testdevice', + 'humidity': 45, + }, blocking=True) + assert helper.characteristics[HUMIDITY_TARGET].value == 45 + + async def test_climate_read_thermostat_state(hass, utcnow): """Test that we can read the state of a HomeKit thermostat accessory.""" - from homekit.model.services import ThermostatService - - helper = await setup_test_component(hass, [ThermostatService()]) + helper = await setup_test_component(hass, [create_thermostat_service()]) # Simulate that heating is on helper.characteristics[TEMPERATURE_CURRENT].value = 19 helper.characteristics[TEMPERATURE_TARGET].value = 21 helper.characteristics[HEATING_COOLING_CURRENT].value = 1 helper.characteristics[HEATING_COOLING_TARGET].value = 1 + helper.characteristics[HUMIDITY_CURRENT].value = 50 + helper.characteristics[HUMIDITY_TARGET].value = 45 state = await helper.poll_and_get_state() assert state.state == 'heat' assert state.attributes['current_temperature'] == 19 + assert state.attributes['current_humidity'] == 50 # Simulate that cooling is on helper.characteristics[TEMPERATURE_CURRENT].value = 21 helper.characteristics[TEMPERATURE_TARGET].value = 19 helper.characteristics[HEATING_COOLING_CURRENT].value = 2 helper.characteristics[HEATING_COOLING_TARGET].value = 2 + helper.characteristics[HUMIDITY_CURRENT].value = 45 + helper.characteristics[HUMIDITY_TARGET].value = 45 state = await helper.poll_and_get_state() assert state.state == 'cool' assert state.attributes['current_temperature'] == 21 + assert state.attributes['current_humidity'] == 45