Support HitachiAirToAirHeatPump (hlrrwifi:HLinkMainController) in Overkiz (#103803)
parent
65a2f5bcd5
commit
a1701f0c56
|
@ -7,7 +7,10 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import HomeAssistantOverkizData
|
||||
from .climate_entities import WIDGET_TO_CLIMATE_ENTITY
|
||||
from .climate_entities import (
|
||||
WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY,
|
||||
WIDGET_TO_CLIMATE_ENTITY,
|
||||
)
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
|
@ -24,3 +27,13 @@ async def async_setup_entry(
|
|||
for device in data.platforms[Platform.CLIMATE]
|
||||
if device.widget in WIDGET_TO_CLIMATE_ENTITY
|
||||
)
|
||||
|
||||
# Hitachi Air To Air Heat Pumps
|
||||
async_add_entities(
|
||||
WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget][device.protocol](
|
||||
device.device_url, data.coordinator
|
||||
)
|
||||
for device in data.platforms[Platform.CLIMATE]
|
||||
if device.widget in WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY
|
||||
and device.protocol in WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget]
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Climate entities for the Overkiz (by Somfy) integration."""
|
||||
from pyoverkiz.enums import Protocol
|
||||
from pyoverkiz.enums.ui import UIWidget
|
||||
|
||||
from .atlantic_electrical_heater import AtlanticElectricalHeater
|
||||
|
@ -9,6 +10,7 @@ from .atlantic_electrical_towel_dryer import AtlanticElectricalTowelDryer
|
|||
from .atlantic_heat_recovery_ventilation import AtlanticHeatRecoveryVentilation
|
||||
from .atlantic_pass_apc_heating_zone import AtlanticPassAPCHeatingZone
|
||||
from .atlantic_pass_apc_zone_control import AtlanticPassAPCZoneControl
|
||||
from .hitachi_air_to_air_heat_pump_hlrrwifi import HitachiAirToAirHeatPumpHLRRWIFI
|
||||
from .somfy_heating_temperature_interface import SomfyHeatingTemperatureInterface
|
||||
from .somfy_thermostat import SomfyThermostat
|
||||
from .valve_heating_temperature_interface import ValveHeatingTemperatureInterface
|
||||
|
@ -26,3 +28,10 @@ WIDGET_TO_CLIMATE_ENTITY = {
|
|||
UIWidget.SOMFY_THERMOSTAT: SomfyThermostat,
|
||||
UIWidget.VALVE_HEATING_TEMPERATURE_INTERFACE: ValveHeatingTemperatureInterface,
|
||||
}
|
||||
|
||||
# Hitachi air-to-air heatpumps come in 2 flavors (HLRRWIFI and OVP) that are separated in 2 classes
|
||||
WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY = {
|
||||
UIWidget.HITACHI_AIR_TO_AIR_HEAT_PUMP: {
|
||||
Protocol.HLRR_WIFI: HitachiAirToAirHeatPumpHLRRWIFI,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
"""Support for HitachiAirToAirHeatPump."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, cast
|
||||
|
||||
from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
FAN_AUTO,
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
PRESET_NONE,
|
||||
SWING_BOTH,
|
||||
SWING_HORIZONTAL,
|
||||
SWING_OFF,
|
||||
SWING_VERTICAL,
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||
|
||||
from ..const import DOMAIN
|
||||
from ..coordinator import OverkizDataUpdateCoordinator
|
||||
from ..entity import OverkizEntity
|
||||
|
||||
PRESET_HOLIDAY_MODE = "holiday_mode"
|
||||
FAN_SILENT = "silent"
|
||||
FAN_SPEED_STATE = OverkizState.HLRRWIFI_FAN_SPEED
|
||||
LEAVE_HOME_STATE = OverkizState.HLRRWIFI_LEAVE_HOME
|
||||
MAIN_OPERATION_STATE = OverkizState.HLRRWIFI_MAIN_OPERATION
|
||||
MODE_CHANGE_STATE = OverkizState.HLRRWIFI_MODE_CHANGE
|
||||
ROOM_TEMPERATURE_STATE = OverkizState.HLRRWIFI_ROOM_TEMPERATURE
|
||||
SWING_STATE = OverkizState.HLRRWIFI_SWING
|
||||
|
||||
OVERKIZ_TO_HVAC_MODES: dict[str, HVACMode] = {
|
||||
OverkizCommandParam.AUTOHEATING: HVACMode.AUTO,
|
||||
OverkizCommandParam.AUTOCOOLING: HVACMode.AUTO,
|
||||
OverkizCommandParam.ON: HVACMode.HEAT,
|
||||
OverkizCommandParam.OFF: HVACMode.OFF,
|
||||
OverkizCommandParam.HEATING: HVACMode.HEAT,
|
||||
OverkizCommandParam.FAN: HVACMode.FAN_ONLY,
|
||||
OverkizCommandParam.DEHUMIDIFY: HVACMode.DRY,
|
||||
OverkizCommandParam.COOLING: HVACMode.COOL,
|
||||
OverkizCommandParam.AUTO: HVACMode.AUTO,
|
||||
}
|
||||
|
||||
HVAC_MODES_TO_OVERKIZ: dict[HVACMode, str] = {
|
||||
HVACMode.AUTO: OverkizCommandParam.AUTO,
|
||||
HVACMode.HEAT: OverkizCommandParam.HEATING,
|
||||
HVACMode.OFF: OverkizCommandParam.AUTO,
|
||||
HVACMode.FAN_ONLY: OverkizCommandParam.FAN,
|
||||
HVACMode.DRY: OverkizCommandParam.DEHUMIDIFY,
|
||||
HVACMode.COOL: OverkizCommandParam.COOLING,
|
||||
}
|
||||
|
||||
OVERKIZ_TO_SWING_MODES: dict[str, str] = {
|
||||
OverkizCommandParam.BOTH: SWING_BOTH,
|
||||
OverkizCommandParam.HORIZONTAL: SWING_HORIZONTAL,
|
||||
OverkizCommandParam.STOP: SWING_OFF,
|
||||
OverkizCommandParam.VERTICAL: SWING_VERTICAL,
|
||||
}
|
||||
|
||||
SWING_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_SWING_MODES.items()}
|
||||
|
||||
OVERKIZ_TO_FAN_MODES: dict[str, str] = {
|
||||
OverkizCommandParam.AUTO: FAN_AUTO,
|
||||
OverkizCommandParam.HIGH: FAN_HIGH,
|
||||
OverkizCommandParam.LOW: FAN_LOW,
|
||||
OverkizCommandParam.MEDIUM: FAN_MEDIUM,
|
||||
OverkizCommandParam.SILENT: FAN_SILENT,
|
||||
}
|
||||
|
||||
FAN_MODES_TO_OVERKIZ: dict[str, str] = {
|
||||
FAN_AUTO: OverkizCommandParam.AUTO,
|
||||
FAN_HIGH: OverkizCommandParam.HIGH,
|
||||
FAN_LOW: OverkizCommandParam.LOW,
|
||||
FAN_MEDIUM: OverkizCommandParam.MEDIUM,
|
||||
FAN_SILENT: OverkizCommandParam.SILENT,
|
||||
}
|
||||
|
||||
|
||||
class HitachiAirToAirHeatPumpHLRRWIFI(OverkizEntity, ClimateEntity):
|
||||
"""Representation of Hitachi Air To Air HeatPump."""
|
||||
|
||||
_attr_hvac_modes = [*HVAC_MODES_TO_OVERKIZ]
|
||||
_attr_preset_modes = [PRESET_NONE, PRESET_HOLIDAY_MODE]
|
||||
_attr_swing_modes = [*SWING_MODES_TO_OVERKIZ]
|
||||
_attr_target_temperature_step = 1.0
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_translation_key = DOMAIN
|
||||
|
||||
def __init__(
|
||||
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
||||
) -> None:
|
||||
"""Init method."""
|
||||
super().__init__(device_url, coordinator)
|
||||
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.PRESET_MODE
|
||||
)
|
||||
|
||||
if self.device.states.get(SWING_STATE):
|
||||
self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
|
||||
|
||||
if self._attr_device_info:
|
||||
self._attr_device_info["manufacturer"] = "Hitachi"
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode:
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
if (
|
||||
main_op_state := self.device.states[MAIN_OPERATION_STATE]
|
||||
) and main_op_state.value_as_str:
|
||||
if main_op_state.value_as_str.lower() == OverkizCommandParam.OFF:
|
||||
return HVACMode.OFF
|
||||
|
||||
if (
|
||||
mode_change_state := self.device.states[MODE_CHANGE_STATE]
|
||||
) and mode_change_state.value_as_str:
|
||||
sanitized_value = mode_change_state.value_as_str.lower()
|
||||
return OVERKIZ_TO_HVAC_MODES[sanitized_value]
|
||||
|
||||
return HVACMode.OFF
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
await self._global_control(main_operation=OverkizCommandParam.OFF)
|
||||
else:
|
||||
await self._global_control(
|
||||
main_operation=OverkizCommandParam.ON,
|
||||
hvac_mode=HVAC_MODES_TO_OVERKIZ[hvac_mode],
|
||||
)
|
||||
|
||||
@property
|
||||
def fan_mode(self) -> str | None:
|
||||
"""Return the fan setting."""
|
||||
if (state := self.device.states[FAN_SPEED_STATE]) and state.value_as_str:
|
||||
return OVERKIZ_TO_FAN_MODES[state.value_as_str]
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> list[str] | None:
|
||||
"""Return the list of available fan modes."""
|
||||
return [*FAN_MODES_TO_OVERKIZ]
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new target fan mode."""
|
||||
await self._global_control(fan_mode=FAN_MODES_TO_OVERKIZ[fan_mode])
|
||||
|
||||
@property
|
||||
def swing_mode(self) -> str | None:
|
||||
"""Return the swing setting."""
|
||||
if (state := self.device.states[SWING_STATE]) and state.value_as_str:
|
||||
return OVERKIZ_TO_SWING_MODES[state.value_as_str]
|
||||
|
||||
return None
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||
"""Set new target swing operation."""
|
||||
await self._global_control(swing_mode=SWING_MODES_TO_OVERKIZ[swing_mode])
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> int | None:
|
||||
"""Return the temperature."""
|
||||
if (
|
||||
temperature := self.device.states[OverkizState.CORE_TARGET_TEMPERATURE]
|
||||
) and temperature.value_as_int:
|
||||
return temperature.value_as_int
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> int | None:
|
||||
"""Return current temperature."""
|
||||
if (state := self.device.states[ROOM_TEMPERATURE_STATE]) and state.value_as_int:
|
||||
return state.value_as_int
|
||||
|
||||
return None
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new temperature."""
|
||||
temperature = cast(float, kwargs.get(ATTR_TEMPERATURE))
|
||||
await self._global_control(target_temperature=int(temperature))
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> str | None:
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
if (state := self.device.states[LEAVE_HOME_STATE]) and state.value_as_str:
|
||||
if state.value_as_str == OverkizCommandParam.ON:
|
||||
return PRESET_HOLIDAY_MODE
|
||||
|
||||
if state.value_as_str == OverkizCommandParam.OFF:
|
||||
return PRESET_NONE
|
||||
|
||||
return None
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
if preset_mode == PRESET_HOLIDAY_MODE:
|
||||
await self._global_control(leave_home=OverkizCommandParam.ON)
|
||||
|
||||
if preset_mode == PRESET_NONE:
|
||||
await self._global_control(leave_home=OverkizCommandParam.OFF)
|
||||
|
||||
def _control_backfill(
|
||||
self, value: str | None, state_name: str, fallback_value: str
|
||||
) -> str:
|
||||
"""Overkiz doesn't accept commands with undefined parameters. This function is guaranteed to return a `str` which is the provided `value` if set, or the current device state if set, or the provided `fallback_value` otherwise."""
|
||||
if value:
|
||||
return value
|
||||
state = self.device.states[state_name]
|
||||
if state and state.value_as_str:
|
||||
return state.value_as_str
|
||||
return fallback_value
|
||||
|
||||
async def _global_control(
|
||||
self,
|
||||
main_operation: str | None = None,
|
||||
target_temperature: int | None = None,
|
||||
fan_mode: str | None = None,
|
||||
hvac_mode: str | None = None,
|
||||
swing_mode: str | None = None,
|
||||
leave_home: str | None = None,
|
||||
) -> None:
|
||||
"""Execute globalControl command with all parameters. There is no option to only set a single parameter, without passing all other values."""
|
||||
|
||||
main_operation = self._control_backfill(
|
||||
main_operation, MAIN_OPERATION_STATE, OverkizCommandParam.ON
|
||||
)
|
||||
target_temperature = target_temperature or self.target_temperature
|
||||
|
||||
fan_mode = self._control_backfill(
|
||||
fan_mode,
|
||||
FAN_SPEED_STATE,
|
||||
OverkizCommandParam.AUTO,
|
||||
)
|
||||
hvac_mode = self._control_backfill(
|
||||
hvac_mode,
|
||||
MODE_CHANGE_STATE,
|
||||
OverkizCommandParam.AUTO,
|
||||
).lower() # Overkiz can return states that have uppercase characters which are not accepted back as commands
|
||||
if hvac_mode.replace(
|
||||
" ", ""
|
||||
) in [ # Overkiz can return states like 'auto cooling' or 'autoHeating' that are not valid commands and need to be converted to 'auto'
|
||||
OverkizCommandParam.AUTOCOOLING,
|
||||
OverkizCommandParam.AUTOHEATING,
|
||||
]:
|
||||
hvac_mode = OverkizCommandParam.AUTO
|
||||
|
||||
swing_mode = self._control_backfill(
|
||||
swing_mode,
|
||||
SWING_STATE,
|
||||
OverkizCommandParam.STOP,
|
||||
)
|
||||
|
||||
leave_home = self._control_backfill(
|
||||
leave_home,
|
||||
LEAVE_HOME_STATE,
|
||||
OverkizCommandParam.OFF,
|
||||
)
|
||||
|
||||
command_data = [
|
||||
main_operation, # Main Operation
|
||||
target_temperature, # Target Temperature
|
||||
fan_mode, # Fan Mode
|
||||
hvac_mode, # Mode
|
||||
swing_mode, # Swing Mode
|
||||
leave_home, # Leave Home
|
||||
]
|
||||
|
||||
await self.executor.async_execute_command(
|
||||
OverkizCommand.GLOBAL_CONTROL, *command_data
|
||||
)
|
Loading…
Reference in New Issue