hOn/custom_components/hon/climate.py

411 lines
14 KiB
Python
Raw Normal View History

2023-04-26 23:57:44 +02:00
import logging
2023-05-28 07:50:59 +02:00
from dataclasses import dataclass
from typing import Any
2023-04-26 23:57:44 +02:00
from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityDescription,
)
from homeassistant.components.climate.const import (
SWING_OFF,
SWING_BOTH,
SWING_VERTICAL,
SWING_HORIZONTAL,
ClimateEntityFeature,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_TEMPERATURE,
UnitOfTemperature,
2023-04-26 23:57:44 +02:00
)
from homeassistant.core import callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import HomeAssistantType
from pyhon.appliance import HonAppliance
from pyhon.parameter.range import HonParameterRange
2023-07-11 00:17:19 +02:00
from .const import HON_HVAC_MODE, HON_FAN, DOMAIN, HON_HVAC_PROGRAM
2023-05-25 01:30:33 +02:00
from .hon import HonEntity
2023-04-26 23:57:44 +02:00
_LOGGER = logging.getLogger(__name__)
2023-05-28 07:50:59 +02:00
2024-01-11 00:41:49 +01:00
@dataclass(frozen=True)
2023-05-28 07:50:59 +02:00
class HonACClimateEntityDescription(ClimateEntityDescription):
pass
2024-01-11 00:41:49 +01:00
@dataclass(frozen=True)
2023-05-28 17:38:56 +02:00
class HonClimateEntityDescription(ClimateEntityDescription):
mode: HVACMode = HVACMode.AUTO
2023-05-28 07:50:59 +02:00
CLIMATES: dict[
str, tuple[HonACClimateEntityDescription | HonClimateEntityDescription, ...]
] = {
2023-05-10 18:13:05 +02:00
"AC": (
2023-05-28 07:50:59 +02:00
HonACClimateEntityDescription(
2023-05-10 18:13:05 +02:00
key="settings",
name="Air Conditioner",
icon="mdi:air-conditioner",
translation_key="air_conditioner",
),
),
2023-05-28 07:50:59 +02:00
"REF": (
2023-05-28 17:38:56 +02:00
HonClimateEntityDescription(
2023-05-28 07:50:59 +02:00
key="settings.tempSelZ1",
2023-05-28 17:38:56 +02:00
mode=HVACMode.COOL,
2023-05-28 07:50:59 +02:00
name="Fridge",
icon="mdi:thermometer",
translation_key="fridge",
),
2023-05-28 17:38:56 +02:00
HonClimateEntityDescription(
2023-05-28 07:50:59 +02:00
key="settings.tempSelZ2",
2023-05-28 17:38:56 +02:00
mode=HVACMode.COOL,
2023-05-28 07:50:59 +02:00
name="Freezer",
icon="mdi:snowflake-thermometer",
translation_key="freezer",
),
2023-11-20 17:35:58 +01:00
HonClimateEntityDescription(
key="settings.tempSelZ3",
mode=HVACMode.COOL,
name="MyZone",
icon="mdi:thermometer",
translation_key="my_zone",
),
2023-05-28 07:50:59 +02:00
),
2023-05-28 17:38:56 +02:00
"OV": (
HonClimateEntityDescription(
key="settings.tempSel",
mode=HVACMode.HEAT,
name="Oven",
icon="mdi:thermometer",
translation_key="oven",
),
),
2023-06-10 06:44:19 +02:00
"WC": (
HonClimateEntityDescription(
key="settings.tempSel",
mode=HVACMode.COOL,
name="Wine Cellar",
icon="mdi:thermometer",
translation_key="wine",
),
HonClimateEntityDescription(
key="settings.tempSelZ2",
mode=HVACMode.COOL,
name="Wine Cellar",
icon="mdi:thermometer",
translation_key="wine",
),
),
2023-04-26 23:57:44 +02:00
}
async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
2023-05-25 01:30:33 +02:00
entities = []
entity: HonClimateEntity | HonACClimateEntity
2023-05-25 01:30:33 +02:00
for device in hass.data[DOMAIN][entry.unique_id].appliances:
for description in CLIMATES.get(device.appliance_type, []):
2023-05-28 07:50:59 +02:00
if isinstance(description, HonACClimateEntityDescription):
if description.key not in list(device.commands):
continue
entity = HonACClimateEntity(hass, entry, device, description)
2023-05-28 17:38:56 +02:00
elif isinstance(description, HonClimateEntityDescription):
2023-05-28 07:50:59 +02:00
if description.key not in device.available_settings:
continue
2023-05-28 17:38:56 +02:00
entity = HonClimateEntity(hass, entry, device, description)
2023-05-28 07:50:59 +02:00
else:
continue # type: ignore[unreachable]
2023-05-25 01:30:33 +02:00
await entity.coordinator.async_config_entry_first_refresh()
entities.append(entity)
async_add_entities(entities)
2023-04-26 23:57:44 +02:00
2023-05-28 07:50:59 +02:00
class HonACClimateEntity(HonEntity, ClimateEntity):
entity_description: HonACClimateEntityDescription
def __init__(
self,
hass: HomeAssistantType,
entry: ConfigEntry,
device: HonAppliance,
description: HonACClimateEntityDescription,
) -> None:
2023-05-28 00:30:08 +02:00
super().__init__(hass, entry, device, description)
2023-04-26 23:57:44 +02:00
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
2023-06-21 19:52:32 +02:00
self._set_temperature_bound()
2023-04-26 23:57:44 +02:00
self._attr_hvac_modes = [HVACMode.OFF]
for mode in device.settings["settings.machMode"].values:
2023-06-13 00:14:51 +02:00
self._attr_hvac_modes.append(HON_HVAC_MODE[int(mode)])
2023-06-21 19:52:32 +02:00
self._attr_preset_modes = []
for mode in device.settings["startProgram.program"].values:
self._attr_preset_modes.append(mode)
2023-04-26 23:57:44 +02:00
self._attr_swing_modes = [
SWING_OFF,
SWING_VERTICAL,
SWING_HORIZONTAL,
SWING_BOTH,
]
self._attr_supported_features = (
2024-03-18 01:11:23 +01:00
ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TARGET_TEMPERATURE
2023-04-26 23:57:44 +02:00
| ClimateEntityFeature.FAN_MODE
| ClimateEntityFeature.SWING_MODE
2023-06-21 19:52:32 +02:00
| ClimateEntityFeature.PRESET_MODE
2023-04-26 23:57:44 +02:00
)
2023-05-19 01:27:44 +02:00
self._handle_coordinator_update(update=False)
2023-05-08 02:05:04 +02:00
2023-06-21 19:52:32 +02:00
def _set_temperature_bound(self) -> None:
2023-07-24 21:37:48 +02:00
temperature = self._device.settings["settings.tempSel"]
if not isinstance(temperature, HonParameterRange):
raise ValueError
self._attr_max_temp = temperature.max
self._attr_target_temperature_step = temperature.step
self._attr_min_temp = temperature.min
2023-06-21 19:52:32 +02:00
@property
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
return self._device.get("tempSel", 0.0)
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._device.get("tempIndoor", 0.0)
async def async_set_temperature(self, **kwargs: Any) -> None:
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
return
self._device.settings["settings.tempSel"].value = str(int(temperature))
await self._device.commands["settings"].send()
self.async_write_ha_state()
2023-05-28 17:38:56 +02:00
@property
def hvac_mode(self) -> HVACMode:
2023-06-13 00:14:51 +02:00
if self._device.get("onOffStatus") == 0:
2023-05-28 17:38:56 +02:00
return HVACMode.OFF
2023-05-28 07:50:59 +02:00
else:
2023-05-28 17:38:56 +02:00
return HON_HVAC_MODE[self._device.get("machMode")]
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
self._attr_hvac_mode = hvac_mode
2023-04-26 23:57:44 +02:00
if hvac_mode == HVACMode.OFF:
2023-06-21 19:52:32 +02:00
await self._device.commands["stopProgram"].send()
self._device.sync_command("stopProgram", "settings")
2023-04-26 23:57:44 +02:00
else:
2023-06-21 19:52:32 +02:00
self._device.settings["settings.onOffStatus"].value = "1"
setting = self._device.settings["settings.machMode"]
2023-06-22 13:18:45 +02:00
modes = {HON_HVAC_MODE[int(number)]: number for number in setting.values}
2023-07-11 00:17:19 +02:00
if hvac_mode in modes:
setting.value = modes[hvac_mode]
else:
await self.async_set_preset_mode(HON_HVAC_PROGRAM[hvac_mode])
return
2023-06-21 19:52:32 +02:00
await self._device.commands["settings"].send()
self.async_write_ha_state()
@property
def preset_mode(self) -> str | None:
"""Return the current Preset for this channel."""
return None
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the new preset mode."""
2023-07-11 00:17:19 +02:00
if program := self._device.settings.get("startProgram.program"):
2023-06-21 19:52:32 +02:00
program.value = preset_mode
self._device.sync_command("startProgram", "settings")
self._set_temperature_bound()
self._handle_coordinator_update(update=False)
await self.coordinator.async_refresh()
self._attr_preset_mode = preset_mode
await self._device.commands["startProgram"].send()
2023-05-17 00:01:33 +02:00
self.async_write_ha_state()
2023-04-26 23:57:44 +02:00
2023-07-11 00:17:19 +02:00
@property
def fan_modes(self) -> list[str]:
"""Return the list of available fan modes."""
fan_modes = []
for mode in reversed(self._device.settings["settings.windSpeed"].values):
fan_modes.append(HON_FAN[int(mode)])
return fan_modes
@property
def fan_mode(self) -> str | None:
"""Return the fan setting."""
return HON_FAN[self._device.get("windSpeed")]
async def async_set_fan_mode(self, fan_mode: str) -> None:
2023-07-11 00:17:19 +02:00
fan_modes = {}
for mode in reversed(self._device.settings["settings.windSpeed"].values):
fan_modes[HON_FAN[int(mode)]] = mode
self._device.settings["settings.windSpeed"].value = str(fan_modes[fan_mode])
self._attr_fan_mode = fan_mode
2023-05-08 02:05:04 +02:00
await self._device.commands["settings"].send()
2023-05-17 00:01:33 +02:00
self.async_write_ha_state()
2023-04-26 23:57:44 +02:00
@property
def swing_mode(self) -> str | None:
"""Return the swing setting."""
horizontal = self._device.get("windDirectionHorizontal")
vertical = self._device.get("windDirectionVertical")
2023-06-13 00:14:51 +02:00
if horizontal == 7 and vertical == 8:
return SWING_BOTH
if horizontal == 7:
return SWING_HORIZONTAL
if vertical == 8:
return SWING_VERTICAL
return SWING_OFF
async def async_set_swing_mode(self, swing_mode: str) -> None:
2023-05-08 02:05:04 +02:00
horizontal = self._device.settings["settings.windDirectionHorizontal"]
vertical = self._device.settings["settings.windDirectionVertical"]
2023-04-26 23:57:44 +02:00
if swing_mode in [SWING_BOTH, SWING_HORIZONTAL]:
horizontal.value = "7"
if swing_mode in [SWING_BOTH, SWING_VERTICAL]:
vertical.value = "8"
if swing_mode in [SWING_OFF, SWING_HORIZONTAL] and vertical.value == "8":
vertical.value = "5"
if swing_mode in [SWING_OFF, SWING_VERTICAL] and horizontal.value == "7":
horizontal.value = "0"
self._attr_swing_mode = swing_mode
2023-05-08 02:05:04 +02:00
await self._device.commands["settings"].send()
2023-05-17 00:01:33 +02:00
self.async_write_ha_state()
2023-04-26 23:57:44 +02:00
@callback
def _handle_coordinator_update(self, update: bool = True) -> None:
2023-05-19 01:27:44 +02:00
if update:
self.async_write_ha_state()
2023-05-28 07:50:59 +02:00
2023-05-28 17:38:56 +02:00
class HonClimateEntity(HonEntity, ClimateEntity):
2023-06-09 05:56:52 +02:00
entity_description: HonClimateEntityDescription
2023-05-28 17:38:56 +02:00
def __init__(
self,
hass: HomeAssistantType,
entry: ConfigEntry,
device: HonAppliance,
description: HonClimateEntityDescription,
) -> None:
2023-05-28 07:50:59 +02:00
super().__init__(hass, entry, device, description)
2024-03-18 01:11:23 +01:00
self._attr_supported_features = (
ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TARGET_TEMPERATURE
)
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
2023-05-28 17:38:56 +02:00
self._set_temperature_bound()
2023-05-28 07:50:59 +02:00
2023-05-28 17:38:56 +02:00
self._attr_hvac_modes = [description.mode]
2023-06-10 06:44:19 +02:00
if "stopProgram" in device.commands:
2024-03-18 01:11:23 +01:00
self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
2023-05-28 17:38:56 +02:00
self._attr_hvac_modes += [HVACMode.OFF]
modes = []
else:
modes = ["no_mode"]
2023-05-28 07:50:59 +02:00
for mode, data in device.commands["startProgram"].categories.items():
2023-05-28 17:38:56 +02:00
if mode not in data.parameters["program"].values:
continue
if (zone := data.parameters.get("zone")) and isinstance(
self.entity_description.name, str
):
2023-05-28 07:50:59 +02:00
if self.entity_description.name.lower() in zone.values:
modes.append(mode)
2023-05-28 17:38:56 +02:00
else:
modes.append(mode)
2023-11-20 17:35:58 +01:00
if modes:
2024-03-18 01:11:23 +01:00
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
2023-11-20 17:35:58 +01:00
self._attr_preset_modes = modes
2023-05-28 07:50:59 +02:00
2023-05-28 17:38:56 +02:00
self._handle_coordinator_update(update=False)
2023-05-28 07:50:59 +02:00
@property
2023-06-10 06:44:19 +02:00
def target_temperature(self) -> float | None:
2023-05-28 07:50:59 +02:00
"""Return the temperature we try to reach."""
return self._device.get(self.entity_description.key, 0.0)
2023-05-28 07:50:59 +02:00
@property
2023-06-10 06:44:19 +02:00
def current_temperature(self) -> float | None:
2023-05-28 07:50:59 +02:00
"""Return the current temperature."""
temp_key = self.entity_description.key.split(".")[-1].replace("Sel", "")
return self._device.get(temp_key, 0.0)
2023-05-28 07:50:59 +02:00
async def async_set_temperature(self, **kwargs: Any) -> None:
2023-05-28 07:50:59 +02:00
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
return
2023-05-28 07:50:59 +02:00
self._device.settings[self.entity_description.key].value = str(int(temperature))
await self._device.commands["settings"].send()
self.async_write_ha_state()
2023-05-28 17:38:56 +02:00
@property
def hvac_mode(self) -> HVACMode:
2023-06-13 00:14:51 +02:00
if self._device.get("onOffStatus") == 0:
2023-05-28 17:38:56 +02:00
return HVACMode.OFF
else:
return self.entity_description.mode
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
2023-05-28 17:38:56 +02:00
if len(self.hvac_modes) <= 1:
return
if hvac_mode == HVACMode.OFF:
await self._device.commands["stopProgram"].send()
else:
await self._device.commands["startProgram"].send()
self._attr_hvac_mode = hvac_mode
self.async_write_ha_state()
2023-05-28 07:50:59 +02:00
@property
def preset_mode(self) -> str | None:
"""Return the current Preset for this channel."""
2023-05-28 17:38:56 +02:00
if self._device.get("onOffStatus") is not None:
return self._device.get("programName", "")
else:
return self._device.get(
f"mode{self.entity_description.key[-2:]}", "no_mode"
)
2023-05-28 07:50:59 +02:00
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the new preset mode."""
2023-09-29 17:24:21 +02:00
if preset_mode == "no_mode" and HVACMode.OFF in self.hvac_modes:
command = "stopProgram"
elif preset_mode == "no_mode":
command = "settings"
self._device.commands["settings"].reset()
else:
command = "startProgram"
2023-05-28 17:38:56 +02:00
if program := self._device.settings.get(f"{command}.program"):
program.value = preset_mode
zone = self._device.settings.get(f"{command}.zone")
if zone and isinstance(self.entity_description.name, str):
2023-05-28 17:38:56 +02:00
zone.value = self.entity_description.name.lower()
self._device.sync_command(command, "settings")
self._set_temperature_bound()
2023-09-29 17:24:21 +02:00
self._attr_preset_mode = preset_mode
2023-05-28 17:38:56 +02:00
await self.coordinator.async_refresh()
await self._device.commands[command].send()
2023-05-28 07:50:59 +02:00
self.async_write_ha_state()
def _set_temperature_bound(self) -> None:
temperature = self._device.settings[self.entity_description.key]
if not isinstance(temperature, HonParameterRange):
raise ValueError
self._attr_max_temp = temperature.max
self._attr_target_temperature_step = temperature.step
self._attr_min_temp = temperature.min
2023-05-28 17:38:56 +02:00
2023-05-28 07:50:59 +02:00
@callback
def _handle_coordinator_update(self, update: bool = True) -> None:
2023-05-28 07:50:59 +02:00
if update:
self.async_write_ha_state()