mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 00:37:13 +00:00
Fix Fan support in nest climate by adding HVAC_MODE_FAN_ONLY support (#44203)
* Add HVAC_MODE_FAN_ONLY to nest climate * Remove unreachable code * Fix HVAC_FAV_ONLY bug; must also turn off hvac
This commit is contained in:
parent
c1027cace6
commit
c92353088c
@ -23,6 +23,7 @@ from homeassistant.components.climate.const import (
|
|||||||
FAN_ON,
|
FAN_ON,
|
||||||
HVAC_MODE_AUTO,
|
HVAC_MODE_AUTO,
|
||||||
HVAC_MODE_COOL,
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
HVAC_MODE_HEAT,
|
HVAC_MODE_HEAT,
|
||||||
HVAC_MODE_HEAT_COOL,
|
HVAC_MODE_HEAT_COOL,
|
||||||
HVAC_MODE_OFF,
|
HVAC_MODE_OFF,
|
||||||
@ -188,11 +189,14 @@ class ThermostatEntity(ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def hvac_mode(self):
|
def hvac_mode(self):
|
||||||
"""Return the current operation (e.g. heat, cool, idle)."""
|
"""Return the current operation (e.g. heat, cool, idle)."""
|
||||||
|
hvac_mode = HVAC_MODE_OFF
|
||||||
if ThermostatModeTrait.NAME in self._device.traits:
|
if ThermostatModeTrait.NAME in self._device.traits:
|
||||||
trait = self._device.traits[ThermostatModeTrait.NAME]
|
trait = self._device.traits[ThermostatModeTrait.NAME]
|
||||||
if trait.mode in THERMOSTAT_MODE_MAP:
|
if trait.mode in THERMOSTAT_MODE_MAP:
|
||||||
return THERMOSTAT_MODE_MAP[trait.mode]
|
hvac_mode = THERMOSTAT_MODE_MAP[trait.mode]
|
||||||
return HVAC_MODE_OFF
|
if hvac_mode == HVAC_MODE_OFF and self.fan_mode == FAN_ON:
|
||||||
|
hvac_mode = HVAC_MODE_FAN_ONLY
|
||||||
|
return hvac_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_modes(self):
|
def hvac_modes(self):
|
||||||
@ -201,6 +205,8 @@ class ThermostatEntity(ClimateEntity):
|
|||||||
for mode in self._get_device_hvac_modes:
|
for mode in self._get_device_hvac_modes:
|
||||||
if mode in THERMOSTAT_MODE_MAP:
|
if mode in THERMOSTAT_MODE_MAP:
|
||||||
supported_modes.append(THERMOSTAT_MODE_MAP[mode])
|
supported_modes.append(THERMOSTAT_MODE_MAP[mode])
|
||||||
|
if self.supported_features & SUPPORT_FAN_MODE:
|
||||||
|
supported_modes.append(HVAC_MODE_FAN_ONLY)
|
||||||
return supported_modes
|
return supported_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -280,6 +286,10 @@ class ThermostatEntity(ClimateEntity):
|
|||||||
"""Set new target hvac mode."""
|
"""Set new target hvac mode."""
|
||||||
if hvac_mode not in self.hvac_modes:
|
if hvac_mode not in self.hvac_modes:
|
||||||
raise ValueError(f"Unsupported hvac_mode '{hvac_mode}'")
|
raise ValueError(f"Unsupported hvac_mode '{hvac_mode}'")
|
||||||
|
if hvac_mode == HVAC_MODE_FAN_ONLY:
|
||||||
|
# Turn the fan on but also turn off the hvac if it is on
|
||||||
|
await self.async_set_fan_mode(FAN_ON)
|
||||||
|
hvac_mode = HVAC_MODE_OFF
|
||||||
api_mode = THERMOSTAT_INV_MODE_MAP[hvac_mode]
|
api_mode = THERMOSTAT_INV_MODE_MAP[hvac_mode]
|
||||||
trait = self._device.traits[ThermostatModeTrait.NAME]
|
trait = self._device.traits[ThermostatModeTrait.NAME]
|
||||||
await trait.set_mode(api_mode)
|
await trait.set_mode(api_mode)
|
||||||
|
@ -27,6 +27,7 @@ from homeassistant.components.climate.const import (
|
|||||||
FAN_ON,
|
FAN_ON,
|
||||||
HVAC_MODE_COOL,
|
HVAC_MODE_COOL,
|
||||||
HVAC_MODE_DRY,
|
HVAC_MODE_DRY,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
HVAC_MODE_HEAT,
|
HVAC_MODE_HEAT,
|
||||||
HVAC_MODE_HEAT_COOL,
|
HVAC_MODE_HEAT_COOL,
|
||||||
HVAC_MODE_OFF,
|
HVAC_MODE_OFF,
|
||||||
@ -699,6 +700,7 @@ async def test_thermostat_fan_off(hass):
|
|||||||
HVAC_MODE_HEAT,
|
HVAC_MODE_HEAT,
|
||||||
HVAC_MODE_COOL,
|
HVAC_MODE_COOL,
|
||||||
HVAC_MODE_HEAT_COOL,
|
HVAC_MODE_HEAT_COOL,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
HVAC_MODE_OFF,
|
HVAC_MODE_OFF,
|
||||||
}
|
}
|
||||||
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_OFF
|
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_OFF
|
||||||
@ -730,13 +732,49 @@ async def test_thermostat_fan_on(hass):
|
|||||||
assert len(hass.states.async_all()) == 1
|
assert len(hass.states.async_all()) == 1
|
||||||
thermostat = hass.states.get("climate.my_thermostat")
|
thermostat = hass.states.get("climate.my_thermostat")
|
||||||
assert thermostat is not None
|
assert thermostat is not None
|
||||||
assert thermostat.state == HVAC_MODE_OFF
|
assert thermostat.state == HVAC_MODE_FAN_ONLY
|
||||||
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF
|
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF
|
||||||
assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2
|
assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2
|
||||||
assert set(thermostat.attributes[ATTR_HVAC_MODES]) == {
|
assert set(thermostat.attributes[ATTR_HVAC_MODES]) == {
|
||||||
HVAC_MODE_HEAT,
|
HVAC_MODE_HEAT,
|
||||||
HVAC_MODE_COOL,
|
HVAC_MODE_COOL,
|
||||||
HVAC_MODE_HEAT_COOL,
|
HVAC_MODE_HEAT_COOL,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
}
|
||||||
|
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
|
||||||
|
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_thermostat_cool_with_fan(hass):
|
||||||
|
"""Test a thermostat cooling while the fan is on."""
|
||||||
|
await setup_climate(
|
||||||
|
hass,
|
||||||
|
{
|
||||||
|
"sdm.devices.traits.Fan": {
|
||||||
|
"timerMode": "ON",
|
||||||
|
"timerTimeout": "2019-05-10T03:22:54Z",
|
||||||
|
},
|
||||||
|
"sdm.devices.traits.ThermostatHvac": {
|
||||||
|
"status": "OFF",
|
||||||
|
},
|
||||||
|
"sdm.devices.traits.ThermostatMode": {
|
||||||
|
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
|
||||||
|
"mode": "COOL",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(hass.states.async_all()) == 1
|
||||||
|
thermostat = hass.states.get("climate.my_thermostat")
|
||||||
|
assert thermostat is not None
|
||||||
|
assert thermostat.state == HVAC_MODE_COOL
|
||||||
|
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF
|
||||||
|
assert set(thermostat.attributes[ATTR_HVAC_MODES]) == {
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_HEAT_COOL,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
HVAC_MODE_OFF,
|
HVAC_MODE_OFF,
|
||||||
}
|
}
|
||||||
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
|
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
|
||||||
@ -766,7 +804,7 @@ async def test_thermostat_set_fan(hass, auth):
|
|||||||
assert len(hass.states.async_all()) == 1
|
assert len(hass.states.async_all()) == 1
|
||||||
thermostat = hass.states.get("climate.my_thermostat")
|
thermostat = hass.states.get("climate.my_thermostat")
|
||||||
assert thermostat is not None
|
assert thermostat is not None
|
||||||
assert thermostat.state == HVAC_MODE_OFF
|
assert thermostat.state == HVAC_MODE_FAN_ONLY
|
||||||
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
|
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
|
||||||
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
|
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
|
||||||
|
|
||||||
@ -845,13 +883,14 @@ async def test_thermostat_invalid_fan_mode(hass):
|
|||||||
assert len(hass.states.async_all()) == 1
|
assert len(hass.states.async_all()) == 1
|
||||||
thermostat = hass.states.get("climate.my_thermostat")
|
thermostat = hass.states.get("climate.my_thermostat")
|
||||||
assert thermostat is not None
|
assert thermostat is not None
|
||||||
assert thermostat.state == HVAC_MODE_OFF
|
assert thermostat.state == HVAC_MODE_FAN_ONLY
|
||||||
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF
|
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF
|
||||||
assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2
|
assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2
|
||||||
assert set(thermostat.attributes[ATTR_HVAC_MODES]) == {
|
assert set(thermostat.attributes[ATTR_HVAC_MODES]) == {
|
||||||
HVAC_MODE_HEAT,
|
HVAC_MODE_HEAT,
|
||||||
HVAC_MODE_COOL,
|
HVAC_MODE_COOL,
|
||||||
HVAC_MODE_HEAT_COOL,
|
HVAC_MODE_HEAT_COOL,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
HVAC_MODE_OFF,
|
HVAC_MODE_OFF,
|
||||||
}
|
}
|
||||||
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
|
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
|
||||||
@ -862,6 +901,54 @@ async def test_thermostat_invalid_fan_mode(hass):
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_thermostat_set_hvac_fan_only(hass, auth):
|
||||||
|
"""Test a thermostat enabling the fan via hvac_mode."""
|
||||||
|
await setup_climate(
|
||||||
|
hass,
|
||||||
|
{
|
||||||
|
"sdm.devices.traits.Fan": {
|
||||||
|
"timerMode": "OFF",
|
||||||
|
"timerTimeout": "2019-05-10T03:22:54Z",
|
||||||
|
},
|
||||||
|
"sdm.devices.traits.ThermostatHvac": {
|
||||||
|
"status": "OFF",
|
||||||
|
},
|
||||||
|
"sdm.devices.traits.ThermostatMode": {
|
||||||
|
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
|
||||||
|
"mode": "OFF",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
auth=auth,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(hass.states.async_all()) == 1
|
||||||
|
thermostat = hass.states.get("climate.my_thermostat")
|
||||||
|
assert thermostat is not None
|
||||||
|
assert thermostat.state == HVAC_MODE_OFF
|
||||||
|
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_OFF
|
||||||
|
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
|
||||||
|
|
||||||
|
await common.async_set_hvac_mode(hass, HVAC_MODE_FAN_ONLY)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(auth.captured_requests) == 2
|
||||||
|
|
||||||
|
(method, url, json) = auth.captured_requests.pop(0)
|
||||||
|
assert method == "post"
|
||||||
|
assert url == "some-device-id:executeCommand"
|
||||||
|
assert json == {
|
||||||
|
"command": "sdm.devices.commands.Fan.SetTimer",
|
||||||
|
"params": {"timerMode": "ON"},
|
||||||
|
}
|
||||||
|
(method, url, json) = auth.captured_requests.pop(0)
|
||||||
|
assert method == "post"
|
||||||
|
assert url == "some-device-id:executeCommand"
|
||||||
|
assert json == {
|
||||||
|
"command": "sdm.devices.commands.ThermostatMode.SetMode",
|
||||||
|
"params": {"mode": "OFF"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_thermostat_target_temp(hass, auth):
|
async def test_thermostat_target_temp(hass, auth):
|
||||||
"""Test a thermostat changing hvac modes and affected on target temps."""
|
"""Test a thermostat changing hvac modes and affected on target temps."""
|
||||||
subscriber = await setup_climate(
|
subscriber = await setup_climate(
|
||||||
|
@ -14,19 +14,18 @@ class FakeAuth(AbstractAuth):
|
|||||||
from the API.
|
from the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Tests can set fake responses here.
|
|
||||||
responses = []
|
|
||||||
# The last request is recorded here.
|
|
||||||
method = None
|
|
||||||
url = None
|
|
||||||
json = None
|
|
||||||
|
|
||||||
# Set up by fixture
|
|
||||||
client = None
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize FakeAuth."""
|
"""Initialize FakeAuth."""
|
||||||
super().__init__(None, None)
|
super().__init__(None, None)
|
||||||
|
# Tests can set fake responses here.
|
||||||
|
self.responses = []
|
||||||
|
# The last request is recorded here.
|
||||||
|
self.method = None
|
||||||
|
self.url = None
|
||||||
|
self.json = None
|
||||||
|
self.captured_requests = []
|
||||||
|
# Set up by fixture
|
||||||
|
self.client = None
|
||||||
|
|
||||||
async def async_get_access_token(self) -> str:
|
async def async_get_access_token(self) -> str:
|
||||||
"""Return a valid access token."""
|
"""Return a valid access token."""
|
||||||
@ -37,6 +36,7 @@ class FakeAuth(AbstractAuth):
|
|||||||
self.method = method
|
self.method = method
|
||||||
self.url = url
|
self.url = url
|
||||||
self.json = json
|
self.json = json
|
||||||
|
self.captured_requests.append((method, url, json))
|
||||||
return await self.client.get("/")
|
return await self.client.get("/")
|
||||||
|
|
||||||
async def response_handler(self, request):
|
async def response_handler(self, request):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user