Follow real AtlanticPassAPCZoneControlZone physical mode on Overkiz (HEAT, COOL or HEAT_COOL) (#111830)

* Support HEAT_COOL when mode is Auto on overkiz AtlanticPassAPCZoneControlZone

* Refactor ZoneControlZone to simplify usic by only using a single hvac mode

* Fix linting issues

* Makes more sense to use halves there

* Fix PR feedback
pull/114764/head
Jeremy TRUFIER 2024-03-29 14:51:44 +01:00 committed by Franck Nijhof
parent 612988cf3e
commit b8a2c14813
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
3 changed files with 325 additions and 98 deletions

View File

@ -7,6 +7,7 @@ from typing import cast
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeAssistantOverkizData from . import HomeAssistantOverkizData
@ -27,15 +28,16 @@ async def async_setup_entry(
"""Set up the Overkiz climate from a config entry.""" """Set up the Overkiz climate from a config entry."""
data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id]
async_add_entities( # Match devices based on the widget.
entities_based_on_widget: list[Entity] = [
WIDGET_TO_CLIMATE_ENTITY[device.widget](device.device_url, data.coordinator) WIDGET_TO_CLIMATE_ENTITY[device.widget](device.device_url, data.coordinator)
for device in data.platforms[Platform.CLIMATE] for device in data.platforms[Platform.CLIMATE]
if device.widget in WIDGET_TO_CLIMATE_ENTITY if device.widget in WIDGET_TO_CLIMATE_ENTITY
) ]
# Match devices based on the widget and controllableName # Match devices based on the widget and controllableName.
# This is for example used for Atlantic APC, where devices with different functionality share the same uiClass and widget. # ie Atlantic APC
async_add_entities( entities_based_on_widget_and_controllable: list[Entity] = [
WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY[device.widget][ WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY[device.widget][
cast(Controllable, device.controllable_name) cast(Controllable, device.controllable_name)
](device.device_url, data.coordinator) ](device.device_url, data.coordinator)
@ -43,14 +45,21 @@ async def async_setup_entry(
if device.widget in WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY if device.widget in WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY
and device.controllable_name and device.controllable_name
in WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY[device.widget] in WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY[device.widget]
) ]
# Hitachi Air To Air Heat Pumps # Match devices based on the widget and protocol.
async_add_entities( # #ie Hitachi Air To Air Heat Pumps
entities_based_on_widget_and_protocol: list[Entity] = [
WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget][device.protocol]( WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget][device.protocol](
device.device_url, data.coordinator device.device_url, data.coordinator
) )
for device in data.platforms[Platform.CLIMATE] for device in data.platforms[Platform.CLIMATE]
if device.widget in WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY if device.widget in WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY
and device.protocol in WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget] and device.protocol in WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget]
]
async_add_entities(
entities_based_on_widget
+ entities_based_on_widget_and_controllable
+ entities_based_on_widget_and_protocol
) )

View File

