diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 5d7733584be..59c227fb0b8 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -173,6 +173,9 @@ def get_accessory(hass, driver, state, aid, config): elif state.domain in ('automation', 'input_boolean', 'remote', 'script'): a_type = 'Switch' + elif state.domain == 'water_heater': + a_type = 'WaterHeater' + if a_type is None: return None diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 6c143f7f0da..d35d38c6455 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -146,3 +146,7 @@ DEVICE_CLASS_WINDOW = 'window' # #### Thresholds #### THRESHOLD_CO = 25 THRESHOLD_CO2 = 1000 + +# #### Default values #### +DEFAULT_MIN_TEMP_WATER_HEATER = 40 # °C +DEFAULT_MAX_TEMP_WATER_HEATER = 60 # °C diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index a7344995021..2513ee50981 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -6,13 +6,18 @@ from pyhap.const import CATEGORY_THERMOSTAT from homeassistant.components.climate import ( ATTR_CURRENT_TEMPERATURE, ATTR_MAX_TEMP, ATTR_MIN_TEMP, ATTR_OPERATION_LIST, ATTR_OPERATION_MODE, - ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, - DOMAIN, SERVICE_SET_TEMPERATURE, SERVICE_SET_OPERATION_MODE, STATE_AUTO, - STATE_COOL, STATE_HEAT, SUPPORT_ON_OFF, SUPPORT_TARGET_TEMPERATURE_HIGH, - SUPPORT_TARGET_TEMPERATURE_LOW) + ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN as DOMAIN_CLIMATE, + SERVICE_SET_OPERATION_MODE as SERVICE_SET_OPERATION_MODE_THERMOSTAT, + SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, + STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_ON_OFF, + SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW) +from homeassistant.components.water_heater import ( + DOMAIN as DOMAIN_WATER_HEATER, + SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_WATER_HEATER) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_OFF, SERVICE_TURN_ON, + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, + SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) from . import TYPES @@ -21,7 +26,9 @@ from .const import ( CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_CURRENT_HEATING_COOLING, CHAR_CURRENT_TEMPERATURE, CHAR_TARGET_HEATING_COOLING, CHAR_HEATING_THRESHOLD_TEMPERATURE, CHAR_TARGET_TEMPERATURE, - CHAR_TEMP_DISPLAY_UNITS, PROP_MAX_VALUE, PROP_MIN_VALUE, SERV_THERMOSTAT) + CHAR_TEMP_DISPLAY_UNITS, + DEFAULT_MAX_TEMP_WATER_HEATER, DEFAULT_MIN_TEMP_WATER_HEATER, + PROP_MAX_VALUE, PROP_MIN_VALUE, SERV_THERMOSTAT) from .util import temperature_to_homekit, temperature_to_states _LOGGER = logging.getLogger(__name__) @@ -114,7 +121,7 @@ class Thermostat(HomeAccessory): return min_temp, max_temp def set_heat_cool(self, value): - """Move operation mode to value if call came from HomeKit.""" + """Change operation mode to value if call came from HomeKit.""" if value in HC_HOMEKIT_TO_HASS: _LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value) self.heat_cool_flag_target_state = True @@ -122,13 +129,14 @@ class Thermostat(HomeAccessory): if self.support_power_state is True: params = {ATTR_ENTITY_ID: self.entity_id} if hass_value == STATE_OFF: - self.call_service(DOMAIN, SERVICE_TURN_OFF, params) + self.call_service(DOMAIN_CLIMATE, SERVICE_TURN_OFF, params) return - self.hass.services.call(DOMAIN, SERVICE_TURN_ON, params) + self.call_service(DOMAIN_CLIMATE, SERVICE_TURN_ON, params) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_OPERATION_MODE: hass_value} self.call_service( - DOMAIN, SERVICE_SET_OPERATION_MODE, params, hass_value) + DOMAIN_CLIMATE, SERVICE_SET_OPERATION_MODE_THERMOSTAT, + params, hass_value) @debounce def set_cooling_threshold(self, value): @@ -142,9 +150,9 @@ class Thermostat(HomeAccessory): ATTR_ENTITY_ID: self.entity_id, ATTR_TARGET_TEMP_HIGH: temperature, ATTR_TARGET_TEMP_LOW: temperature_to_states(low, self._unit)} - self.call_service(DOMAIN, SERVICE_SET_TEMPERATURE, params, - "cooling threshold {}{}".format(temperature, - self._unit)) + self.call_service( + DOMAIN_CLIMATE, SERVICE_SET_TEMPERATURE_THERMOSTAT, + params, 'cooling threshold {}{}'.format(temperature, self._unit)) @debounce def set_heating_threshold(self, value): @@ -158,9 +166,9 @@ class Thermostat(HomeAccessory): ATTR_ENTITY_ID: self.entity_id, ATTR_TARGET_TEMP_HIGH: temperature_to_states(high, self._unit), ATTR_TARGET_TEMP_LOW: temperature} - self.call_service(DOMAIN, SERVICE_SET_TEMPERATURE, params, - "heating threshold {}{}".format(temperature, - self._unit)) + self.call_service( + DOMAIN_CLIMATE, SERVICE_SET_TEMPERATURE_THERMOSTAT, + params, 'heating threshold {}{}'.format(temperature, self._unit)) @debounce def set_target_temperature(self, value): @@ -172,12 +180,12 @@ class Thermostat(HomeAccessory): params = { ATTR_ENTITY_ID: self.entity_id, ATTR_TEMPERATURE: temperature} - self.call_service(DOMAIN, SERVICE_SET_TEMPERATURE, params, - "target {}{}".format(temperature, - self._unit)) + self.call_service( + DOMAIN_CLIMATE, SERVICE_SET_TEMPERATURE_THERMOSTAT, + params, 'target {}{}'.format(temperature, self._unit)) def update_state(self, new_state): - """Update security state after state changed.""" + """Update thermostat state after state changed.""" # Update current temperature current_temp = new_state.attributes.get(ATTR_CURRENT_TEMPERATURE) if isinstance(current_temp, (int, float)): @@ -268,3 +276,92 @@ class Thermostat(HomeAccessory): self.char_current_heat_cool.set_value( HC_HASS_TO_HOMEKIT[current_operation_mode]) + + +@TYPES.register('WaterHeater') +class WaterHeater(HomeAccessory): + """Generate a WaterHeater accessory for a water_heater.""" + + def __init__(self, *args): + """Initialize a WaterHeater accessory object.""" + super().__init__(*args, category=CATEGORY_THERMOSTAT) + self._unit = self.hass.config.units.temperature_unit + self.flag_heat_cool = False + self.flag_temperature = False + min_temp, max_temp = self.get_temperature_range() + + serv_thermostat = self.add_preload_service(SERV_THERMOSTAT) + + self.char_current_heat_cool = serv_thermostat.configure_char( + CHAR_CURRENT_HEATING_COOLING, value=1) + self.char_target_heat_cool = serv_thermostat.configure_char( + CHAR_TARGET_HEATING_COOLING, value=1, + setter_callback=self.set_heat_cool) + + self.char_current_temp = serv_thermostat.configure_char( + CHAR_CURRENT_TEMPERATURE, value=50.0) + self.char_target_temp = serv_thermostat.configure_char( + CHAR_TARGET_TEMPERATURE, value=50.0, + properties={PROP_MIN_VALUE: min_temp, + PROP_MAX_VALUE: max_temp}, + setter_callback=self.set_target_temperature) + + self.char_display_units = serv_thermostat.configure_char( + CHAR_TEMP_DISPLAY_UNITS, value=0) + + def get_temperature_range(self): + """Return min and max temperature range.""" + max_temp = self.hass.states.get(self.entity_id) \ + .attributes.get(ATTR_MAX_TEMP) + max_temp = temperature_to_homekit(max_temp, self._unit) if max_temp \ + else DEFAULT_MAX_TEMP_WATER_HEATER + + min_temp = self.hass.states.get(self.entity_id) \ + .attributes.get(ATTR_MIN_TEMP) + min_temp = temperature_to_homekit(min_temp, self._unit) if min_temp \ + else DEFAULT_MIN_TEMP_WATER_HEATER + + return min_temp, max_temp + + def set_heat_cool(self, value): + """Change operation mode to value if call came from HomeKit.""" + _LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value) + self.flag_heat_cool = True + hass_value = HC_HOMEKIT_TO_HASS[value] + if hass_value != STATE_HEAT: + self.char_target_heat_cool.set_value(1) # Heat + + @debounce + def set_target_temperature(self, value): + """Set target temperature to value if call came from HomeKit.""" + _LOGGER.debug('%s: Set target temperature to %.2f°C', + self.entity_id, value) + self.flag_temperature = True + temperature = temperature_to_states(value, self._unit) + params = { + ATTR_ENTITY_ID: self.entity_id, + ATTR_TEMPERATURE: temperature} + self.call_service( + DOMAIN_WATER_HEATER, SERVICE_SET_TEMPERATURE_WATER_HEATER, + params, 'target {}{}'.format(temperature, self._unit)) + + def update_state(self, new_state): + """Update water_heater state after state change.""" + # Update current and target temperature + temperature = new_state.attributes.get(ATTR_TEMPERATURE) + if isinstance(temperature, (int, float)): + temperature = temperature_to_homekit(temperature, self._unit) + self.char_current_temp.set_value(temperature) + if not self.flag_temperature: + self.char_target_temp.set_value(temperature) + self.flag_temperature = False + + # Update display units + if self._unit and self._unit in UNIT_HASS_TO_HOMEKIT: + self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit]) + + # Update target operation mode + operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE) + if operation_mode and not self.flag_heat_cool: + self.char_target_heat_cool.set_value(1) # Heat + self.flag_heat_cool = False diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index d5552cce82c..7d303c38e93 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -69,6 +69,7 @@ def test_customize_options(config, name): ('Thermostat', 'climate.test', 'auto', {ATTR_SUPPORTED_FEATURES: climate.SUPPORT_TARGET_TEMPERATURE_LOW | climate.SUPPORT_TARGET_TEMPERATURE_HIGH}, {}), + ('WaterHeater', 'water_heater.test', 'auto', {}, {}), ]) def test_types(type_name, entity_id, state, attrs, config): """Test if types are associated correctly.""" diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index e3187b6cf02..73b27ca851c 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -5,15 +5,18 @@ from unittest.mock import patch import pytest from homeassistant.components.climate import ( - ATTR_CURRENT_TEMPERATURE, ATTR_MAX_TEMP, ATTR_MIN_TEMP, ATTR_TEMPERATURE, + ATTR_CURRENT_TEMPERATURE, ATTR_MAX_TEMP, ATTR_MIN_TEMP, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, ATTR_OPERATION_MODE, - ATTR_OPERATION_LIST, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, - STATE_AUTO, STATE_COOL, STATE_HEAT) + ATTR_OPERATION_LIST, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, + DOMAIN as DOMAIN_CLIMATE, STATE_AUTO, STATE_COOL, STATE_HEAT) from homeassistant.components.homekit.const import ( - ATTR_VALUE, PROP_MAX_VALUE, PROP_MIN_VALUE) + ATTR_VALUE, DEFAULT_MAX_TEMP_WATER_HEATER, DEFAULT_MIN_TEMP_WATER_HEATER, + PROP_MAX_VALUE, PROP_MIN_VALUE) +from homeassistant.components.water_heater import ( + DOMAIN as DOMAIN_WATER_HEATER) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_TEMPERATURE_UNIT, STATE_OFF, - TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, ATTR_TEMPERATURE, ATTR_SUPPORTED_FEATURES, + CONF_TEMPERATURE_UNIT, STATE_OFF, TEMP_FAHRENHEIT) from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce @@ -25,13 +28,14 @@ def cls(): patcher = patch_debounce() patcher.start() _import = __import__('homeassistant.components.homekit.type_thermostats', - fromlist=['Thermostat']) - patcher_tuple = namedtuple('Cls', ['thermostat']) - yield patcher_tuple(thermostat=_import.Thermostat) + fromlist=['Thermostat', 'WaterHeater']) + patcher_tuple = namedtuple('Cls', ['thermostat', 'water_heater']) + yield patcher_tuple(thermostat=_import.Thermostat, + water_heater=_import.WaterHeater) patcher.stop() -async def test_default_thermostat(hass, hk_driver, cls, events): +async def test_thermostat(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = 'climate.test' @@ -147,8 +151,9 @@ async def test_default_thermostat(hass, hk_driver, cls, events): assert acc.char_display_units.value == 0 # Set from HomeKit - call_set_temperature = async_mock_service(hass, DOMAIN, 'set_temperature') - call_set_operation_mode = async_mock_service(hass, DOMAIN, + call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, + 'set_temperature') + call_set_operation_mode = async_mock_service(hass, DOMAIN_CLIMATE, 'set_operation_mode') await hass.async_add_job(acc.char_target_temp.client_update_value, 19.0) @@ -158,8 +163,7 @@ async def test_default_thermostat(hass, hk_driver, cls, events): assert call_set_temperature[0].data[ATTR_TEMPERATURE] == 19.0 assert acc.char_target_temp.value == 19.0 assert len(events) == 1 - assert events[-1].data[ATTR_VALUE] == "target {}°C".format( - acc.char_target_temp.value) + assert events[-1].data[ATTR_VALUE] == 'target 19.0°C' await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 1) await hass.async_block_till_done() @@ -171,7 +175,7 @@ async def test_default_thermostat(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] == STATE_HEAT -async def test_auto_thermostat(hass, hk_driver, cls, events): +async def test_thermostat_auto(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = 'climate.test' @@ -234,7 +238,8 @@ async def test_auto_thermostat(hass, hk_driver, cls, events): assert acc.char_display_units.value == 0 # Set from HomeKit - call_set_temperature = async_mock_service(hass, DOMAIN, 'set_temperature') + call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, + 'set_temperature') await hass.async_add_job( acc.char_heating_thresh_temp.client_update_value, 20.0) @@ -244,8 +249,7 @@ async def test_auto_thermostat(hass, hk_driver, cls, events): assert call_set_temperature[0].data[ATTR_TARGET_TEMP_LOW] == 20.0 assert acc.char_heating_thresh_temp.value == 20.0 assert len(events) == 1 - assert events[-1].data[ATTR_VALUE] == "heating threshold {}°C".format( - acc.char_heating_thresh_temp.value) + assert events[-1].data[ATTR_VALUE] == 'heating threshold 20.0°C' await hass.async_add_job( acc.char_cooling_thresh_temp.client_update_value, 25.0) @@ -255,11 +259,10 @@ async def test_auto_thermostat(hass, hk_driver, cls, events): assert call_set_temperature[1].data[ATTR_TARGET_TEMP_HIGH] == 25.0 assert acc.char_cooling_thresh_temp.value == 25.0 assert len(events) == 2 - assert events[-1].data[ATTR_VALUE] == "cooling threshold {}°C".format( - acc.char_cooling_thresh_temp.value) + assert events[-1].data[ATTR_VALUE] == 'cooling threshold 25.0°C' -async def test_power_state(hass, hk_driver, cls, events): +async def test_thermostat_power_state(hass, hk_driver, cls, events): """Test if accessory and HA are updated accordingly.""" entity_id = 'climate.test' @@ -295,9 +298,9 @@ async def test_power_state(hass, hk_driver, cls, events): assert acc.char_target_heat_cool.value == 0 # Set from HomeKit - call_turn_on = async_mock_service(hass, DOMAIN, 'turn_on') - call_turn_off = async_mock_service(hass, DOMAIN, 'turn_off') - call_set_operation_mode = async_mock_service(hass, DOMAIN, + call_turn_on = async_mock_service(hass, DOMAIN_CLIMATE, 'turn_on') + call_turn_off = async_mock_service(hass, DOMAIN_CLIMATE, 'turn_off') + call_set_operation_mode = async_mock_service(hass, DOMAIN_CLIMATE, 'set_operation_mode') await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 1) @@ -308,7 +311,7 @@ async def test_power_state(hass, hk_driver, cls, events): assert call_set_operation_mode[0].data[ATTR_ENTITY_ID] == entity_id assert call_set_operation_mode[0].data[ATTR_OPERATION_MODE] == STATE_HEAT assert acc.char_target_heat_cool.value == 1 - assert len(events) == 1 + assert len(events) == 2 assert events[-1].data[ATTR_VALUE] == STATE_HEAT await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 0) @@ -316,7 +319,7 @@ async def test_power_state(hass, hk_driver, cls, events): assert call_turn_off assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id assert acc.char_target_heat_cool.value == 0 - assert len(events) == 2 + assert len(events) == 3 assert events[-1].data[ATTR_VALUE] is None @@ -347,7 +350,8 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events): assert acc.char_display_units.value == 1 # Set from HomeKit - call_set_temperature = async_mock_service(hass, DOMAIN, 'set_temperature') + call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, + 'set_temperature') await hass.async_add_job( acc.char_cooling_thresh_temp.client_update_value, 23) @@ -357,7 +361,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events): assert call_set_temperature[0].data[ATTR_TARGET_TEMP_HIGH] == 73.4 assert call_set_temperature[0].data[ATTR_TARGET_TEMP_LOW] == 68 assert len(events) == 1 - assert events[-1].data[ATTR_VALUE] == "cooling threshold 73.4°F" + assert events[-1].data[ATTR_VALUE] == 'cooling threshold 73.4°F' await hass.async_add_job( acc.char_heating_thresh_temp.client_update_value, 22) @@ -367,7 +371,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events): assert call_set_temperature[1].data[ATTR_TARGET_TEMP_HIGH] == 73.4 assert call_set_temperature[1].data[ATTR_TARGET_TEMP_LOW] == 71.6 assert len(events) == 2 - assert events[-1].data[ATTR_VALUE] == "heating threshold 71.6°F" + assert events[-1].data[ATTR_VALUE] == 'heating threshold 71.6°F' await hass.async_add_job(acc.char_target_temp.client_update_value, 24.0) await hass.async_block_till_done() @@ -375,10 +379,10 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events): assert call_set_temperature[2].data[ATTR_ENTITY_ID] == entity_id assert call_set_temperature[2].data[ATTR_TEMPERATURE] == 75.2 assert len(events) == 3 - assert events[-1].data[ATTR_VALUE] == "target 75.2°F" + assert events[-1].data[ATTR_VALUE] == 'target 75.2°F' -async def test_get_temperature_range(hass, hk_driver, cls): +async def test_thermostat_get_temperature_range(hass, hk_driver, cls): """Test if temperature range is evaluated correctly.""" entity_id = 'climate.test' @@ -396,3 +400,123 @@ async def test_get_temperature_range(hass, hk_driver, cls): {ATTR_MIN_TEMP: 60, ATTR_MAX_TEMP: 70}) await hass.async_block_till_done() assert acc.get_temperature_range() == (15.6, 21.1) + + +async def test_water_heater(hass, hk_driver, cls, events): + """Test if accessory and HA are updated accordingly.""" + entity_id = 'water_heater.test' + + hass.states.async_set(entity_id, STATE_HEAT) + await hass.async_block_till_done() + acc = cls.water_heater(hass, hk_driver, 'WaterHeater', entity_id, 2, None) + await hass.async_add_job(acc.run) + await hass.async_block_till_done() + + assert acc.aid == 2 + assert acc.category == 9 # Thermostat + + assert acc.char_current_heat_cool.value == 1 # Heat + assert acc.char_target_heat_cool.value == 1 # Heat + assert acc.char_current_temp.value == 50.0 + assert acc.char_target_temp.value == 50.0 + assert acc.char_display_units.value == 0 + + assert acc.char_target_temp.properties[PROP_MAX_VALUE] == \ + DEFAULT_MAX_TEMP_WATER_HEATER + assert acc.char_target_temp.properties[PROP_MIN_VALUE] == \ + DEFAULT_MIN_TEMP_WATER_HEATER + + hass.states.async_set(entity_id, STATE_HEAT, + {ATTR_OPERATION_MODE: STATE_HEAT, + ATTR_TEMPERATURE: 56.0}) + await hass.async_block_till_done() + assert acc.char_target_temp.value == 56.0 + assert acc.char_current_temp.value == 56.0 + assert acc.char_target_heat_cool.value == 1 + assert acc.char_current_heat_cool.value == 1 + assert acc.char_display_units.value == 0 + + hass.states.async_set(entity_id, STATE_AUTO, + {ATTR_OPERATION_MODE: STATE_AUTO}) + await hass.async_block_till_done() + assert acc.char_target_heat_cool.value == 1 + assert acc.char_current_heat_cool.value == 1 + + # Set from HomeKit + call_set_temperature = async_mock_service(hass, DOMAIN_WATER_HEATER, + 'set_temperature') + + await hass.async_add_job(acc.char_target_temp.client_update_value, 52.0) + await hass.async_block_till_done() + assert call_set_temperature + assert call_set_temperature[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_temperature[0].data[ATTR_TEMPERATURE] == 52.0 + assert acc.char_target_temp.value == 52.0 + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == 'target 52.0°C' + + await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 0) + await hass.async_block_till_done() + assert acc.char_target_heat_cool.value == 1 + + await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 2) + await hass.async_block_till_done() + assert acc.char_target_heat_cool.value == 1 + + await hass.async_add_job(acc.char_target_heat_cool.client_update_value, 3) + await hass.async_block_till_done() + assert acc.char_target_heat_cool.value == 1 + + +async def test_water_heater_fahrenheit(hass, hk_driver, cls, events): + """Test if accessory and HA are update accordingly.""" + entity_id = 'water_heater.test' + + hass.states.async_set(entity_id, STATE_HEAT) + await hass.async_block_till_done() + with patch.object(hass.config.units, CONF_TEMPERATURE_UNIT, + new=TEMP_FAHRENHEIT): + acc = cls.water_heater(hass, hk_driver, 'WaterHeater', + entity_id, 2, None) + await hass.async_add_job(acc.run) + await hass.async_block_till_done() + + hass.states.async_set(entity_id, STATE_HEAT, + {ATTR_TEMPERATURE: 131}) + await hass.async_block_till_done() + assert acc.char_target_temp.value == 55.0 + assert acc.char_current_temp.value == 55.0 + assert acc.char_display_units.value == 1 + + # Set from HomeKit + call_set_temperature = async_mock_service(hass, DOMAIN_WATER_HEATER, + 'set_temperature') + + await hass.async_add_job(acc.char_target_temp.client_update_value, 60) + await hass.async_block_till_done() + assert call_set_temperature + assert call_set_temperature[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_temperature[0].data[ATTR_TEMPERATURE] == 140.0 + assert acc.char_target_temp.value == 60.0 + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == 'target 140.0°F' + + +async def test_water_heater_get_temperature_range(hass, hk_driver, cls): + """Test if temperature range is evaluated correctly.""" + entity_id = 'water_heater.test' + + hass.states.async_set(entity_id, STATE_HEAT) + await hass.async_block_till_done() + acc = cls.thermostat(hass, hk_driver, 'Climate', entity_id, 2, None) + + hass.states.async_set(entity_id, STATE_HEAT, + {ATTR_MIN_TEMP: 20, ATTR_MAX_TEMP: 25}) + await hass.async_block_till_done() + assert acc.get_temperature_range() == (20, 25) + + acc._unit = TEMP_FAHRENHEIT + hass.states.async_set(entity_id, STATE_OFF, + {ATTR_MIN_TEMP: 60, ATTR_MAX_TEMP: 70}) + await hass.async_block_till_done() + assert acc.get_temperature_range() == (15.6, 21.1)