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
@property
def hvac_mode(self) -> str | None: # type: ignore[override]
def hvac_mode(self) -> str | None:
"""Return hvac operation ie. heat, cool mode."""
if self.coordinator.data.climate.hvac_mode in HVAC_MODES:
return self.coordinator.data.climate.hvac_mode

View File

@ -75,6 +75,7 @@ from .const import ( # noqa: F401
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
ClimateEntityFeature,
HVACMode,
)
DEFAULT_MIN_TEMP = 7
@ -99,7 +100,7 @@ SET_TEMPERATURE_SCHEMA = vol.All(
vol.Exclusive(ATTR_TEMPERATURE, "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.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_SET_HVAC_MODE,
{vol.Required(ATTR_HVAC_MODE): vol.In(HVAC_MODES)},
{vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)},
"async_set_hvac_mode",
)
component.async_register_entity_service(
@ -188,8 +189,8 @@ class ClimateEntity(Entity):
_attr_fan_mode: str | None
_attr_fan_modes: list[str] | None
_attr_hvac_action: str | None = None
_attr_hvac_mode: str
_attr_hvac_modes: list[str]
_attr_hvac_mode: HVACMode | str | None
_attr_hvac_modes: list[HVACMode | str]
_attr_is_aux_heat: bool | None
_attr_max_humidity: int = DEFAULT_MAX_HUMIDITY
_attr_max_temp: float
@ -208,10 +209,15 @@ class ClimateEntity(Entity):
_attr_target_temperature: float | None = None
_attr_temperature_unit: str
@final
@property
def state(self) -> str:
def state(self) -> str | None:
"""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
def precision(self) -> float:
@ -226,7 +232,7 @@ class ClimateEntity(Entity):
def capability_attributes(self) -> dict[str, Any] | None:
"""Return the capability attributes."""
supported_features = self.supported_features
data = {
data: dict[str, Any] = {
ATTR_HVAC_MODES: self.hvac_modes,
ATTR_MIN_TEMP: show_temp(
self.hass, self.min_temp, self.temperature_unit, self.precision
@ -329,19 +335,13 @@ class ClimateEntity(Entity):
return self._attr_target_humidity
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
def hvac_mode(self) -> HVACMode | str | None:
"""Return hvac operation ie. heat, cool mode."""
return self._attr_hvac_mode
@property
def hvac_modes(self) -> list[str]:
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
def hvac_modes(self) -> list[HVACMode | str]:
"""Return the list of available hvac operation modes."""
return self._attr_hvac_modes
@property
@ -465,11 +465,11 @@ class ClimateEntity(Entity):
"""Set new target 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."""
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."""
await self.hass.async_add_executor_job(self.set_hvac_mode, hvac_mode)
@ -512,7 +512,7 @@ class ClimateEntity(Entity):
return
# 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:
continue
await self.async_set_hvac_mode(mode)
@ -525,8 +525,8 @@ class ClimateEntity(Entity):
return
# Fake turn off
if HVAC_MODE_OFF in self.hvac_modes:
await self.async_set_hvac_mode(HVAC_MODE_OFF)
if HVACMode.OFF in self.hvac_modes:
await self.async_set_hvac_mode(HVACMode.OFF)
@property
def supported_features(self) -> int:

View File

@ -2,37 +2,45 @@
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"
# Heating
HVAC_MODE_HEAT = "heat"
# Cooling
HVAC_MODE_COOL = "cool"
# The device supports heating/cooling to a range
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"
# Device is in Dry/Humidity mode
HVAC_MODE_DRY = "dry"
# Only the fan is on, not fan and another mode like cool
HVAC_MODE_FAN_ONLY = "fan_only"
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,
]
HVAC_MODES = [cls.value for cls in HVACMode]
# No preset is active
PRESET_NONE = "none"
@ -149,7 +157,7 @@ class ClimateEntityFeature(IntEnum):
# 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_RANGE = 2
SUPPORT_TARGET_HUMIDITY = 4

View File

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

View File

@ -7,13 +7,8 @@ from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL,
CURRENT_HVAC_HEAT,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
HVAC_MODES,
ClimateEntityFeature,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
@ -47,12 +42,12 @@ async def async_setup_platform(
target_humidity=None,
current_humidity=None,
swing_mode=None,
hvac_mode=HVAC_MODE_HEAT,
hvac_mode=HVACMode.HEAT,
hvac_action=CURRENT_HVAC_HEAT,
aux=None,
target_temp_high=None,
target_temp_low=None,
hvac_modes=[HVAC_MODE_HEAT, HVAC_MODE_OFF],
hvac_modes=[HVACMode.HEAT, HVACMode.OFF],
),
DemoClimate(
unique_id="climate_2",
@ -65,12 +60,12 @@ async def async_setup_platform(
target_humidity=67,
current_humidity=54,
swing_mode="Off",
hvac_mode=HVAC_MODE_COOL,
hvac_mode=HVACMode.COOL,
hvac_action=CURRENT_HVAC_COOL,
aux=False,
target_temp_high=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(
unique_id="climate_3",
@ -84,12 +79,12 @@ async def async_setup_platform(
target_humidity=None,
current_humidity=None,
swing_mode="Auto",
hvac_mode=HVAC_MODE_HEAT_COOL,
hvac_mode=HVACMode.HEAT_COOL,
hvac_action=None,
aux=None,
target_temp_high=24,
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
if aux is not None:
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 | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
)

View File

@ -230,7 +230,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
return features
@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 _CLIMATE_MODES.from_esphome(self._state.mode)

View File

@ -22,9 +22,6 @@ from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL,
DOMAIN,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_ECO,
SERVICE_SET_AUX_HEAT,
@ -34,6 +31,7 @@ from homeassistant.components.climate.const import (
SERVICE_SET_PRESET_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE,
HVACMode,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
@ -62,7 +60,7 @@ async def setup_demo_climate(hass):
def test_setup_params(hass):
"""Test the initial parameters."""
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_CURRENT_TEMPERATURE) == 22
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_AUX_HEAT) == STATE_OFF
assert state.attributes.get(ATTR_HVAC_MODES) == [
"off",
"heat",
"cool",
"auto",
"dry",
"fan_only",
HVACMode.OFF,
HVACMode.HEAT,
HVACMode.COOL,
HVACMode.AUTO,
HVACMode.DRY,
HVACMode.FAN_ONLY,
]
@ -293,7 +291,7 @@ async def test_set_hvac_bad_attr_and_state(hass):
"""
state = hass.states.get(ENTITY_CLIMATE)
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):
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)
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):
"""Test setting of new hvac mode."""
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_COOL
assert state.state == HVACMode.COOL
await hass.services.async_call(
DOMAIN,
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,
)
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):
@ -398,18 +396,18 @@ async def test_turn_on(hass):
await hass.services.async_call(
DOMAIN,
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,
)
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_OFF
assert state.state == HVACMode.OFF
await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_CLIMATE}, blocking=True
)
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_HEAT
assert state.state == HVACMode.HEAT
async def test_turn_off(hass):
@ -417,15 +415,15 @@ async def test_turn_off(hass):
await hass.services.async_call(
DOMAIN,
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,
)
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_HEAT
assert state.state == HVACMode.HEAT
await hass.services.async_call(
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_CLIMATE}, blocking=True
)
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:
await common.async_set_hvac_mode(hass, None, ENTITY_CLIMATE)
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)
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == "off"