@ -159,7 +159,7 @@ class AtlanticPassAPCHeatingZone(OverkizEntity, ClimateEntity):
await self.async_set_heating_mode(PRESET_MODES_TO_OVERKIZ[preset_mode]) await self.async_set_heating_mode(PRESET_MODES_TO_OVERKIZ[preset_mode])
@property @property
def preset_mode(self) -> str: def preset_mode(self) -> str | None:
"""Return the current preset mode, e.g., home, away, temp.""" """Return the current preset mode, e.g., home, away, temp."""
heating_mode = cast( heating_mode = cast(
str, self.executor.select_state(OverkizState.IO_PASS_APC_HEATING_MODE) str, self.executor.select_state(OverkizState.IO_PASS_APC_HEATING_MODE)
@ -179,7 +179,7 @@ class AtlanticPassAPCHeatingZone(OverkizEntity, ClimateEntity):
return OVERKIZ_TO_PRESET_MODES[heating_mode] return OVERKIZ_TO_PRESET_MODES[heating_mode]
@property @property
def target_temperature(self) -> float: def target_temperature(self) -> float | None:
"""Return hvac target temperature.""" """Return hvac target temperature."""
current_heating_profile = self.current_heating_profile current_heating_profile = self.current_heating_profile
if current_heating_profile in OVERKIZ_TEMPERATURE_STATE_BY_PROFILE: if current_heating_profile in OVERKIZ_TEMPERATURE_STATE_BY_PROFILE:

View File

@ -3,16 +3,24 @@
from __future__ import annotations from __future__ import annotations
from asyncio import sleep from asyncio import sleep
from functools import cached_property
from typing import Any, cast from typing import Any, cast
from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState
from homeassistant.components.climate import PRESET_NONE, HVACMode from homeassistant.components.climate import (
from homeassistant.const import ATTR_TEMPERATURE ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
PRESET_NONE,
ClimateEntityFeature,
HVACAction,
HVACMode,
)
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES
from ..coordinator import OverkizDataUpdateCoordinator from ..coordinator import OverkizDataUpdateCoordinator
from ..executor import OverkizExecutor
from .atlantic_pass_apc_heating_zone import AtlanticPassAPCHeatingZone from .atlantic_pass_apc_heating_zone import AtlanticPassAPCHeatingZone
from .atlantic_pass_apc_zone_control import OVERKIZ_TO_HVAC_MODE
PRESET_SCHEDULE = "schedule" PRESET_SCHEDULE = "schedule"
PRESET_MANUAL = "manual" PRESET_MANUAL = "manual"
@ -24,32 +32,127 @@ OVERKIZ_MODE_TO_PRESET_MODES: dict[str, str] = {
PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_MODE_TO_PRESET_MODES.items()} PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_MODE_TO_PRESET_MODES.items()}
TEMPERATURE_ZONECONTROL_DEVICE_INDEX = 20 # Maps the HVAC current ZoneControl system operating mode.
OVERKIZ_TO_HVAC_ACTION: dict[str, HVACAction] = {
OverkizCommandParam.COOLING: HVACAction.COOLING,
OverkizCommandParam.DRYING: HVACAction.DRYING,
OverkizCommandParam.HEATING: HVACAction.HEATING,
# There is no known way to differentiate OFF from Idle.
OverkizCommandParam.STOP: HVACAction.OFF,
}
HVAC_ACTION_TO_OVERKIZ_PROFILE_STATE: dict[HVACAction, OverkizState] = {
HVACAction.COOLING: OverkizState.IO_PASS_APC_COOLING_PROFILE,
HVACAction.HEATING: OverkizState.IO_PASS_APC_HEATING_PROFILE,
}
HVAC_ACTION_TO_OVERKIZ_MODE_STATE: dict[HVACAction, OverkizState] = {
HVACAction.COOLING: OverkizState.IO_PASS_APC_COOLING_MODE,
HVACAction.HEATING: OverkizState.IO_PASS_APC_HEATING_MODE,
}
TEMPERATURE_ZONECONTROL_DEVICE_INDEX = 1
SUPPORTED_FEATURES: ClimateEntityFeature = (
ClimateEntityFeature.PRESET_MODE
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TURN_ON
)
OVERKIZ_THERMAL_CONFIGURATION_TO_HVAC_MODE: dict[
OverkizCommandParam, tuple[HVACMode, ClimateEntityFeature]
] = {
OverkizCommandParam.COOLING: (
HVACMode.COOL,
SUPPORTED_FEATURES | ClimateEntityFeature.TARGET_TEMPERATURE,
),
OverkizCommandParam.HEATING: (
HVACMode.HEAT,
SUPPORTED_FEATURES | ClimateEntityFeature.TARGET_TEMPERATURE,
),
OverkizCommandParam.HEATING_AND_COOLING: (
HVACMode.HEAT_COOL,
SUPPORTED_FEATURES | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
),
}
# Those device depends on a main probe that choose the operating mode (heating, cooling, ...) # Those device depends on a main probe that choose the operating mode (heating, cooling, ...).
class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone):
"""Representation of Atlantic Pass APC Heating And Cooling Zone Control.""" """Representation of Atlantic Pass APC Heating And Cooling Zone Control."""
_attr_target_temperature_step = PRECISION_HALVES
def __init__( def __init__(
self, device_url: str, coordinator: OverkizDataUpdateCoordinator self, device_url: str, coordinator: OverkizDataUpdateCoordinator
) -> None: ) -> None:
"""Init method.""" """Init method."""
super().__init__(device_url, coordinator) super().__init__(device_url, coordinator)
# There is less supported functions, because they depend on the ZoneControl. # When using derogated temperature, we fallback to legacy behavior.
if not self.is_using_derogated_temperature_fallback: if self.is_using_derogated_temperature_fallback:
# Modes are not configurable, they will follow current HVAC Mode of Zone Control. return
self._attr_hvac_modes = []
# Those are available and tested presets on Shogun. self._attr_hvac_modes = []
self._attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ] self._attr_supported_features = ClimateEntityFeature(0)
# Modes depends on device capabilities.
if (thermal_configuration := self.thermal_configuration) is not None:
(
device_hvac_mode,
climate_entity_feature,
) = thermal_configuration
self._attr_hvac_modes = [device_hvac_mode, HVACMode.OFF]
self._attr_supported_features = climate_entity_feature
# Those are available and tested presets on Shogun.
self._attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ]
# Those APC Heating and Cooling probes depends on the zone control device (main probe). # Those APC Heating and Cooling probes depends on the zone control device (main probe).
# Only the base device (#1) can be used to get/set some states. # Only the base device (#1) can be used to get/set some states.
# Like to retrieve and set the current operating mode (heating, cooling, drying, off). # Like to retrieve and set the current operating mode (heating, cooling, drying, off).
self.zone_control_device = self.executor.linked_device(
TEMPERATURE_ZONECONTROL_DEVICE_INDEX self.zone_control_executor: OverkizExecutor | None = None
if (
zone_control_device := self.executor.linked_device(
TEMPERATURE_ZONECONTROL_DEVICE_INDEX
)
) is not None:
self.zone_control_executor = OverkizExecutor(
zone_control_device.device_url,
coordinator,
)
@cached_property
def thermal_configuration(self) -> tuple[HVACMode, ClimateEntityFeature] | None:
"""Retrieve thermal configuration for this devices."""
if (
(
state_thermal_configuration := cast(
OverkizCommandParam | None,
self.executor.select_state(OverkizState.CORE_THERMAL_CONFIGURATION),
)
)
is not None
and state_thermal_configuration
in OVERKIZ_THERMAL_CONFIGURATION_TO_HVAC_MODE
):
return OVERKIZ_THERMAL_CONFIGURATION_TO_HVAC_MODE[
state_thermal_configuration
]
return None
@cached_property
def device_hvac_mode(self) -> HVACMode | None:
"""ZoneControlZone device has a single possible mode."""
return (
None
if self.thermal_configuration is None
else self.thermal_configuration[0]
) )
@property @property
@ -61,21 +164,37 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone):
) )
@property @property
def zone_control_hvac_mode(self) -> HVACMode: def zone_control_hvac_action(self) -> HVACAction:
"""Return hvac operation ie. heat, cool, dry, off mode.""" """Return hvac operation ie. heat, cool, dry, off mode."""
if ( if self.zone_control_executor is not None and (
self.zone_control_device is not None (
and ( state := self.zone_control_executor.select_state(
state := self.zone_control_device.states[
OverkizState.IO_PASS_APC_OPERATING_MODE OverkizState.IO_PASS_APC_OPERATING_MODE
] )
) )
is not None is not None
and (value := state.value_as_str) is not None
): ):
return OVERKIZ_TO_HVAC_MODE[value] return OVERKIZ_TO_HVAC_ACTION[cast(str, state)]
return HVACMode.OFF
return HVACAction.OFF
@property
def hvac_action(self) -> HVACAction | None:
"""Return the current running hvac operation."""
# When ZoneControl action is heating/cooling but Zone is stopped, means the zone is idle.
if (
hvac_action := self.zone_control_hvac_action
) in HVAC_ACTION_TO_OVERKIZ_PROFILE_STATE and cast(
str,
self.executor.select_state(
HVAC_ACTION_TO_OVERKIZ_PROFILE_STATE[hvac_action]
),
) == OverkizCommandParam.STOP:
return HVACAction.IDLE
return hvac_action
@property @property
def hvac_mode(self) -> HVACMode: def hvac_mode(self) -> HVACMode:
@ -84,30 +203,32 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone):
if self.is_using_derogated_temperature_fallback: if self.is_using_derogated_temperature_fallback:
return super().hvac_mode return super().hvac_mode
zone_control_hvac_mode = self.zone_control_hvac_mode if (device_hvac_mode := self.device_hvac_mode) is None:
return HVACMode.OFF
# Should be same, because either thermostat or this integration change both. cooling_is_off = cast(
on_off_state = cast(
str, str,
self.executor.select_state( self.executor.select_state(OverkizState.CORE_COOLING_ON_OFF),
OverkizState.CORE_COOLING_ON_OFF ) in (OverkizCommandParam.OFF, None)
if zone_control_hvac_mode == HVACMode.COOL
else OverkizState.CORE_HEATING_ON_OFF heating_is_off = cast(
), str,
) self.executor.select_state(OverkizState.CORE_HEATING_ON_OFF),
) in (OverkizCommandParam.OFF, None)
# Device is Stopped, it means the air flux is flowing but its venting door is closed. # Device is Stopped, it means the air flux is flowing but its venting door is closed.
if on_off_state == OverkizCommandParam.OFF: if (
hvac_mode = HVACMode.OFF (device_hvac_mode == HVACMode.COOL and cooling_is_off)
else: or (device_hvac_mode == HVACMode.HEAT and heating_is_off)
hvac_mode = zone_control_hvac_mode or (
device_hvac_mode == HVACMode.HEAT_COOL
and cooling_is_off
and heating_is_off
)
):
return HVACMode.OFF
# It helps keep it consistent with the Zone Control, within the interface. return device_hvac_mode
if self._attr_hvac_modes != [zone_control_hvac_mode, HVACMode.OFF]:
self._attr_hvac_modes = [zone_control_hvac_mode, HVACMode.OFF]
self.async_write_ha_state()
return hvac_mode
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode.""" """Set new target hvac mode."""
@ -118,46 +239,49 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone):
# They are mainly managed by the Zone Control device # They are mainly managed by the Zone Control device
# However, it make sense to map the OFF Mode to the Overkiz STOP Preset # However, it make sense to map the OFF Mode to the Overkiz STOP Preset
if hvac_mode == HVACMode.OFF: on_off_target_command_param = (
await self.executor.async_execute_command( OverkizCommandParam.OFF
OverkizCommand.SET_COOLING_ON_OFF, if hvac_mode == HVACMode.OFF
OverkizCommandParam.OFF, else OverkizCommandParam.ON
) )
await self.executor.async_execute_command(
OverkizCommand.SET_HEATING_ON_OFF, await self.executor.async_execute_command(
OverkizCommandParam.OFF, OverkizCommand.SET_COOLING_ON_OFF,
) on_off_target_command_param,
else: )
await self.executor.async_execute_command( await self.executor.async_execute_command(
OverkizCommand.SET_COOLING_ON_OFF, OverkizCommand.SET_HEATING_ON_OFF,
OverkizCommandParam.ON, on_off_target_command_param,
) )
await self.executor.async_execute_command(
OverkizCommand.SET_HEATING_ON_OFF,
OverkizCommandParam.ON,
)
await self.async_refresh_modes() await self.async_refresh_modes()
@property @property
def preset_mode(self) -> str: def preset_mode(self) -> str | None:
"""Return the current preset mode, e.g., schedule, manual.""" """Return the current preset mode, e.g., schedule, manual."""
if self.is_using_derogated_temperature_fallback: if self.is_using_derogated_temperature_fallback:
return super().preset_mode return super().preset_mode
mode = OVERKIZ_MODE_TO_PRESET_MODES[ if (
cast( self.zone_control_hvac_action in HVAC_ACTION_TO_OVERKIZ_MODE_STATE
str, and (
self.executor.select_state( mode_state := HVAC_ACTION_TO_OVERKIZ_MODE_STATE[
OverkizState.IO_PASS_APC_COOLING_MODE self.zone_control_hvac_action
if self.zone_control_hvac_mode == HVACMode.COOL ]
else OverkizState.IO_PASS_APC_HEATING_MODE
),
) )
] and (
(
mode := OVERKIZ_MODE_TO_PRESET_MODES[
cast(str, self.executor.select_state(mode_state))
]
)
is not None
)
):
return mode
return mode if mode is not None else PRESET_NONE return PRESET_NONE
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode.""" """Set new preset mode."""
@ -178,13 +302,18 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone):
await self.async_refresh_modes() await self.async_refresh_modes()
@property @property
def target_temperature(self) -> float: def target_temperature(self) -> float | None:
"""Return hvac target temperature.""" """Return hvac target temperature."""
if self.is_using_derogated_temperature_fallback: if self.is_using_derogated_temperature_fallback:
return super().target_temperature return super().target_temperature
if self.zone_control_hvac_mode == HVACMode.COOL: device_hvac_mode = self.device_hvac_mode
if device_hvac_mode == HVACMode.HEAT_COOL:
return None
if device_hvac_mode == HVACMode.COOL:
return cast( return cast(
float, float,
self.executor.select_state( self.executor.select_state(
@ -192,7 +321,7 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone):
), ),
) )
if self.zone_control_hvac_mode == HVACMode.HEAT: if device_hvac_mode == HVACMode.HEAT:
return cast( return cast(
float, float,
self.executor.select_state( self.executor.select_state(
@ -204,32 +333,73 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone):
float, self.executor.select_state(OverkizState.CORE_TARGET_TEMPERATURE) float, self.executor.select_state(OverkizState.CORE_TARGET_TEMPERATURE)
) )
@property
def target_temperature_high(self) -> float | None:
"""Return the highbound target temperature we try to reach (cooling)."""
if self.device_hvac_mode != HVACMode.HEAT_COOL:
return None
return cast(
float,
self.executor.select_state(OverkizState.CORE_COOLING_TARGET_TEMPERATURE),
)
@property
def target_temperature_low(self) -> float | None:
"""Return the lowbound target temperature we try to reach (heating)."""
if self.device_hvac_mode != HVACMode.HEAT_COOL:
return None
return cast(
float,
self.executor.select_state(OverkizState.CORE_HEATING_TARGET_TEMPERATURE),
)
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new temperature.""" """Set new temperature."""
if self.is_using_derogated_temperature_fallback: if self.is_using_derogated_temperature_fallback:
return await super().async_set_temperature(**kwargs) return await super().async_set_temperature(**kwargs)
temperature = kwargs[ATTR_TEMPERATURE] target_temperature = kwargs.get(ATTR_TEMPERATURE)
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
hvac_mode = self.hvac_mode
if hvac_mode == HVACMode.HEAT_COOL:
if target_temp_low is not None:
await self.executor.async_execute_command(
OverkizCommand.SET_HEATING_TARGET_TEMPERATURE,
target_temp_low,
)
if target_temp_high is not None:
await self.executor.async_execute_command(
OverkizCommand.SET_COOLING_TARGET_TEMPERATURE,
target_temp_high,
)
elif target_temperature is not None:
if hvac_mode == HVACMode.HEAT:
await self.executor.async_execute_command(
OverkizCommand.SET_HEATING_TARGET_TEMPERATURE,
target_temperature,
)
elif hvac_mode == HVACMode.COOL:
await self.executor.async_execute_command(
OverkizCommand.SET_COOLING_TARGET_TEMPERATURE,
target_temperature,
)
# Change both (heating/cooling) temperature is a good way to have consistency
await self.executor.async_execute_command(
OverkizCommand.SET_HEATING_TARGET_TEMPERATURE,
temperature,
)
await self.executor.async_execute_command(
OverkizCommand.SET_COOLING_TARGET_TEMPERATURE,
temperature,
)
await self.executor.async_execute_command( await self.executor.async_execute_command(
OverkizCommand.SET_DEROGATION_ON_OFF_STATE, OverkizCommand.SET_DEROGATION_ON_OFF_STATE,
OverkizCommandParam.OFF, OverkizCommandParam.ON,
) )
# Target temperature may take up to 1 minute to get refreshed. await self.async_refresh_modes()
await self.executor.async_execute_command(
OverkizCommand.REFRESH_TARGET_TEMPERATURE
)
async def async_refresh_modes(self) -> None: async def async_refresh_modes(self) -> None:
"""Refresh the device modes to have new states.""" """Refresh the device modes to have new states."""
@ -256,3 +426,51 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone):
await self.executor.async_execute_command( await self.executor.async_execute_command(
OverkizCommand.REFRESH_TARGET_TEMPERATURE OverkizCommand.REFRESH_TARGET_TEMPERATURE
) )
@property
def min_temp(self) -> float:
"""Return Minimum Temperature for AC of this group."""
device_hvac_mode = self.device_hvac_mode
if device_hvac_mode in (HVACMode.HEAT, HVACMode.HEAT_COOL):
return cast(
float,
self.executor.select_state(
OverkizState.CORE_MINIMUM_HEATING_TARGET_TEMPERATURE
),
)
if device_hvac_mode == HVACMode.COOL:
return cast(
float,
self.executor.select_state(
OverkizState.CORE_MINIMUM_COOLING_TARGET_TEMPERATURE
),
)
return super().min_temp
@property
def max_temp(self) -> float:
"""Return Max Temperature for AC of this group."""
device_hvac_mode = self.device_hvac_mode
if device_hvac_mode == HVACMode.HEAT:
return cast(
float,
self.executor.select_state(
OverkizState.CORE_MAXIMUM_HEATING_TARGET_TEMPERATURE
),
)
if device_hvac_mode in (HVACMode.COOL, HVACMode.HEAT_COOL):
return cast(
float,
self.executor.select_state(
OverkizState.CORE_MAXIMUM_COOLING_TARGET_TEMPERATURE
),
)
return super().max_temp