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 feedbackpull/114764/head
parent
612988cf3e
commit
b8a2c14813
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue