From 90eb6c23023470eba00ce2f5a93abbc6a80e5710 Mon Sep 17 00:00:00 2001 From: Zach Langbert Date: Sat, 17 Feb 2024 17:34:09 -0800 Subject: [PATCH] add support for emergency heat mode through a climate entity preset (#27) --- custom_components/daikinone/climate.py | 47 ++++++++++++++++++- custom_components/daikinone/daikinone.py | 3 ++ .../daikinone/translations/en.json | 15 +++++- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/custom_components/daikinone/climate.py b/custom_components/daikinone/climate.py index 0b4d93b..0862eab 100644 --- a/custom_components/daikinone/climate.py +++ b/custom_components/daikinone/climate.py @@ -1,3 +1,4 @@ +from enum import Enum import logging from typing import Callable @@ -50,6 +51,11 @@ async def async_setup_entry( async_add_entities(entities, True) +class DaikinOneThermostatPresetMode(Enum): + NONE = "none" + EMERGENCY_HEAT = "emergency_heat" + + class DaikinOneThermostat(ClimateEntity): """Thermostat entity for Daikin One""" @@ -71,6 +77,7 @@ def __init__( self._data = data self._thermostat = thermostat + self._attr_translation_key = "daikinone_thermostat" self._attr_unique_id = f"{self._thermostat.id}-climate" self._attr_temperature_unit = UnitOfTemperature.CELSIUS self._attr_supported_features = ( @@ -79,6 +86,8 @@ def __init__( | ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ) + self._attr_hvac_modes = self.get_hvac_modes() + self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self._thermostat.id)}, name=f"{self._thermostat.name} Thermostat", @@ -86,7 +95,6 @@ def __init__( model=self._thermostat.model, sw_version=self._thermostat.firmware_version, ) - self._attr_hvac_modes = self.get_hvac_modes() # These attributes must be initialized otherwise HA `CachedProperties` doesn't create a # backing prop. If they are not initialized, climate will error during setup because we support @@ -96,6 +104,15 @@ def __init__( self._attr_target_temperature_low = None self._attr_target_temperature_high = None + # Set up preset modes based on thermostat capabilities. The preset climate feature will only be + # enabled if at least one preset is detected as supported. + self._attr_preset_modes = [DaikinOneThermostatPresetMode.NONE.value] + self._attr_preset_mode = None + + if DaikinThermostatCapability.EMERGENCY_HEAT in self._thermostat.capabilities: + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE + self._attr_preset_modes += [DaikinOneThermostatPresetMode.EMERGENCY_HEAT.value] + def get_hvac_modes(self) -> list[HVACMode]: modes: list[HVACMode] = [] @@ -129,6 +146,9 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: case _: raise ValueError(f"Attempted to set unsupported HVAC mode: {hvac_mode}") + await self.set_thermostat_mode(target_mode) + + async def set_thermostat_mode(self, target_mode: DaikinThermostatMode) -> None: log.debug("Setting thermostat mode to %s", target_mode) # update thermostat mode @@ -143,6 +163,28 @@ def update(t: DaikinThermostat): check=lambda t: t.mode == target_mode, ) + async def async_set_preset_mode(self, preset_mode: str): + """Set new target preset mode.""" + match preset_mode: + + case DaikinOneThermostatPresetMode.EMERGENCY_HEAT.value: + await self.set_thermostat_mode(DaikinThermostatMode.AUX_HEAT) + + case DaikinOneThermostatPresetMode.NONE.value: + match self._thermostat.mode: + + # turning off emergency heat should set the thermostat mode to heat + case DaikinThermostatMode.AUX_HEAT: + await self.set_thermostat_mode(DaikinThermostatMode.HEAT) + + # any other thermostat mode should already be "none", and if its not, + # we don't need to do anything + case _: + pass + + case _: + raise ValueError(f"Attempted to set unsupported preset mode: {preset_mode}") + async def async_set_temperature(self, **kwargs: float) -> None: """Set new target temperature(s).""" @@ -232,6 +274,8 @@ def update_entity_attributes(self) -> None: self._attr_current_temperature = self._thermostat.indoor_temperature.celsius self._attr_current_humidity = self._thermostat.indoor_humidity + # hvac current mode and preset + self._attr_preset_mode = DaikinOneThermostatPresetMode.NONE.value match self._thermostat.mode: case DaikinThermostatMode.AUTO: self._attr_hvac_mode = HVACMode.HEAT_COOL @@ -241,6 +285,7 @@ def update_entity_attributes(self) -> None: self._attr_hvac_mode = HVACMode.COOL case DaikinThermostatMode.AUX_HEAT: self._attr_hvac_mode = HVACMode.HEAT + self._attr_preset_mode = DaikinOneThermostatPresetMode.EMERGENCY_HEAT.value case DaikinThermostatMode.OFF: self._attr_hvac_mode = HVACMode.OFF diff --git a/custom_components/daikinone/daikinone.py b/custom_components/daikinone/daikinone.py index 5e5ad8a..523c246 100644 --- a/custom_components/daikinone/daikinone.py +++ b/custom_components/daikinone/daikinone.py @@ -119,6 +119,7 @@ class DaikinEEVCoil(DaikinEquipment): class DaikinThermostatCapability(Enum): HEAT = auto() COOL = auto() + EMERGENCY_HEAT = auto() class DaikinThermostatMode(Enum): @@ -252,6 +253,8 @@ def __map_thermostat(self, payload: DaikinDeviceDataResponse) -> DaikinThermosta capabilities.add(DaikinThermostatCapability.HEAT) if payload.data["ctSystemCapCool"]: capabilities.add(DaikinThermostatCapability.COOL) + if payload.data["ctSystemCapEmergencyHeat"]: + capabilities.add(DaikinThermostatCapability.EMERGENCY_HEAT) schedule = DaikinThermostatSchedule(enabled=payload.data["schedEnabled"]) diff --git a/custom_components/daikinone/translations/en.json b/custom_components/daikinone/translations/en.json index 133a432..07643d6 100644 --- a/custom_components/daikinone/translations/en.json +++ b/custom_components/daikinone/translations/en.json @@ -13,5 +13,18 @@ "error": { "auth_failed": "Authentication failed, please check your credentials and try again. Check the logs for more info." } + }, + "entity": { + "climate": { + "daikinone_thermostat": { + "state_attributes": { + "preset_mode": { + "state": { + "emergency_heat": "Emergency Heat" + } + } + } + } + } } -} \ No newline at end of file +}