Avoid homekit crash when temperature is clamped above max value (#37746)

This commit is contained in:
J. Nick Koston 2020-07-14 10:21:10 -10:00 committed by GitHub
parent 9ecaa10e51
commit f5cbae0cd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 90 additions and 39 deletions

View File

@ -120,21 +120,12 @@ class Thermostat(HomeAccessory):
self._state_updates = 0
self.hc_homekit_to_hass = None
self.hc_hass_to_homekit = None
min_temp, max_temp = self.get_temperature_range()
# Homekit only supports 10-38, overwriting
# the max to appears to work, but less than 0 causes
# a crash on the home app
hc_min_temp = max(min_temp, 0)
hc_max_temp = max_temp
min_humidity = self.hass.states.get(self.entity_id).attributes.get(
ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY
)
hc_min_temp, hc_max_temp = self.get_temperature_range()
# Add additional characteristics if auto mode is supported
self.chars = []
state = self.hass.states.get(self.entity_id)
min_humidity = state.attributes.get(ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY)
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if features & SUPPORT_TARGET_TEMPERATURE_RANGE:
@ -370,19 +361,12 @@ class Thermostat(HomeAccessory):
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 = (
self._temperature_to_homekit(max_temp) if max_temp else DEFAULT_MAX_TEMP
return _get_temperature_range_from_state(
self.hass.states.get(self.entity_id),
self._unit,
DEFAULT_MIN_TEMP,
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 = (
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_target_humidity(self, value):
"""Set target humidity to value if call came from HomeKit."""
@ -551,23 +535,12 @@ class WaterHeater(HomeAccessory):
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
return _get_temperature_range_from_state(
self.hass.states.get(self.entity_id),
self._unit,
DEFAULT_MIN_TEMP_WATER_HEATER,
DEFAULT_MAX_TEMP_WATER_HEATER,
)
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_WATER_HEATER
)
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."""
@ -609,3 +582,27 @@ class WaterHeater(HomeAccessory):
operation_mode = new_state.state
if operation_mode and self.char_target_heat_cool.value != 1:
self.char_target_heat_cool.set_value(1) # Heat
def _get_temperature_range_from_state(state, unit, default_min, default_max):
"""Calculate the temperature range from a state."""
min_temp = state.attributes.get(ATTR_MIN_TEMP)
if min_temp:
min_temp = round(temperature_to_homekit(min_temp, unit) * 2) / 2
else:
min_temp = default_min
max_temp = state.attributes.get(ATTR_MAX_TEMP)
if max_temp:
max_temp = round(temperature_to_homekit(max_temp, unit) * 2) / 2
else:
max_temp = default_max
# Homekit only supports 10-38, overwriting
# the max to appears to work, but less than 0 causes
# a crash on the home app
min_temp = max(min_temp, 0)
if min_temp > max_temp:
max_temp = min_temp
return min_temp, max_temp

View File

@ -1617,3 +1617,57 @@ async def test_thermostat_with_no_off_after_recheck(hass, hk_driver, cls, events
assert acc.char_target_heat_cool.value == 3
assert acc.char_current_temp.value == 18.0
assert acc.char_display_units.value == 0
async def test_thermostat_with_temp_clamps(hass, hk_driver, cls, events):
"""Test that tempatures are clamped to valid values to prevent homekit crash."""
entity_id = "climate.test"
hass.states.async_set(
entity_id,
HVAC_MODE_COOL,
{
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE
| SUPPORT_TARGET_TEMPERATURE_RANGE,
ATTR_HVAC_MODES: [],
ATTR_MAX_TEMP: 50,
ATTR_MIN_TEMP: 100,
},
)
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
assert acc.char_cooling_thresh_temp.value == 100
assert acc.char_heating_thresh_temp.value == 100
assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == 100
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 100
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1
assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == 100
assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 100
assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1
assert acc.char_target_heat_cool.value == 2
hass.states.async_set(
entity_id,
HVAC_MODE_HEAT_COOL,
{
ATTR_TARGET_TEMP_HIGH: 822.0,
ATTR_TARGET_TEMP_LOW: 20.0,
ATTR_CURRENT_TEMPERATURE: 9918.0,
ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT,
ATTR_HVAC_MODES: [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO],
},
)
await hass.async_block_till_done()
assert acc.char_heating_thresh_temp.value == 100.0
assert acc.char_cooling_thresh_temp.value == 100.0
assert acc.char_current_heat_cool.value == 1
assert acc.char_target_heat_cool.value == 3
assert acc.char_current_temp.value == 1000
assert acc.char_display_units.value == 0