Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remap homekit auto to home assistant heat_cool #33701

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 14 additions & 3 deletions homeassistant/components/homekit/type_thermostats.py
Expand Up @@ -27,6 +27,7 @@
DOMAIN as DOMAIN_CLIMATE,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
Expand Down Expand Up @@ -150,15 +151,25 @@ def __init__(self, *args):
HVAC_MODE_OFF,
)

# determine available modes for this entity, prefer AUTO over HEAT_COOL and COOL over FAN_ONLY
# Determine available modes for this entity,
# Prefer HEAT_COOL over AUTO and COOL over FAN_ONLY, DRY
#
# HEAT_COOL is preferred over auto because HomeKit Accessory Protocol describes
# heating or cooling comes on to maintain a target temp which is closest to
# the Home Assistant spec
#
# HVAC_MODE_HEAT_COOL: The device supports heating/cooling to a range
self.hc_homekit_to_hass = {
c: s
for s, c in HC_HASS_TO_HOMEKIT.items()
if (
s in hc_modes
and not (
(s == HVAC_MODE_HEAT_COOL and HVAC_MODE_AUTO in hc_modes)
or (s == HVAC_MODE_FAN_ONLY and HVAC_MODE_COOL in hc_modes)
(s == HVAC_MODE_AUTO and HVAC_MODE_HEAT_COOL in hc_modes)
or (
s in (HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY)
and HVAC_MODE_COOL in hc_modes
)
)
)
}
Expand Down
125 changes: 123 additions & 2 deletions tests/components/homekit/test_type_thermostats.py
Expand Up @@ -294,10 +294,10 @@ async def test_thermostat(hass, hk_driver, cls, events):
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_AUTO
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_AUTO
assert events[-1].data[ATTR_VALUE] == HVAC_MODE_HEAT_COOL


async def test_thermostat_auto(hass, hk_driver, cls, events):
Expand Down Expand Up @@ -660,6 +660,9 @@ async def test_thermostat_hvac_modes(hass, hk_driver, cls):
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
await hass.async_add_job(acc.run)
await hass.async_block_till_done()
hap = acc.char_target_heat_cool.to_HAP()
assert hap["valid-values"] == [0, 1]
assert acc.char_target_heat_cool.value == 0

with pytest.raises(ValueError):
await hass.async_add_job(acc.char_target_heat_cool.set_value, 3)
Expand All @@ -676,6 +679,124 @@ 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):
"""Test we get heat cool over auto."""
entity_id = "climate.test"

hass.states.async_set(
entity_id,
HVAC_MODE_HEAT_COOL,
{
ATTR_HVAC_MODES: [
HVAC_MODE_HEAT_COOL,
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)
await hass.async_add_job(acc.run)
bdraco marked this conversation as resolved.
Show resolved Hide resolved
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

await hass.async_add_job(acc.char_target_heat_cool.set_value, 3)
await hass.async_block_till_done()
assert acc.char_target_heat_cool.value == 3

await hass.async_add_job(acc.char_target_heat_cool.set_value, 1)
await hass.async_block_till_done()
assert acc.char_target_heat_cool.value == 1

with pytest.raises(ValueError):
await hass.async_add_job(acc.char_target_heat_cool.set_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 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_COOL
assert acc.char_target_heat_cool.value == 3


async def test_thermostat_hvac_modes_with_auto_no_heat_cool(hass, hk_driver, cls):
"""Test we get auto when there is no heat cool."""
entity_id = "climate.test"

hass.states.async_set(
entity_id,
HVAC_MODE_HEAT_COOL,
{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)
await hass.async_add_job(acc.run)
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

await hass.async_add_job(acc.char_target_heat_cool.set_value, 3)
await hass.async_block_till_done()
assert acc.char_target_heat_cool.value == 3

await hass.async_add_job(acc.char_target_heat_cool.set_value, 1)
await hass.async_block_till_done()
assert acc.char_target_heat_cool.value == 1

with pytest.raises(ValueError):
await hass.async_add_job(acc.char_target_heat_cool.set_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 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_AUTO
assert acc.char_target_heat_cool.value == 3


async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls):
"""Test if unsupported HVAC modes are deactivated in HomeKit."""
entity_id = "climate.test"

hass.states.async_set(
entity_id, HVAC_MODE_AUTO, {ATTR_HVAC_MODES: [HVAC_MODE_AUTO, HVAC_MODE_OFF]}
)

await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 2, None)
await hass.async_add_job(acc.run)
await hass.async_block_till_done()
hap = acc.char_target_heat_cool.to_HAP()
assert hap["valid-values"] == [0, 3]
assert acc.char_target_heat_cool.value == 3

await hass.async_add_job(acc.char_target_heat_cool.set_value, 3)
await hass.async_block_till_done()
assert acc.char_target_heat_cool.value == 3

with pytest.raises(ValueError):
await hass.async_add_job(acc.char_target_heat_cool.set_value, 1)
await hass.async_block_till_done()
assert acc.char_target_heat_cool.value == 3

with pytest.raises(ValueError):
await hass.async_add_job(acc.char_target_heat_cool.set_value, 2)
await hass.async_block_till_done()
assert acc.char_target_heat_cool.value == 3


async def test_water_heater(hass, hk_driver, cls, events):
"""Test if accessory and HA are updated accordingly."""
entity_id = "water_heater.test"
Expand Down