mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Convert homekit thermostats to use service callbacks (#34073)
* Convert homekit thermostats to use service callbacks Service callbacks allow us to get all the temperature changes in one request so we can avoid all the need to store state and debounce. * remove excess debug * Fix lock and light tests * Ensure all code for Thermostats has coverage * I am answering all the homekit cases anyways so might as well be aware of regressions * Make lock notifications reliable * Update homeassistant/components/homekit/type_lights.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
173d7fa060
commit
5d649b2541
@ -162,6 +162,7 @@ homeassistant/components/hisense_aehw4a1/* @bannhead
|
||||
homeassistant/components/history/* @home-assistant/core
|
||||
homeassistant/components/hive/* @Rendili @KJonline
|
||||
homeassistant/components/homeassistant/* @home-assistant/core
|
||||
homeassistant/components/homekit/* @bdraco
|
||||
homeassistant/components/homekit_controller/* @Jc2k
|
||||
homeassistant/components/homematic/* @pvizeli @danielperna84
|
||||
homeassistant/components/homematicip_cloud/* @SukramJ
|
||||
|
@ -3,5 +3,5 @@
|
||||
"name": "HomeKit",
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit",
|
||||
"requirements": ["HAP-python==2.8.2"],
|
||||
"codeowners": []
|
||||
"codeowners": ["@bdraco"]
|
||||
}
|
||||
|
@ -54,9 +54,9 @@ class Light(HomeAccessory):
|
||||
super().__init__(*args, category=CATEGORY_LIGHTBULB)
|
||||
|
||||
self.chars = []
|
||||
self._features = self.hass.states.get(self.entity_id).attributes.get(
|
||||
ATTR_SUPPORTED_FEATURES
|
||||
)
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
|
||||
self._features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
if self._features & SUPPORT_BRIGHTNESS:
|
||||
self.chars.append(CHAR_BRIGHTNESS)
|
||||
@ -101,10 +101,12 @@ class Light(HomeAccessory):
|
||||
if CHAR_SATURATION in self.chars:
|
||||
self.char_saturation = serv_light.configure_char(CHAR_SATURATION, value=75)
|
||||
|
||||
self.update_state(state)
|
||||
|
||||
serv_light.setter_callback = self._set_chars
|
||||
|
||||
def _set_chars(self, char_values):
|
||||
_LOGGER.debug("_set_chars: %s", char_values)
|
||||
_LOGGER.debug("Light _set_chars: %s", char_values)
|
||||
events = []
|
||||
service = SERVICE_TURN_ON
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
|
@ -35,7 +35,7 @@ class Lock(HomeAccessory):
|
||||
"""Initialize a Lock accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_DOOR_LOCK)
|
||||
self._code = self.config.get(ATTR_CODE)
|
||||
self._flag_state = False
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
|
||||
serv_lock_mechanism = self.add_preload_service(SERV_LOCK)
|
||||
self.char_current_state = serv_lock_mechanism.configure_char(
|
||||
@ -46,15 +46,18 @@ class Lock(HomeAccessory):
|
||||
value=HASS_TO_HOMEKIT[STATE_LOCKED],
|
||||
setter_callback=self.set_state,
|
||||
)
|
||||
self.update_state(state)
|
||||
|
||||
def set_state(self, value):
|
||||
"""Set lock state to value if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set state to %d", self.entity_id, value)
|
||||
self._flag_state = True
|
||||
|
||||
hass_value = HOMEKIT_TO_HASS.get(value)
|
||||
service = STATE_TO_SERVICE[hass_value]
|
||||
|
||||
if self.char_current_state.value != value:
|
||||
self.char_current_state.set_value(value)
|
||||
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
if self._code:
|
||||
params[ATTR_CODE] = self._code
|
||||
@ -65,16 +68,21 @@ class Lock(HomeAccessory):
|
||||
hass_state = new_state.state
|
||||
if hass_state in HASS_TO_HOMEKIT:
|
||||
current_lock_state = HASS_TO_HOMEKIT[hass_state]
|
||||
self.char_current_state.set_value(current_lock_state)
|
||||
_LOGGER.debug(
|
||||
"%s: Updated current state to %s (%d)",
|
||||
self.entity_id,
|
||||
hass_state,
|
||||
current_lock_state,
|
||||
)
|
||||
|
||||
# LockTargetState only supports locked and unlocked
|
||||
# Must set lock target state before current state
|
||||
# or there will be no notification
|
||||
if hass_state in (STATE_LOCKED, STATE_UNLOCKED):
|
||||
if not self._flag_state:
|
||||
if self.char_target_state.value != current_lock_state:
|
||||
self.char_target_state.set_value(current_lock_state)
|
||||
self._flag_state = False
|
||||
|
||||
# Set lock current state ONLY after ensuring that
|
||||
# target state is correct or there will be no
|
||||
# notification
|
||||
if self.char_current_state.value != current_lock_state:
|
||||
self.char_current_state.set_value(current_lock_state)
|
||||
|
@ -36,6 +36,7 @@ from homeassistant.components.climate.const import (
|
||||
SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT,
|
||||
SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT,
|
||||
SUPPORT_TARGET_HUMIDITY,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
)
|
||||
from homeassistant.components.water_heater import (
|
||||
@ -52,7 +53,7 @@ from homeassistant.const import (
|
||||
)
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, debounce
|
||||
from .accessories import HomeAccessory
|
||||
from .const import (
|
||||
CHAR_COOLING_THRESHOLD_TEMPERATURE,
|
||||
CHAR_CURRENT_HEATING_COOLING,
|
||||
@ -76,26 +77,37 @@ _LOGGER = logging.getLogger(__name__)
|
||||
HC_HOMEKIT_VALID_MODES_WATER_HEATER = {"Heat": 1}
|
||||
UNIT_HASS_TO_HOMEKIT = {TEMP_CELSIUS: 0, TEMP_FAHRENHEIT: 1}
|
||||
|
||||
HC_HEAT_COOL_OFF = 0
|
||||
HC_HEAT_COOL_HEAT = 1
|
||||
HC_HEAT_COOL_COOL = 2
|
||||
HC_HEAT_COOL_AUTO = 3
|
||||
|
||||
HC_MIN_TEMP = 10
|
||||
HC_MAX_TEMP = 38
|
||||
|
||||
UNIT_HOMEKIT_TO_HASS = {c: s for s, c in UNIT_HASS_TO_HOMEKIT.items()}
|
||||
HC_HASS_TO_HOMEKIT = {
|
||||
HVAC_MODE_OFF: 0,
|
||||
HVAC_MODE_HEAT: 1,
|
||||
HVAC_MODE_COOL: 2,
|
||||
HVAC_MODE_AUTO: 3,
|
||||
HVAC_MODE_HEAT_COOL: 3,
|
||||
HVAC_MODE_FAN_ONLY: 2,
|
||||
HVAC_MODE_OFF: HC_HEAT_COOL_OFF,
|
||||
HVAC_MODE_HEAT: HC_HEAT_COOL_HEAT,
|
||||
HVAC_MODE_COOL: HC_HEAT_COOL_COOL,
|
||||
HVAC_MODE_AUTO: HC_HEAT_COOL_AUTO,
|
||||
HVAC_MODE_HEAT_COOL: HC_HEAT_COOL_AUTO,
|
||||
HVAC_MODE_DRY: HC_HEAT_COOL_COOL,
|
||||
HVAC_MODE_FAN_ONLY: HC_HEAT_COOL_COOL,
|
||||
}
|
||||
HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()}
|
||||
|
||||
HC_HASS_TO_HOMEKIT_ACTION = {
|
||||
CURRENT_HVAC_OFF: 0,
|
||||
CURRENT_HVAC_IDLE: 0,
|
||||
CURRENT_HVAC_HEAT: 1,
|
||||
CURRENT_HVAC_COOL: 2,
|
||||
CURRENT_HVAC_DRY: 2,
|
||||
CURRENT_HVAC_FAN: 2,
|
||||
CURRENT_HVAC_OFF: HC_HEAT_COOL_OFF,
|
||||
CURRENT_HVAC_IDLE: HC_HEAT_COOL_OFF,
|
||||
CURRENT_HVAC_HEAT: HC_HEAT_COOL_HEAT,
|
||||
CURRENT_HVAC_COOL: HC_HEAT_COOL_COOL,
|
||||
CURRENT_HVAC_DRY: HC_HEAT_COOL_COOL,
|
||||
CURRENT_HVAC_FAN: HC_HEAT_COOL_COOL,
|
||||
}
|
||||
|
||||
HEAT_COOL_DEADBAND = 5
|
||||
|
||||
|
||||
@TYPES.register("Thermostat")
|
||||
class Thermostat(HomeAccessory):
|
||||
@ -105,12 +117,12 @@ class Thermostat(HomeAccessory):
|
||||
"""Initialize a Thermostat 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
|
||||
self._flag_coolingthresh = False
|
||||
self._flag_heatingthresh = False
|
||||
min_temp, max_temp = self.get_temperature_range()
|
||||
|
||||
# Homekit only supports 10-38
|
||||
hc_min_temp = max(min_temp, HC_MIN_TEMP)
|
||||
hc_max_temp = min(max_temp, HC_MAX_TEMP)
|
||||
|
||||
min_humidity = self.hass.states.get(self.entity_id).attributes.get(
|
||||
ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY
|
||||
)
|
||||
@ -174,24 +186,22 @@ class Thermostat(HomeAccessory):
|
||||
hc_valid_values = {k: v for v, k in self.hc_homekit_to_hass.items()}
|
||||
|
||||
self.char_target_heat_cool = serv_thermostat.configure_char(
|
||||
CHAR_TARGET_HEATING_COOLING,
|
||||
value=list(hc_valid_values.values())[0],
|
||||
setter_callback=self.set_heat_cool,
|
||||
valid_values=hc_valid_values,
|
||||
CHAR_TARGET_HEATING_COOLING, valid_values=hc_valid_values,
|
||||
)
|
||||
|
||||
# Current and target temperature characteristics
|
||||
|
||||
self.char_current_temp = serv_thermostat.configure_char(
|
||||
CHAR_CURRENT_TEMPERATURE, value=21.0
|
||||
)
|
||||
|
||||
self.char_target_temp = serv_thermostat.configure_char(
|
||||
CHAR_TARGET_TEMPERATURE,
|
||||
value=21.0,
|
||||
# We do not set PROP_MIN_STEP here and instead use the HomeKit
|
||||
# default of 0.1 in order to have enough precision to convert
|
||||
# temperature units and avoid setting to 73F will result in 74F
|
||||
properties={PROP_MIN_VALUE: min_temp, PROP_MAX_VALUE: max_temp},
|
||||
setter_callback=self.set_target_temperature,
|
||||
properties={PROP_MIN_VALUE: hc_min_temp, PROP_MAX_VALUE: hc_max_temp},
|
||||
)
|
||||
|
||||
# Display units characteristic
|
||||
@ -209,8 +219,7 @@ class Thermostat(HomeAccessory):
|
||||
# We do not set PROP_MIN_STEP here and instead use the HomeKit
|
||||
# default of 0.1 in order to have enough precision to convert
|
||||
# temperature units and avoid setting to 73F will result in 74F
|
||||
properties={PROP_MIN_VALUE: min_temp, PROP_MAX_VALUE: max_temp},
|
||||
setter_callback=self.set_cooling_threshold,
|
||||
properties={PROP_MIN_VALUE: hc_min_temp, PROP_MAX_VALUE: hc_max_temp},
|
||||
)
|
||||
if CHAR_HEATING_THRESHOLD_TEMPERATURE in self.chars:
|
||||
self.char_heating_thresh_temp = serv_thermostat.configure_char(
|
||||
@ -219,8 +228,7 @@ class Thermostat(HomeAccessory):
|
||||
# We do not set PROP_MIN_STEP here and instead use the HomeKit
|
||||
# default of 0.1 in order to have enough precision to convert
|
||||
# temperature units and avoid setting to 73F will result in 74F
|
||||
properties={PROP_MIN_VALUE: min_temp, PROP_MAX_VALUE: max_temp},
|
||||
setter_callback=self.set_heating_threshold,
|
||||
properties={PROP_MIN_VALUE: hc_min_temp, PROP_MAX_VALUE: hc_max_temp},
|
||||
)
|
||||
self.char_target_humidity = None
|
||||
self.char_current_humidity = None
|
||||
@ -234,43 +242,134 @@ class Thermostat(HomeAccessory):
|
||||
# of 80% homekit will give you the options 20%-100% instead
|
||||
# of 0-80%
|
||||
properties={PROP_MIN_VALUE: min_humidity},
|
||||
setter_callback=self.set_target_humidity,
|
||||
)
|
||||
self.char_current_humidity = serv_thermostat.configure_char(
|
||||
CHAR_CURRENT_HUMIDITY, value=50
|
||||
)
|
||||
|
||||
self.update_state(state)
|
||||
|
||||
serv_thermostat.setter_callback = self._set_chars
|
||||
|
||||
def _temperature_to_homekit(self, temp):
|
||||
return temperature_to_homekit(temp, self._unit)
|
||||
|
||||
def _temperature_to_states(self, temp):
|
||||
return temperature_to_states(temp, self._unit)
|
||||
|
||||
def _set_chars(self, char_values):
|
||||
_LOGGER.debug("Thermostat _set_chars: %s", char_values)
|
||||
events = []
|
||||
params = {}
|
||||
service = None
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
hvac_mode = self.hass.states.get(self.entity_id).state
|
||||
homekit_hvac_mode = HC_HASS_TO_HOMEKIT[hvac_mode]
|
||||
|
||||
if CHAR_TARGET_HEATING_COOLING in char_values:
|
||||
# Homekit will reset the mode when VIEWING the temp
|
||||
# Ignore it if its the same mode
|
||||
if char_values[CHAR_TARGET_HEATING_COOLING] != homekit_hvac_mode:
|
||||
service = SERVICE_SET_HVAC_MODE_THERMOSTAT
|
||||
hass_value = self.hc_homekit_to_hass[
|
||||
char_values[CHAR_TARGET_HEATING_COOLING]
|
||||
]
|
||||
params = {ATTR_HVAC_MODE: hass_value}
|
||||
events.append(
|
||||
f"{CHAR_TARGET_HEATING_COOLING} to {char_values[CHAR_TARGET_HEATING_COOLING]}"
|
||||
)
|
||||
|
||||
if CHAR_TARGET_TEMPERATURE in char_values:
|
||||
hc_target_temp = char_values[CHAR_TARGET_TEMPERATURE]
|
||||
if features & SUPPORT_TARGET_TEMPERATURE:
|
||||
service = SERVICE_SET_TEMPERATURE_THERMOSTAT
|
||||
temperature = self._temperature_to_states(hc_target_temp)
|
||||
events.append(
|
||||
f"{CHAR_TARGET_TEMPERATURE} to {char_values[CHAR_TARGET_TEMPERATURE]}°C"
|
||||
)
|
||||
params[ATTR_TEMPERATURE] = temperature
|
||||
elif features & SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||
# Homekit will send us a target temperature
|
||||
# even if the device does not support it
|
||||
_LOGGER.debug(
|
||||
"Homekit requested target temp: %s and the device does not support",
|
||||
hc_target_temp,
|
||||
)
|
||||
if (
|
||||
homekit_hvac_mode == HC_HEAT_COOL_HEAT
|
||||
and CHAR_HEATING_THRESHOLD_TEMPERATURE not in char_values
|
||||
):
|
||||
char_values[CHAR_HEATING_THRESHOLD_TEMPERATURE] = hc_target_temp
|
||||
if (
|
||||
homekit_hvac_mode == HC_HEAT_COOL_COOL
|
||||
and CHAR_COOLING_THRESHOLD_TEMPERATURE not in char_values
|
||||
):
|
||||
char_values[CHAR_COOLING_THRESHOLD_TEMPERATURE] = hc_target_temp
|
||||
|
||||
if (
|
||||
CHAR_HEATING_THRESHOLD_TEMPERATURE in char_values
|
||||
or CHAR_COOLING_THRESHOLD_TEMPERATURE in char_values
|
||||
):
|
||||
service = SERVICE_SET_TEMPERATURE_THERMOSTAT
|
||||
high = self.char_cooling_thresh_temp.value
|
||||
low = self.char_heating_thresh_temp.value
|
||||
min_temp, max_temp = self.get_temperature_range()
|
||||
if CHAR_COOLING_THRESHOLD_TEMPERATURE in char_values:
|
||||
events.append(
|
||||
f"{CHAR_COOLING_THRESHOLD_TEMPERATURE} to {char_values[CHAR_COOLING_THRESHOLD_TEMPERATURE]}°C"
|
||||
)
|
||||
high = char_values[CHAR_COOLING_THRESHOLD_TEMPERATURE]
|
||||
# If the device doesn't support TARGET_TEMPATURE
|
||||
# this can happen
|
||||
if high < low:
|
||||
low = high - HEAT_COOL_DEADBAND
|
||||
if CHAR_HEATING_THRESHOLD_TEMPERATURE in char_values:
|
||||
events.append(
|
||||
f"{CHAR_HEATING_THRESHOLD_TEMPERATURE} to {char_values[CHAR_HEATING_THRESHOLD_TEMPERATURE]}°C"
|
||||
)
|
||||
low = char_values[CHAR_HEATING_THRESHOLD_TEMPERATURE]
|
||||
# If the device doesn't support TARGET_TEMPATURE
|
||||
# this can happen
|
||||
if low > high:
|
||||
high = low + HEAT_COOL_DEADBAND
|
||||
|
||||
high = min(high, max_temp)
|
||||
low = max(low, min_temp)
|
||||
|
||||
params.update(
|
||||
{
|
||||
ATTR_TARGET_TEMP_HIGH: self._temperature_to_states(high),
|
||||
ATTR_TARGET_TEMP_LOW: self._temperature_to_states(low),
|
||||
}
|
||||
)
|
||||
|
||||
if service:
|
||||
params[ATTR_ENTITY_ID] = self.entity_id
|
||||
self.call_service(
|
||||
DOMAIN_CLIMATE, service, params, ", ".join(events),
|
||||
)
|
||||
|
||||
if CHAR_TARGET_HUMIDITY in char_values:
|
||||
self.set_target_humidity(char_values[CHAR_TARGET_HUMIDITY])
|
||||
|
||||
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
|
||||
self._temperature_to_homekit(max_temp) if max_temp else DEFAULT_MAX_TEMP
|
||||
)
|
||||
max_temp = round(max_temp * 2) / 2
|
||||
|
||||
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
|
||||
self._temperature_to_homekit(min_temp) if min_temp else DEFAULT_MIN_TEMP
|
||||
)
|
||||
min_temp = round(min_temp * 2) / 2
|
||||
|
||||
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 = self.hc_homekit_to_hass[value]
|
||||
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_HVAC_MODE: hass_value}
|
||||
self.call_service(
|
||||
DOMAIN_CLIMATE, SERVICE_SET_HVAC_MODE_THERMOSTAT, params, hass_value
|
||||
)
|
||||
|
||||
@debounce
|
||||
def set_target_humidity(self, value):
|
||||
"""Set target humidity to value if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set target humidity to %d", self.entity_id, value)
|
||||
@ -279,126 +378,85 @@ class Thermostat(HomeAccessory):
|
||||
DOMAIN_CLIMATE, SERVICE_SET_HUMIDITY, params, f"{value}{UNIT_PERCENTAGE}"
|
||||
)
|
||||
|
||||
@debounce
|
||||
def set_cooling_threshold(self, value):
|
||||
"""Set cooling threshold temp to value if call came from HomeKit."""
|
||||
_LOGGER.debug(
|
||||
"%s: Set cooling threshold temperature to %.1f°C", self.entity_id, value
|
||||
)
|
||||
self._flag_coolingthresh = True
|
||||
low = self.char_heating_thresh_temp.value
|
||||
temperature = temperature_to_states(value, self._unit)
|
||||
params = {
|
||||
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_CLIMATE,
|
||||
SERVICE_SET_TEMPERATURE_THERMOSTAT,
|
||||
params,
|
||||
f"cooling threshold {temperature}{self._unit}",
|
||||
)
|
||||
|
||||
@debounce
|
||||
def set_heating_threshold(self, value):
|
||||
"""Set heating threshold temp to value if call came from HomeKit."""
|
||||
_LOGGER.debug(
|
||||
"%s: Set heating threshold temperature to %.1f°C", self.entity_id, value
|
||||
)
|
||||
self._flag_heatingthresh = True
|
||||
high = self.char_cooling_thresh_temp.value
|
||||
temperature = temperature_to_states(value, self._unit)
|
||||
params = {
|
||||
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_CLIMATE,
|
||||
SERVICE_SET_TEMPERATURE_THERMOSTAT,
|
||||
params,
|
||||
f"heating threshold {temperature}{self._unit}",
|
||||
)
|
||||
|
||||
@debounce
|
||||
def set_target_temperature(self, value):
|
||||
"""Set target temperature to value if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set target temperature to %.1f°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_CLIMATE,
|
||||
SERVICE_SET_TEMPERATURE_THERMOSTAT,
|
||||
params,
|
||||
f"{temperature}{self._unit}",
|
||||
)
|
||||
|
||||
def update_state(self, new_state):
|
||||
"""Update thermostat state after state changed."""
|
||||
features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
# Update target operation mode FIRST
|
||||
hvac_mode = new_state.state
|
||||
if hvac_mode and hvac_mode in HC_HASS_TO_HOMEKIT:
|
||||
homekit_hvac_mode = HC_HASS_TO_HOMEKIT[hvac_mode]
|
||||
if self.char_target_heat_cool.value != homekit_hvac_mode:
|
||||
self.char_target_heat_cool.set_value(homekit_hvac_mode)
|
||||
|
||||
# Set current operation mode for supported thermostats
|
||||
hvac_action = new_state.attributes.get(ATTR_HVAC_ACTION)
|
||||
if hvac_action:
|
||||
homekit_hvac_action = HC_HASS_TO_HOMEKIT_ACTION[hvac_action]
|
||||
if self.char_current_heat_cool.value != homekit_hvac_action:
|
||||
self.char_current_heat_cool.set_value(homekit_hvac_action)
|
||||
|
||||
# Update current temperature
|
||||
current_temp = new_state.attributes.get(ATTR_CURRENT_TEMPERATURE)
|
||||
if isinstance(current_temp, (int, float)):
|
||||
current_temp = temperature_to_homekit(current_temp, self._unit)
|
||||
self.char_current_temp.set_value(current_temp)
|
||||
current_temp = self._temperature_to_homekit(current_temp)
|
||||
if self.char_current_temp.value != current_temp:
|
||||
self.char_current_temp.set_value(current_temp)
|
||||
|
||||
# Update current humidity
|
||||
if CHAR_CURRENT_HUMIDITY in self.chars:
|
||||
current_humdity = new_state.attributes.get(ATTR_CURRENT_HUMIDITY)
|
||||
if isinstance(current_humdity, (int, float)):
|
||||
self.char_current_humidity.set_value(current_humdity)
|
||||
|
||||
# Update target temperature
|
||||
target_temp = new_state.attributes.get(ATTR_TEMPERATURE)
|
||||
if isinstance(target_temp, (int, float)):
|
||||
target_temp = temperature_to_homekit(target_temp, self._unit)
|
||||
if not self._flag_temperature:
|
||||
self.char_target_temp.set_value(target_temp)
|
||||
self._flag_temperature = False
|
||||
if self.char_current_humidity.value != current_humdity:
|
||||
self.char_current_humidity.set_value(current_humdity)
|
||||
|
||||
# Update target humidity
|
||||
if CHAR_TARGET_HUMIDITY in self.chars:
|
||||
target_humdity = new_state.attributes.get(ATTR_HUMIDITY)
|
||||
if isinstance(target_humdity, (int, float)):
|
||||
self.char_target_humidity.set_value(target_humdity)
|
||||
if self.char_target_humidity.value != target_humdity:
|
||||
self.char_target_humidity.set_value(target_humdity)
|
||||
|
||||
# Update cooling threshold temperature if characteristic exists
|
||||
if self.char_cooling_thresh_temp:
|
||||
cooling_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
if isinstance(cooling_thresh, (int, float)):
|
||||
cooling_thresh = temperature_to_homekit(cooling_thresh, self._unit)
|
||||
if not self._flag_coolingthresh:
|
||||
cooling_thresh = self._temperature_to_homekit(cooling_thresh)
|
||||
if self.char_heating_thresh_temp.value != cooling_thresh:
|
||||
self.char_cooling_thresh_temp.set_value(cooling_thresh)
|
||||
self._flag_coolingthresh = False
|
||||
|
||||
# Update heating threshold temperature if characteristic exists
|
||||
if self.char_heating_thresh_temp:
|
||||
heating_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
if isinstance(heating_thresh, (int, float)):
|
||||
heating_thresh = temperature_to_homekit(heating_thresh, self._unit)
|
||||
if not self._flag_heatingthresh:
|
||||
heating_thresh = self._temperature_to_homekit(heating_thresh)
|
||||
if self.char_heating_thresh_temp.value != heating_thresh:
|
||||
self.char_heating_thresh_temp.set_value(heating_thresh)
|
||||
self._flag_heatingthresh = False
|
||||
|
||||
# Update target temperature
|
||||
target_temp = new_state.attributes.get(ATTR_TEMPERATURE)
|
||||
if isinstance(target_temp, (int, float)):
|
||||
target_temp = self._temperature_to_homekit(target_temp)
|
||||
elif features & SUPPORT_TARGET_TEMPERATURE_RANGE:
|
||||
# Homekit expects a target temperature
|
||||
# even if the device does not support it
|
||||
hc_hvac_mode = self.char_target_heat_cool.value
|
||||
if hc_hvac_mode == HC_HEAT_COOL_HEAT:
|
||||
target_temp = self._temperature_to_homekit(
|
||||
new_state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
)
|
||||
elif hc_hvac_mode == HC_HEAT_COOL_COOL:
|
||||
target_temp = self._temperature_to_homekit(
|
||||
new_state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
)
|
||||
if target_temp and self.char_target_temp.value != target_temp:
|
||||
self.char_target_temp.set_value(target_temp)
|
||||
|
||||
# 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
|
||||
hvac_mode = new_state.state
|
||||
if hvac_mode and hvac_mode in HC_HASS_TO_HOMEKIT:
|
||||
if not self._flag_heat_cool:
|
||||
self.char_target_heat_cool.set_value(HC_HASS_TO_HOMEKIT[hvac_mode])
|
||||
self._flag_heat_cool = False
|
||||
|
||||
# Set current operation mode for supported thermostats
|
||||
hvac_action = new_state.attributes.get(ATTR_HVAC_ACTION)
|
||||
if hvac_action:
|
||||
|
||||
self.char_current_heat_cool.set_value(
|
||||
HC_HASS_TO_HOMEKIT_ACTION[hvac_action]
|
||||
)
|
||||
unit = UNIT_HASS_TO_HOMEKIT[self._unit]
|
||||
if self.char_display_units.value != unit:
|
||||
self.char_display_units.set_value(unit)
|
||||
|
||||
|
||||
@TYPES.register("WaterHeater")
|
||||
@ -409,8 +467,6 @@ class WaterHeater(HomeAccessory):
|
||||
"""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)
|
||||
@ -442,6 +498,9 @@ class WaterHeater(HomeAccessory):
|
||||
CHAR_TEMP_DISPLAY_UNITS, value=0
|
||||
)
|
||||
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
self.update_state(state)
|
||||
|
||||
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)
|
||||
@ -465,16 +524,14 @@ class WaterHeater(HomeAccessory):
|
||||
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 != HVAC_MODE_HEAT:
|
||||
self.char_target_heat_cool.set_value(1) # Heat
|
||||
if self.char_target_heat_cool.value != 1:
|
||||
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 %.1f°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(
|
||||
@ -490,17 +547,16 @@ class WaterHeater(HomeAccessory):
|
||||
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:
|
||||
if temperature != self.char_current_temp.value:
|
||||
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])
|
||||
unit = UNIT_HASS_TO_HOMEKIT[self._unit]
|
||||
if self.char_display_units.value != unit:
|
||||
self.char_display_units.set_value(unit)
|
||||
|
||||
# Update target operation mode
|
||||
operation_mode = new_state.state
|
||||
if operation_mode and not self._flag_heat_cool:
|
||||
if operation_mode and self.char_target_heat_cool.value != 1:
|
||||
self.char_target_heat_cool.set_value(1) # Heat
|
||||
self._flag_heat_cool = False
|
||||
|
@ -66,7 +66,7 @@ async def test_light_basic(hass, hk_driver, cls, events, driver):
|
||||
|
||||
assert acc.aid == 1
|
||||
assert acc.category == 5 # Lightbulb
|
||||
assert acc.char_on.value == 0
|
||||
assert acc.char_on.value
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
@ -260,7 +260,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events, driver):
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
assert acc.char_color_temperature.value == 153
|
||||
assert acc.char_color_temperature.value == 190
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
@ -326,8 +326,8 @@ async def test_light_rgb_color(hass, hk_driver, cls, events, driver):
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
assert acc.char_hue.value == 0
|
||||
assert acc.char_saturation.value == 75
|
||||
assert acc.char_hue.value == 260
|
||||
assert acc.char_saturation.value == 90
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
|
@ -2,6 +2,8 @@
|
||||
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
|
||||
|
||||
from homeassistant.components.climate.const import (
|
||||
@ -23,7 +25,6 @@ from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_IDLE,
|
||||
DEFAULT_MAX_TEMP,
|
||||
DEFAULT_MIN_HUMIDITY,
|
||||
DEFAULT_MIN_TEMP,
|
||||
DOMAIN as DOMAIN_CLIMATE,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_COOL,
|
||||
@ -32,6 +33,8 @@ from homeassistant.components.climate.const import (
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
)
|
||||
from homeassistant.components.homekit.const import (
|
||||
ATTR_VALUE,
|
||||
@ -41,6 +44,7 @@ from homeassistant.components.homekit.const import (
|
||||
PROP_MIN_STEP,
|
||||
PROP_MIN_VALUE,
|
||||
)
|
||||
from homeassistant.components.homekit.type_thermostats import HC_MIN_TEMP
|
||||
from homeassistant.components.water_heater import DOMAIN as DOMAIN_WATER_HEATER
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
@ -58,6 +62,15 @@ 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."""
|
||||
@ -65,14 +78,14 @@ def cls():
|
||||
patcher.start()
|
||||
_import = __import__(
|
||||
"homeassistant.components.homekit.type_thermostats",
|
||||
fromlist=["Thermostat", "WaterHeater"],
|
||||
fromlist=["WaterHeater", "Thermostat"],
|
||||
)
|
||||
patcher_tuple = namedtuple("Cls", ["thermostat", "water_heater"])
|
||||
patcher_tuple = namedtuple("Cls", ["water_heater", "thermostat"])
|
||||
yield patcher_tuple(thermostat=_import.Thermostat, water_heater=_import.WaterHeater)
|
||||
patcher.stop()
|
||||
|
||||
|
||||
async def test_thermostat(hass, hk_driver, cls, events):
|
||||
async def test_thermostat(hass, hk_driver, cls, events, driver):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
@ -80,6 +93,7 @@ async def test_thermostat(hass, hk_driver, cls, events):
|
||||
entity_id,
|
||||
HVAC_MODE_OFF,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE,
|
||||
ATTR_HVAC_MODES: [
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
@ -87,15 +101,17 @@ async def test_thermostat(hass, hk_driver, cls, events):
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_AUTO,
|
||||
]
|
||||
],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert acc.aid == 2
|
||||
assert acc.aid == 1
|
||||
assert acc.category == 9 # Thermostat
|
||||
|
||||
assert acc.get_temperature_range() == (7.0, 35.0)
|
||||
@ -110,7 +126,7 @@ async def test_thermostat(hass, hk_driver, cls, events):
|
||||
assert acc.char_current_humidity is None
|
||||
|
||||
assert acc.char_target_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP
|
||||
assert acc.char_target_temp.properties[PROP_MIN_VALUE] == DEFAULT_MIN_TEMP
|
||||
assert acc.char_target_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP
|
||||
assert acc.char_target_temp.properties[PROP_MIN_STEP] == 0.1
|
||||
|
||||
hass.states.async_set(
|
||||
@ -257,6 +273,7 @@ async def test_thermostat(hass, hk_driver, cls, events):
|
||||
entity_id,
|
||||
HVAC_MODE_DRY,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE,
|
||||
ATTR_TEMPERATURE: 22.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 22.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_DRY,
|
||||
@ -273,42 +290,102 @@ async def test_thermostat(hass, hk_driver, cls, events):
|
||||
call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, "set_temperature")
|
||||
call_set_hvac_mode = async_mock_service(hass, DOMAIN_CLIMATE, "set_hvac_mode")
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_temp.client_update_value, 19.0)
|
||||
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(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_temp_iid,
|
||||
HAP_REPR_VALUE: 19.0,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
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] == 19.0
|
||||
assert acc.char_target_temp.value == 19.0
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "19.0°C"
|
||||
assert events[-1].data[ATTR_VALUE] == "TargetTemperature to 19.0°C"
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_heat_cool.client_update_value, 2)
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_heat_cool_iid,
|
||||
HAP_REPR_VALUE: 2,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert not call_set_hvac_mode
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_heat_cool_iid,
|
||||
HAP_REPR_VALUE: 1,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_hvac_mode
|
||||
assert call_set_hvac_mode[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_hvac_mode[0].data[ATTR_HVAC_MODE] == HVAC_MODE_COOL
|
||||
assert acc.char_target_heat_cool.value == 2
|
||||
assert call_set_hvac_mode[0].data[ATTR_HVAC_MODE] == HVAC_MODE_HEAT
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == HVAC_MODE_COOL
|
||||
assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 1"
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_heat_cool.client_update_value, 3)
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_heat_cool_iid,
|
||||
HAP_REPR_VALUE: 3,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_hvac_mode
|
||||
assert call_set_hvac_mode[1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_hvac_mode[1].data[ATTR_HVAC_MODE] == HVAC_MODE_HEAT_COOL
|
||||
assert acc.char_target_heat_cool.value == 3
|
||||
assert len(events) == 3
|
||||
assert events[-1].data[ATTR_VALUE] == HVAC_MODE_HEAT_COOL
|
||||
assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 3"
|
||||
|
||||
|
||||
async def test_thermostat_auto(hass, hk_driver, cls, events):
|
||||
async def test_thermostat_auto(hass, hk_driver, cls, events, driver):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
# support_auto = True
|
||||
hass.states.async_set(entity_id, HVAC_MODE_OFF, {ATTR_SUPPORTED_FEATURES: 6})
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_OFF,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE
|
||||
| SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -316,10 +393,10 @@ async def test_thermostat_auto(hass, hk_driver, cls, events):
|
||||
assert acc.char_heating_thresh_temp.value == 19.0
|
||||
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == DEFAULT_MIN_TEMP
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == DEFAULT_MIN_TEMP
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1
|
||||
|
||||
hass.states.async_set(
|
||||
@ -379,37 +456,51 @@ async def test_thermostat_auto(hass, hk_driver, cls, events):
|
||||
# Set from HomeKit
|
||||
call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, "set_temperature")
|
||||
|
||||
await hass.async_add_executor_job(
|
||||
acc.char_heating_thresh_temp.client_update_value, 20.0
|
||||
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(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_heating_thresh_temp_iid,
|
||||
HAP_REPR_VALUE: 20.0,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_cooling_thresh_temp_iid,
|
||||
HAP_REPR_VALUE: 25.0,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_temperature[0]
|
||||
assert call_set_temperature[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_LOW] == 20.0
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_HIGH] == 25.0
|
||||
assert acc.char_heating_thresh_temp.value == 20.0
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "heating threshold 20.0°C"
|
||||
|
||||
await hass.async_add_executor_job(
|
||||
acc.char_cooling_thresh_temp.client_update_value, 25.0
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_temperature[1]
|
||||
assert call_set_temperature[1].data[ATTR_ENTITY_ID] == entity_id
|
||||
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 25.0°C"
|
||||
assert len(events) == 1
|
||||
assert (
|
||||
events[-1].data[ATTR_VALUE]
|
||||
== "CoolingThresholdTemperature to 25.0°C, HeatingThresholdTemperature to 20.0°C"
|
||||
)
|
||||
|
||||
|
||||
async def test_thermostat_humidity(hass, hk_driver, cls, events):
|
||||
async def test_thermostat_humidity(hass, hk_driver, cls, events, driver):
|
||||
"""Test if accessory and HA are updated accordingly with humidity."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
# support_auto = True
|
||||
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, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -435,7 +526,21 @@ async def test_thermostat_humidity(hass, hk_driver, cls, events):
|
||||
# Set from HomeKit
|
||||
call_set_humidity = async_mock_service(hass, DOMAIN_CLIMATE, "set_humidity")
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_humidity.client_update_value, 35)
|
||||
char_target_humidity_iid = acc.char_target_humidity.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_humidity_iid,
|
||||
HAP_REPR_VALUE: 35,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_humidity[0]
|
||||
assert call_set_humidity[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
@ -445,7 +550,7 @@ async def test_thermostat_humidity(hass, hk_driver, cls, events):
|
||||
assert events[-1].data[ATTR_VALUE] == "35%"
|
||||
|
||||
|
||||
async def test_thermostat_power_state(hass, hk_driver, cls, events):
|
||||
async def test_thermostat_power_state(hass, hk_driver, cls, events, driver):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
@ -458,10 +563,19 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events):
|
||||
ATTR_TEMPERATURE: 23.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 18.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT,
|
||||
ATTR_HVAC_MODES: [
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF,
|
||||
],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -475,6 +589,13 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events):
|
||||
ATTR_TEMPERATURE: 23.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 18.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE,
|
||||
ATTR_HVAC_MODES: [
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF,
|
||||
],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@ -488,6 +609,13 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events):
|
||||
ATTR_TEMPERATURE: 23.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 18.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE,
|
||||
ATTR_HVAC_MODES: [
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF,
|
||||
],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@ -497,31 +625,68 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events):
|
||||
# Set from HomeKit
|
||||
call_set_hvac_mode = async_mock_service(hass, DOMAIN_CLIMATE, "set_hvac_mode")
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_heat_cool.client_update_value, 1)
|
||||
char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_heat_cool_iid,
|
||||
HAP_REPR_VALUE: 1,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_hvac_mode
|
||||
assert call_set_hvac_mode[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_hvac_mode[0].data[ATTR_HVAC_MODE] == HVAC_MODE_HEAT
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == HVAC_MODE_HEAT
|
||||
assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 1"
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_heat_cool_iid,
|
||||
HAP_REPR_VALUE: 2,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_heat_cool.client_update_value, 0)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_target_heat_cool.value == 0
|
||||
assert call_set_hvac_mode
|
||||
assert call_set_hvac_mode[1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_hvac_mode[1].data[ATTR_HVAC_MODE] == HVAC_MODE_COOL
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == HVAC_MODE_OFF
|
||||
assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 2"
|
||||
assert acc.char_target_heat_cool.value == 2
|
||||
|
||||
|
||||
async def test_thermostat_fahrenheit(hass, hk_driver, cls, events):
|
||||
async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
# support_ = True
|
||||
hass.states.async_set(entity_id, HVAC_MODE_OFF, {ATTR_SUPPORTED_FEATURES: 6})
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_OFF,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE
|
||||
| SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||
},
|
||||
)
|
||||
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, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -533,6 +698,8 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events):
|
||||
ATTR_TARGET_TEMP_LOW: 68.1,
|
||||
ATTR_TEMPERATURE: 71.6,
|
||||
ATTR_CURRENT_TEMPERATURE: 73.4,
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE
|
||||
| SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@ -546,38 +713,73 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events):
|
||||
# Set from HomeKit
|
||||
call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, "set_temperature")
|
||||
|
||||
await hass.async_add_executor_job(
|
||||
acc.char_cooling_thresh_temp.client_update_value, 23
|
||||
char_cooling_thresh_temp_iid = acc.char_cooling_thresh_temp.to_HAP()[HAP_REPR_IID]
|
||||
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(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_cooling_thresh_temp_iid,
|
||||
HAP_REPR_VALUE: 23,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_temperature[0]
|
||||
assert call_set_temperature[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_HIGH] == 73.5
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_LOW] == 68
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "cooling threshold 73.5°F"
|
||||
assert events[-1].data[ATTR_VALUE] == "CoolingThresholdTemperature to 23°C"
|
||||
|
||||
await hass.async_add_executor_job(
|
||||
acc.char_heating_thresh_temp.client_update_value, 22
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_heating_thresh_temp_iid,
|
||||
HAP_REPR_VALUE: 22,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_temperature[1]
|
||||
assert call_set_temperature[1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_temperature[1].data[ATTR_TARGET_TEMP_HIGH] == 73.5
|
||||
assert call_set_temperature[1].data[ATTR_TARGET_TEMP_LOW] == 71.5
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == "heating threshold 71.5°F"
|
||||
assert events[-1].data[ATTR_VALUE] == "HeatingThresholdTemperature to 22°C"
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_temp.client_update_value, 24.0)
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_temp_iid,
|
||||
HAP_REPR_VALUE: 24.0,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_temperature[2]
|
||||
assert call_set_temperature[2].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_temperature[2].data[ATTR_TEMPERATURE] == 75.0
|
||||
assert len(events) == 3
|
||||
assert events[-1].data[ATTR_VALUE] == "75.0°F"
|
||||
assert events[-1].data[ATTR_VALUE] == "TargetTemperature to 24.0°C"
|
||||
|
||||
|
||||
async def test_thermostat_get_temperature_range(hass, hk_driver, cls):
|
||||
async def test_thermostat_get_temperature_range(hass, hk_driver, cls, driver):
|
||||
"""Test if temperature range is evaluated correctly."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
@ -599,13 +801,15 @@ async def test_thermostat_get_temperature_range(hass, hk_driver, cls):
|
||||
assert acc.get_temperature_range() == (15.5, 21.0)
|
||||
|
||||
|
||||
async def test_thermostat_temperature_step_whole(hass, hk_driver, cls):
|
||||
async def test_thermostat_temperature_step_whole(hass, hk_driver, cls, driver):
|
||||
"""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, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -657,7 +861,7 @@ async def test_thermostat_restore(hass, hk_driver, cls, events):
|
||||
}
|
||||
|
||||
|
||||
async def test_thermostat_hvac_modes(hass, hk_driver, cls):
|
||||
async def test_thermostat_hvac_modes(hass, hk_driver, cls, driver):
|
||||
"""Test if unsupported HVAC modes are deactivated in HomeKit."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
@ -666,7 +870,9 @@ async def test_thermostat_hvac_modes(hass, hk_driver, cls):
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
hap = acc.char_target_heat_cool.to_HAP()
|
||||
@ -688,13 +894,13 @@ async def test_thermostat_hvac_modes(hass, hk_driver, cls):
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
|
||||
|
||||
async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls):
|
||||
async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, driver):
|
||||
"""Test we get heat cool over auto."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
{
|
||||
ATTR_HVAC_MODES: [
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
@ -707,12 +913,14 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls):
|
||||
call_set_hvac_mode = async_mock_service(hass, DOMAIN_CLIMATE, "set_hvac_mode")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
hap = acc.char_target_heat_cool.to_HAP()
|
||||
assert hap["valid-values"] == [0, 1, 3]
|
||||
assert acc.char_target_heat_cool.value == 3
|
||||
assert acc.char_target_heat_cool.value == 0
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_heat_cool.set_value, 3)
|
||||
await hass.async_block_till_done()
|
||||
@ -727,7 +935,21 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls):
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_heat_cool.client_update_value, 3)
|
||||
char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_heat_cool_iid,
|
||||
HAP_REPR_VALUE: 3,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_hvac_mode
|
||||
assert call_set_hvac_mode[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
@ -735,24 +957,28 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls):
|
||||
assert acc.char_target_heat_cool.value == 3
|
||||
|
||||
|
||||
async def test_thermostat_hvac_modes_with_auto_no_heat_cool(hass, hk_driver, cls):
|
||||
async def test_thermostat_hvac_modes_with_auto_no_heat_cool(
|
||||
hass, hk_driver, cls, driver
|
||||
):
|
||||
"""Test we get auto when there is no heat cool."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_HEAT,
|
||||
{ATTR_HVAC_MODES: [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF]},
|
||||
)
|
||||
call_set_hvac_mode = async_mock_service(hass, DOMAIN_CLIMATE, "set_hvac_mode")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
hap = acc.char_target_heat_cool.to_HAP()
|
||||
assert hap["valid-values"] == [0, 1, 3]
|
||||
assert acc.char_target_heat_cool.value == 3
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_heat_cool.set_value, 3)
|
||||
await hass.async_block_till_done()
|
||||
@ -767,7 +993,21 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool(hass, hk_driver, cls
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
|
||||
await hass.async_add_executor_job(acc.char_target_heat_cool.client_update_value, 3)
|
||||
char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_heat_cool_iid,
|
||||
HAP_REPR_VALUE: 3,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_hvac_mode
|
||||
assert call_set_hvac_mode[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
@ -775,7 +1015,7 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool(hass, hk_driver, cls
|
||||
assert acc.char_target_heat_cool.value == 3
|
||||
|
||||
|
||||
async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls):
|
||||
async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls, driver):
|
||||
"""Test if unsupported HVAC modes are deactivated in HomeKit."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
@ -784,7 +1024,9 @@ async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls):
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
hap = acc.char_target_heat_cool.to_HAP()
|
||||
@ -806,7 +1048,7 @@ async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls):
|
||||
assert acc.char_target_heat_cool.value == 3
|
||||
|
||||
|
||||
async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls):
|
||||
async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls, driver):
|
||||
"""Test a thermostat that has no off."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
@ -815,7 +1057,9 @@ async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls):
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
hap = acc.char_target_heat_cool.to_HAP()
|
||||
@ -841,6 +1085,166 @@ async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls):
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
|
||||
|
||||
async def test_thermostat_without_target_temp_only_range(
|
||||
hass, hk_driver, cls, events, driver
|
||||
):
|
||||
"""Test a thermostat that only supports a range."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
# support_auto = True
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_OFF,
|
||||
{ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert acc.char_cooling_thresh_temp.value == 23.0
|
||||
assert acc.char_heating_thresh_temp.value == 19.0
|
||||
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
{
|
||||
ATTR_TARGET_TEMP_HIGH: 22.0,
|
||||
ATTR_TARGET_TEMP_LOW: 20.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 18.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT,
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_heating_thresh_temp.value == 20.0
|
||||
assert acc.char_cooling_thresh_temp.value == 22.0
|
||||
assert acc.char_current_heat_cool.value == 1
|
||||
assert acc.char_target_heat_cool.value == 3
|
||||
assert acc.char_current_temp.value == 18.0
|
||||
assert acc.char_display_units.value == 0
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_COOL,
|
||||
{
|
||||
ATTR_TARGET_TEMP_HIGH: 23.0,
|
||||
ATTR_TARGET_TEMP_LOW: 19.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 24.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_COOL,
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_heating_thresh_temp.value == 19.0
|
||||
assert acc.char_cooling_thresh_temp.value == 23.0
|
||||
assert acc.char_current_heat_cool.value == 2
|
||||
assert acc.char_target_heat_cool.value == 2
|
||||
assert acc.char_current_temp.value == 24.0
|
||||
assert acc.char_display_units.value == 0
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_COOL,
|
||||
{
|
||||
ATTR_TARGET_TEMP_HIGH: 23.0,
|
||||
ATTR_TARGET_TEMP_LOW: 19.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 21.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE,
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_heating_thresh_temp.value == 19.0
|
||||
assert acc.char_cooling_thresh_temp.value == 23.0
|
||||
assert acc.char_current_heat_cool.value == 0
|
||||
assert acc.char_target_heat_cool.value == 2
|
||||
assert acc.char_current_temp.value == 21.0
|
||||
assert acc.char_display_units.value == 0
|
||||
|
||||
# Set from HomeKit
|
||||
call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, "set_temperature")
|
||||
|
||||
char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_temp_iid,
|
||||
HAP_REPR_VALUE: 17.0,
|
||||
}
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_temperature[0]
|
||||
assert call_set_temperature[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_LOW] == 12.0
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_HIGH] == 17.0
|
||||
assert acc.char_target_temp.value == 17.0
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "CoolingThresholdTemperature to 17.0°C"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_HEAT,
|
||||
{
|
||||
ATTR_TARGET_TEMP_HIGH: 23.0,
|
||||
ATTR_TARGET_TEMP_LOW: 19.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 21.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE,
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_heating_thresh_temp.value == 19.0
|
||||
assert acc.char_cooling_thresh_temp.value == 23.0
|
||||
assert acc.char_current_heat_cool.value == 0
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
assert acc.char_current_temp.value == 21.0
|
||||
assert acc.char_display_units.value == 0
|
||||
|
||||
# Set from HomeKit
|
||||
call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, "set_temperature")
|
||||
|
||||
char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_temp_iid,
|
||||
HAP_REPR_VALUE: 27.0,
|
||||
}
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_temperature[0]
|
||||
assert call_set_temperature[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_LOW] == 27.0
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_HIGH] == 32.0
|
||||
assert acc.char_target_temp.value == 27.0
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == "HeatingThresholdTemperature to 27.0°C"
|
||||
|
||||
|
||||
async def test_water_heater(hass, hk_driver, cls, events):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
entity_id = "water_heater.test"
|
||||
@ -875,7 +1279,7 @@ async def test_water_heater(hass, hk_driver, cls, events):
|
||||
)
|
||||
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_current_temp.value == 50.0
|
||||
assert acc.char_target_heat_cool.value == 1
|
||||
assert acc.char_current_heat_cool.value == 1
|
||||
assert acc.char_display_units.value == 0
|
||||
@ -929,7 +1333,7 @@ async def test_water_heater_fahrenheit(hass, hk_driver, cls, events):
|
||||
hass.states.async_set(entity_id, HVAC_MODE_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_current_temp.value == 50
|
||||
assert acc.char_display_units.value == 1
|
||||
|
||||
# Set from HomeKit
|
||||
|
Loading…
x
Reference in New Issue
Block a user