1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00

Replace Climate HVAC_MODE_* constants with HVACMode enum (#70286)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Franck Nijhof 2022-04-20 13:20:53 +02:00 committed by GitHub
parent 0dc426e2c4
commit a22f36178f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 87 additions and 86 deletions

View File

@ -51,7 +51,7 @@ class AtagThermostat(AtagEntity, ClimateEntity):
self._attr_temperature_unit = coordinator.data.climate.temp_unit self._attr_temperature_unit = coordinator.data.climate.temp_unit
@property @property
def hvac_mode(self) -> str | None: # type: ignore[override] def hvac_mode(self) -> str | None:
"""Return hvac operation ie. heat, cool mode.""" """Return hvac operation ie. heat, cool mode."""
if self.coordinator.data.climate.hvac_mode in HVAC_MODES: if self.coordinator.data.climate.hvac_mode in HVAC_MODES:
return self.coordinator.data.climate.hvac_mode return self.coordinator.data.climate.hvac_mode

View File

@ -75,6 +75,7 @@ from .const import ( # noqa: F401
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_TARGET_TEMPERATURE_RANGE,
ClimateEntityFeature, ClimateEntityFeature,
HVACMode,
) )
DEFAULT_MIN_TEMP = 7 DEFAULT_MIN_TEMP = 7
@ -99,7 +100,7 @@ SET_TEMPERATURE_SCHEMA = vol.All(
vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float), vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, "temperature"): vol.Coerce(float), vol.Inclusive(ATTR_TARGET_TEMP_HIGH, "temperature"): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, "temperature"): vol.Coerce(float), vol.Inclusive(ATTR_TARGET_TEMP_LOW, "temperature"): vol.Coerce(float),
vol.Optional(ATTR_HVAC_MODE): vol.In(HVAC_MODES), vol.Optional(ATTR_HVAC_MODE): vol.Coerce(HVACMode),
} }
), ),
) )
@ -116,7 +117,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off")
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_SET_HVAC_MODE, SERVICE_SET_HVAC_MODE,
{vol.Required(ATTR_HVAC_MODE): vol.In(HVAC_MODES)}, {vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)},
"async_set_hvac_mode", "async_set_hvac_mode",
) )
component.async_register_entity_service( component.async_register_entity_service(
@ -188,8 +189,8 @@ class ClimateEntity(Entity):
_attr_fan_mode: str | None _attr_fan_mode: str | None
_attr_fan_modes: list[str] | None _attr_fan_modes: list[str] | None
_attr_hvac_action: str | None = None _attr_hvac_action: str | None = None
_attr_hvac_mode: str _attr_hvac_mode: HVACMode | str | None
_attr_hvac_modes: list[str] _attr_hvac_modes: list[HVACMode | str]
_attr_is_aux_heat: bool | None _attr_is_aux_heat: bool | None
_attr_max_humidity: int = DEFAULT_MAX_HUMIDITY _attr_max_humidity: int = DEFAULT_MAX_HUMIDITY
_attr_max_temp: float _attr_max_temp: float
@ -208,10 +209,15 @@ class ClimateEntity(Entity):
_attr_target_temperature: float | None = None _attr_target_temperature: float | None = None
_attr_temperature_unit: str _attr_temperature_unit: str
@final
@property @property
def state(self) -> str: def state(self) -> str | None:
"""Return the current state.""" """Return the current state."""
return self.hvac_mode if self.hvac_mode is None:
return None
if not isinstance(self.hvac_mode, HVACMode):
return HVACMode(self.hvac_mode).value
return self.hvac_mode.value
@property @property
def precision(self) -> float: def precision(self) -> float:
@ -226,7 +232,7 @@ class ClimateEntity(Entity):
def capability_attributes(self) -> dict[str, Any] | None: def capability_attributes(self) -> dict[str, Any] | None:
"""Return the capability attributes.""" """Return the capability attributes."""
supported_features = self.supported_features supported_features = self.supported_features
data = { data: dict[str, Any] = {
ATTR_HVAC_MODES: self.hvac_modes, ATTR_HVAC_MODES: self.hvac_modes,
ATTR_MIN_TEMP: show_temp( ATTR_MIN_TEMP: show_temp(
self.hass, self.min_temp, self.temperature_unit, self.precision self.hass, self.min_temp, self.temperature_unit, self.precision
@ -329,19 +335,13 @@ class ClimateEntity(Entity):
return self._attr_target_humidity return self._attr_target_humidity
@property @property
def hvac_mode(self) -> str: def hvac_mode(self) -> HVACMode | str | None:
"""Return hvac operation ie. heat, cool mode. """Return hvac operation ie. heat, cool mode."""
Need to be one of HVAC_MODE_*.
"""
return self._attr_hvac_mode return self._attr_hvac_mode
@property @property
def hvac_modes(self) -> list[str]: def hvac_modes(self) -> list[HVACMode | str]:
"""Return the list of available hvac operation modes. """Return the list of available hvac operation modes."""
Need to be a subset of HVAC_MODES.
"""
return self._attr_hvac_modes return self._attr_hvac_modes
@property @property
@ -465,11 +465,11 @@ class ClimateEntity(Entity):
"""Set new target fan mode.""" """Set new target fan mode."""
await self.hass.async_add_executor_job(self.set_fan_mode, fan_mode) await self.hass.async_add_executor_job(self.set_fan_mode, fan_mode)
def set_hvac_mode(self, hvac_mode: str) -> None: def set_hvac_mode(self, hvac_mode: HVACMode | str) -> None:
"""Set new target hvac mode.""" """Set new target hvac mode."""
raise NotImplementedError() raise NotImplementedError()
async def async_set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_hvac_mode(self, hvac_mode: HVACMode | str) -> None:
"""Set new target hvac mode.""" """Set new target hvac mode."""
await self.hass.async_add_executor_job(self.set_hvac_mode, hvac_mode) await self.hass.async_add_executor_job(self.set_hvac_mode, hvac_mode)
@ -512,7 +512,7 @@ class ClimateEntity(Entity):
return return
# Fake turn on # Fake turn on
for mode in (HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_COOL): for mode in (HVACMode.HEAT_COOL, HVACMode.HEAT, HVACMode.COOL):
if mode not in self.hvac_modes: if mode not in self.hvac_modes:
continue continue
await self.async_set_hvac_mode(mode) await self.async_set_hvac_mode(mode)
@ -525,8 +525,8 @@ class ClimateEntity(Entity):
return return
# Fake turn off # Fake turn off
if HVAC_MODE_OFF in self.hvac_modes: if HVACMode.OFF in self.hvac_modes:
await self.async_set_hvac_mode(HVAC_MODE_OFF) await self.async_set_hvac_mode(HVACMode.OFF)
@property @property
def supported_features(self) -> int: def supported_features(self) -> int:

View File

@ -2,37 +2,45 @@
from enum import IntEnum from enum import IntEnum
# All activity disabled / Device is off/standby from homeassistant.backports.enum import StrEnum
class HVACMode(StrEnum):
"""HVAC mode for climate devices."""
# All activity disabled / Device is off/standby
OFF = "off"
# Heating
HEAT = "heat"
# Cooling
COOL = "cool"
# The device supports heating/cooling to a range
HEAT_COOL = "heat_cool"
# The temperature is set based on a schedule, learned behavior, AI or some
# other related mechanism. User is not able to adjust the temperature
AUTO = "auto"
# Device is in Dry/Humidity mode
DRY = "dry"
# Only the fan is on, not fan and another mode like cool
FAN_ONLY = "fan_only"
# These HVAC_MODE_* constants are deprecated as of Home Assistant 2022.5.
# Please use the HVACMode enum instead.
HVAC_MODE_OFF = "off" HVAC_MODE_OFF = "off"
# Heating
HVAC_MODE_HEAT = "heat" HVAC_MODE_HEAT = "heat"
# Cooling
HVAC_MODE_COOL = "cool" HVAC_MODE_COOL = "cool"
# The device supports heating/cooling to a range
HVAC_MODE_HEAT_COOL = "heat_cool" HVAC_MODE_HEAT_COOL = "heat_cool"
# The temperature is set based on a schedule, learned behavior, AI or some
# other related mechanism. User is not able to adjust the temperature
HVAC_MODE_AUTO = "auto" HVAC_MODE_AUTO = "auto"
# Device is in Dry/Humidity mode
HVAC_MODE_DRY = "dry" HVAC_MODE_DRY = "dry"
# Only the fan is on, not fan and another mode like cool
HVAC_MODE_FAN_ONLY = "fan_only" HVAC_MODE_FAN_ONLY = "fan_only"
HVAC_MODES = [cls.value for cls in HVACMode]
HVAC_MODES = [
HVAC_MODE_OFF,
HVAC_MODE_HEAT,
HVAC_MODE_COOL,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_AUTO,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
]
# No preset is active # No preset is active
PRESET_NONE = "none" PRESET_NONE = "none"
@ -149,7 +157,7 @@ class ClimateEntityFeature(IntEnum):
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. # These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Pleease use the ClimateEntityFeature enum instead. # Please use the ClimateEntityFeature enum instead.
SUPPORT_TARGET_TEMPERATURE = 1 SUPPORT_TARGET_TEMPERATURE = 1
SUPPORT_TARGET_TEMPERATURE_RANGE = 2 SUPPORT_TARGET_TEMPERATURE_RANGE = 2
SUPPORT_TARGET_HUMIDITY = 4 SUPPORT_TARGET_HUMIDITY = 4

View File

@ -5,7 +5,7 @@ from homeassistant.components.group import GroupIntegrationRegistry
from homeassistant.const import STATE_OFF from homeassistant.const import STATE_OFF
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from .const import HVAC_MODE_OFF, HVAC_MODES from .const import HVAC_MODES, HVACMode
@callback @callback
@ -14,6 +14,6 @@ def async_describe_on_off_states(
) -> None: ) -> None:
"""Describe group on off states.""" """Describe group on off states."""
registry.on_off_states( registry.on_off_states(
set(HVAC_MODES) - {HVAC_MODE_OFF}, set(HVAC_MODES) - {HVACMode.OFF},
STATE_OFF, STATE_OFF,
) )

View File

@ -7,13 +7,8 @@ from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL, CURRENT_HVAC_COOL,
CURRENT_HVAC_HEAT, CURRENT_HVAC_HEAT,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
HVAC_MODES,
ClimateEntityFeature, ClimateEntityFeature,
HVACMode,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
@ -47,12 +42,12 @@ async def async_setup_platform(
target_humidity=None, target_humidity=None,
current_humidity=None, current_humidity=None,
swing_mode=None, swing_mode=None,
hvac_mode=HVAC_MODE_HEAT, hvac_mode=HVACMode.HEAT,
hvac_action=CURRENT_HVAC_HEAT, hvac_action=CURRENT_HVAC_HEAT,
aux=None, aux=None,
target_temp_high=None, target_temp_high=None,
target_temp_low=None, target_temp_low=None,
hvac_modes=[HVAC_MODE_HEAT, HVAC_MODE_OFF], hvac_modes=[HVACMode.HEAT, HVACMode.OFF],
), ),
DemoClimate( DemoClimate(
unique_id="climate_2", unique_id="climate_2",
@ -65,12 +60,12 @@ async def async_setup_platform(
target_humidity=67, target_humidity=67,
current_humidity=54, current_humidity=54,
swing_mode="Off", swing_mode="Off",
hvac_mode=HVAC_MODE_COOL, hvac_mode=HVACMode.COOL,
hvac_action=CURRENT_HVAC_COOL, hvac_action=CURRENT_HVAC_COOL,
aux=False, aux=False,
target_temp_high=None, target_temp_high=None,
target_temp_low=None, target_temp_low=None,
hvac_modes=[mode for mode in HVAC_MODES if mode != HVAC_MODE_HEAT_COOL], hvac_modes=[cls.value for cls in HVACMode if cls != HVACMode.HEAT_COOL],
), ),
DemoClimate( DemoClimate(
unique_id="climate_3", unique_id="climate_3",
@ -84,12 +79,12 @@ async def async_setup_platform(
target_humidity=None, target_humidity=None,
current_humidity=None, current_humidity=None,
swing_mode="Auto", swing_mode="Auto",
hvac_mode=HVAC_MODE_HEAT_COOL, hvac_mode=HVACMode.HEAT_COOL,
hvac_action=None, hvac_action=None,
aux=None, aux=None,
target_temp_high=24, target_temp_high=24,
target_temp_low=21, target_temp_low=21,
hvac_modes=[HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT], hvac_modes=[cls.value for cls in HVACMode if cls != HVACMode.HEAT],
), ),
] ]
) )
@ -147,7 +142,7 @@ class DemoClimate(ClimateEntity):
self._support_flags = self._support_flags | ClimateEntityFeature.SWING_MODE self._support_flags = self._support_flags | ClimateEntityFeature.SWING_MODE
if aux is not None: if aux is not None:
self._support_flags = self._support_flags | ClimateEntityFeature.AUX_HEAT self._support_flags = self._support_flags | ClimateEntityFeature.AUX_HEAT
if HVAC_MODE_HEAT_COOL in hvac_modes or HVAC_MODE_AUTO in hvac_modes: if HVACMode.HEAT_COOL in hvac_modes or HVACMode.AUTO in hvac_modes:
self._support_flags = ( self._support_flags = (
self._support_flags | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE self._support_flags | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
) )

View File

@ -230,7 +230,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
return features return features
@esphome_state_property @esphome_state_property
def hvac_mode(self) -> str | None: # type: ignore[override] def hvac_mode(self) -> str | None:
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
return _CLIMATE_MODES.from_esphome(self._state.mode) return _CLIMATE_MODES.from_esphome(self._state.mode)

View File

@ -22,9 +22,6 @@ from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL, CURRENT_HVAC_COOL,
DOMAIN, DOMAIN,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY, PRESET_AWAY,
PRESET_ECO, PRESET_ECO,
SERVICE_SET_AUX_HEAT, SERVICE_SET_AUX_HEAT,
@ -34,6 +31,7 @@ from homeassistant.components.climate.const import (
SERVICE_SET_PRESET_MODE, SERVICE_SET_PRESET_MODE,
SERVICE_SET_SWING_MODE, SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE, SERVICE_SET_TEMPERATURE,
HVACMode,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -62,7 +60,7 @@ async def setup_demo_climate(hass):
def test_setup_params(hass): def test_setup_params(hass):
"""Test the initial parameters.""" """Test the initial parameters."""
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_COOL assert state.state == HVACMode.COOL
assert state.attributes.get(ATTR_TEMPERATURE) == 21 assert state.attributes.get(ATTR_TEMPERATURE) == 21
assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 22 assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 22
assert state.attributes.get(ATTR_FAN_MODE) == "On High" assert state.attributes.get(ATTR_FAN_MODE) == "On High"
@ -71,12 +69,12 @@ def test_setup_params(hass):
assert state.attributes.get(ATTR_SWING_MODE) == "Off" assert state.attributes.get(ATTR_SWING_MODE) == "Off"
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF
assert state.attributes.get(ATTR_HVAC_MODES) == [ assert state.attributes.get(ATTR_HVAC_MODES) == [
"off", HVACMode.OFF,
"heat", HVACMode.HEAT,
"cool", HVACMode.COOL,
"auto", HVACMode.AUTO,
"dry", HVACMode.DRY,
"fan_only", HVACMode.FAN_ONLY,
] ]
@ -293,7 +291,7 @@ async def test_set_hvac_bad_attr_and_state(hass):
""" """
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_COOL assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_COOL
assert state.state == HVAC_MODE_COOL assert state.state == HVACMode.COOL
with pytest.raises(vol.Invalid): with pytest.raises(vol.Invalid):
await hass.services.async_call( await hass.services.async_call(
@ -305,23 +303,23 @@ async def test_set_hvac_bad_attr_and_state(hass):
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_COOL assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_COOL
assert state.state == HVAC_MODE_COOL assert state.state == HVACMode.COOL
async def test_set_hvac(hass): async def test_set_hvac(hass):
"""Test setting of new hvac mode.""" """Test setting of new hvac mode."""
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_COOL assert state.state == HVACMode.COOL
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_SET_HVAC_MODE, SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, {ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_HVAC_MODE: HVACMode.HEAT},
blocking=True, blocking=True,
) )
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_HEAT assert state.state == HVACMode.HEAT
async def test_set_hold_mode_away(hass): async def test_set_hold_mode_away(hass):
@ -398,18 +396,18 @@ async def test_turn_on(hass):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_SET_HVAC_MODE, SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_HVAC_MODE: HVAC_MODE_OFF}, {ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_HVAC_MODE: HVACMode.OFF},
blocking=True, blocking=True,
) )
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_OFF assert state.state == HVACMode.OFF
await hass.services.async_call( await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_CLIMATE}, blocking=True DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_CLIMATE}, blocking=True
) )
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_HEAT assert state.state == HVACMode.HEAT
async def test_turn_off(hass): async def test_turn_off(hass):
@ -417,15 +415,15 @@ async def test_turn_off(hass):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_SET_HVAC_MODE, SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, {ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_HVAC_MODE: HVACMode.HEAT},
blocking=True, blocking=True,
) )
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_HEAT assert state.state == HVACMode.HEAT
await hass.services.async_call( await hass.services.async_call(
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_CLIMATE}, blocking=True DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_CLIMATE}, blocking=True
) )
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_OFF assert state.state == HVACMode.OFF

View File

@ -212,7 +212,7 @@ async def test_set_operation_bad_attr_and_state(hass, mqtt_mock, caplog):
with pytest.raises(vol.Invalid) as excinfo: with pytest.raises(vol.Invalid) as excinfo:
await common.async_set_hvac_mode(hass, None, ENTITY_CLIMATE) await common.async_set_hvac_mode(hass, None, ENTITY_CLIMATE)
assert ( assert (
"value must be one of ['auto', 'cool', 'dry', 'fan_only', 'heat', 'heat_cool', 'off'] for dictionary value @ data['hvac_mode']" "expected HVACMode or one of 'off', 'heat', 'cool', 'heat_cool', 'auto', 'dry', 'fan_only' for dictionary value @ data['hvac_mode']"
) in str(excinfo.value) ) in str(excinfo.value)
state = hass.states.get(ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE)
assert state.state == "off" assert state.state == "off"