Replace Climate HVAC_MODE_* constants with HVACMode enum (#70286)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
0dc426e2c4
commit
a22f36178f
8 changed files with 87 additions and 86 deletions
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue