Add support for more ac/fan modes (#1389)

* Add support for more ac/fan modes

* Replace deprecated constants with enums

* Add support for ClimateEntityFeature.TURN_{ON,OFF}

* Fix swing log message

---------

Co-authored-by: Simon Tegelid <simon.tegelid@bitvis.io>
This commit is contained in:
Simon Tegelid
2024-04-15 14:28:34 +02:00
committed by GitHub
parent 490ad9ed5a
commit 7b37f07fd0
4 changed files with 197 additions and 56 deletions

View File

@@ -11,16 +11,20 @@ from homeassistant.components.climate import (
ClimateEntity,
)
from homeassistant.components.climate.const import (
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
HVAC_MODE_AUTO,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
HVACAction,
HVACMode,
PRESET_AWAY,
PRESET_ECO,
PRESET_HOME,
PRESET_NONE,
ClimateEntityFeature,
FAN_AUTO,
FAN_LOW,
FAN_MEDIUM,
FAN_HIGH,
FAN_TOP,
SWING_ON,
SWING_OFF,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
@@ -34,6 +38,8 @@ from homeassistant.const import (
from .common import LocalTuyaEntity, async_setup_entry
from .const import (
CONF_CURRENT_TEMPERATURE_DP,
CONF_TEMP_MAX,
CONF_TEMP_MIN,
CONF_ECO_DP,
CONF_ECO_VALUE,
CONF_HEURISTIC_ACTION,
@@ -49,53 +55,79 @@ from .const import (
CONF_TARGET_PRECISION,
CONF_TARGET_TEMPERATURE_DP,
CONF_TEMPERATURE_STEP,
CONF_HVAC_FAN_MODE_DP,
CONF_HVAC_FAN_MODE_SET,
CONF_HVAC_SWING_MODE_DP,
CONF_HVAC_SWING_MODE_SET,
)
_LOGGER = logging.getLogger(__name__)
HVAC_MODE_SETS = {
"manual/auto": {
HVAC_MODE_HEAT: "manual",
HVAC_MODE_AUTO: "auto",
HVACMode.HEAT: "manual",
HVACMode.AUTO: "auto",
},
"Manual/Auto": {
HVAC_MODE_HEAT: "Manual",
HVAC_MODE_AUTO: "Auto",
HVACMode.HEAT: "Manual",
HVACMode.AUTO: "Auto",
},
"Manual/Program": {
HVAC_MODE_HEAT: "Manual",
HVAC_MODE_AUTO: "Program",
HVACMode.HEAT: "Manual",
HVACMode.AUTO: "Program",
},
"m/p": {
HVAC_MODE_HEAT: "m",
HVAC_MODE_AUTO: "p",
HVACMode.HEAT: "m",
HVACMode.AUTO: "p",
},
"True/False": {
HVAC_MODE_HEAT: True,
HVACMode.HEAT: True,
},
"Auto/Cold/Dry/Wind/Hot": {
HVACMode.HEAT: "hot",
HVACMode.FAN_ONLY: "wind",
HVACMode.DRY: "wet",
HVACMode.COOL: "cold",
HVACMode.AUTO: "auto",
},
"1/0": {
HVAC_MODE_HEAT: "1",
HVAC_MODE_AUTO: "0",
HVACMode.HEAT: "1",
HVACMode.AUTO: "0",
},
}
HVAC_ACTION_SETS = {
"True/False": {
CURRENT_HVAC_HEAT: True,
CURRENT_HVAC_IDLE: False,
HVACAction.HEATING: True,
HVACAction.IDLE: False,
},
"open/close": {
CURRENT_HVAC_HEAT: "open",
CURRENT_HVAC_IDLE: "close",
HVACAction.HEATING: "open",
HVACAction.IDLE: "close",
},
"heating/no_heating": {
CURRENT_HVAC_HEAT: "heating",
CURRENT_HVAC_IDLE: "no_heating",
HVACAction.HEATING: "heating",
HVACAction.IDLE: "no_heating",
},
"Heat/Warming": {
CURRENT_HVAC_HEAT: "Heat",
CURRENT_HVAC_IDLE: "Warming",
HVACAction.HEATING: "Heat",
HVACAction.IDLE: "Warming",
},
}
HVAC_FAN_MODE_SETS = {
"Auto/Low/Middle/High/Strong": {
FAN_AUTO: "auto",
FAN_LOW: "low",
FAN_MEDIUM: "middle",
FAN_HIGH: "high",
FAN_TOP: "strong",
}
}
HVAC_SWING_MODE_SETS = {
"True/False": {
SWING_ON: True,
SWING_OFF: False,
}
}
PRESET_SETS = {
"Manual/Holiday/Program": {
PRESET_AWAY: "Holiday",
@@ -118,16 +150,20 @@ def flow_schema(dps):
return {
vol.Optional(CONF_TARGET_TEMPERATURE_DP): vol.In(dps),
vol.Optional(CONF_CURRENT_TEMPERATURE_DP): vol.In(dps),
vol.Optional(CONF_TEMPERATURE_STEP): vol.In(
vol.Optional(CONF_TEMPERATURE_STEP, default=PRECISION_WHOLE): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP_DP): vol.In(dps),
vol.Optional(CONF_MIN_TEMP_DP): vol.In(dps),
vol.Optional(CONF_PRECISION): vol.In(
vol.Optional(CONF_PRECISION, default=PRECISION_WHOLE): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_HVAC_MODE_DP): vol.In(dps),
vol.Optional(CONF_HVAC_MODE_SET): vol.In(list(HVAC_MODE_SETS.keys())),
vol.Optional(CONF_HVAC_FAN_MODE_DP): vol.In(dps),
vol.Optional(CONF_HVAC_FAN_MODE_SET): vol.In(list(HVAC_FAN_MODE_SETS.keys())),
vol.Optional(CONF_HVAC_ACTION_DP): vol.In(dps),
vol.Optional(CONF_HVAC_ACTION_SET): vol.In(list(HVAC_ACTION_SETS.keys())),
vol.Optional(CONF_ECO_DP): vol.In(dps),
@@ -137,7 +173,7 @@ def flow_schema(dps):
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(
[TEMPERATURE_CELSIUS, TEMPERATURE_FAHRENHEIT]
),
vol.Optional(CONF_TARGET_PRECISION): vol.In(
vol.Optional(CONF_TARGET_PRECISION, default=PRECISION_WHOLE): vol.In(
[PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
),
vol.Optional(CONF_HEURISTIC_ACTION): bool,
@@ -160,6 +196,8 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
self._target_temperature = None
self._current_temperature = None
self._hvac_mode = None
self._fan_mode = None
self._swing_mode = None
self._preset_mode = None
self._hvac_action = None
self._precision = self._config.get(CONF_PRECISION, DEFAULT_PRECISION)
@@ -170,6 +208,14 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
self._conf_hvac_mode_set = HVAC_MODE_SETS.get(
self._config.get(CONF_HVAC_MODE_SET), {}
)
self._conf_hvac_fan_mode_dp = self._config.get(CONF_HVAC_FAN_MODE_DP)
self._conf_hvac_fan_mode_set = HVAC_FAN_MODE_SETS.get(
self._config.get(CONF_HVAC_FAN_MODE_SET), {}
)
self._conf_hvac_swing_mode_dp = self._config.get(CONF_HVAC_SWING_MODE_DP)
self._conf_hvac_swing_mode_set = HVAC_SWING_MODE_SETS.get(
self._config.get(CONF_HVAC_SWING_MODE_SET), {}
)
self._conf_preset_dp = self._config.get(CONF_PRESET_DP)
self._conf_preset_set = PRESET_SETS.get(self._config.get(CONF_PRESET_SET), {})
self._conf_hvac_action_dp = self._config.get(CONF_HVAC_ACTION_DP)
@@ -186,13 +232,17 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
@property
def supported_features(self):
"""Flag supported features."""
supported_features = 0
supported_features = ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
if self.has_config(CONF_TARGET_TEMPERATURE_DP):
supported_features = supported_features | ClimateEntityFeature.TARGET_TEMPERATURE
if self.has_config(CONF_MAX_TEMP_DP):
supported_features = supported_features | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
if self.has_config(CONF_PRESET_DP) or self.has_config(CONF_ECO_DP):
supported_features = supported_features | ClimateEntityFeature.PRESET_MODE
if self.has_config(CONF_HVAC_FAN_MODE_DP) and self.has_config(CONF_HVAC_FAN_MODE_SET):
supported_features = supported_features | ClimateEntityFeature.FAN_MODE
if self.has_config(CONF_HVAC_SWING_MODE_DP):
supported_features = supported_features | ClimateEntityFeature.SWING_MODE
return supported_features
@property
@@ -225,7 +275,7 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
"""Return the list of available operation modes."""
if not self.has_config(CONF_HVAC_MODE_DP):
return None
return list(self._conf_hvac_mode_set) + [HVAC_MODE_OFF]
return list(self._conf_hvac_mode_set) + [HVACMode.OFF]
@property
def hvac_action(self):
@@ -234,22 +284,22 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
Need to be one of CURRENT_HVAC_*.
"""
if self._config.get(CONF_HEURISTIC_ACTION, False):
if self._hvac_mode == HVAC_MODE_HEAT:
if self._hvac_mode == HVACMode.HEAT:
if self._current_temperature < (
self._target_temperature - self._precision
):
self._hvac_action = CURRENT_HVAC_HEAT
self._hvac_action = HVACAction.HEATING
if self._current_temperature == (
self._target_temperature - self._precision
):
if self._hvac_action == CURRENT_HVAC_HEAT:
self._hvac_action = CURRENT_HVAC_HEAT
if self._hvac_action == CURRENT_HVAC_IDLE:
self._hvac_action = CURRENT_HVAC_IDLE
if self._hvac_action == HVACAction.HEATING:
self._hvac_action = HVACAction.HEATING
if self._hvac_action == HVACAction.IDLE:
self._hvac_action = HVACAction.IDLE
if (
self._current_temperature + self._precision
) > self._target_temperature:
self._hvac_action = CURRENT_HVAC_IDLE
self._hvac_action = HVACAction.IDLE
return self._hvac_action
return self._hvac_action
@@ -286,12 +336,26 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
@property
def fan_mode(self):
"""Return the fan setting."""
return NotImplementedError()
return self._fan_mode
@property
def fan_modes(self):
"""Return the list of available fan modes."""
return NotImplementedError()
if not self.has_config(CONF_HVAC_FAN_MODE_DP):
return None
return list(self._conf_hvac_fan_mode_set)
@property
def swing_mode(self):
"""Return the swing setting."""
return self._swing_mode
@property
def swing_modes(self):
"""Return the list of available swing modes."""
if not self.has_config(CONF_HVAC_SWING_MODE_DP):
return None
return list(self._conf_hvac_swing_mode_set)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -301,13 +365,21 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
temperature, self._config[CONF_TARGET_TEMPERATURE_DP]
)
def set_fan_mode(self, fan_mode):
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
return NotImplementedError()
if self._conf_hvac_fan_mode_dp is None:
_LOGGER.error("Fan speed unsupported (no DP)")
return
if fan_mode not in self._conf_hvac_fan_mode_set:
_LOGGER.error("Unsupported fan_mode: %s" % fan_mode)
return
await self._device.set_dp(
self._conf_hvac_fan_mode_set[fan_mode], self._conf_hvac_fan_mode_dp
)
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode."""
if hvac_mode == HVAC_MODE_OFF:
if hvac_mode == HVACMode.OFF:
await self._device.set_dp(False, self._dp_id)
return
if not self._state and self._conf_hvac_mode_dp != self._dp_id:
@@ -318,6 +390,18 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
self._conf_hvac_mode_set[hvac_mode], self._conf_hvac_mode_dp
)
async def async_set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
if self._conf_hvac_swing_mode_dp is None:
_LOGGER.error("Swing mode unsupported (no DP)")
return
if swing_mode not in self._conf_hvac_swing_mode_set:
_LOGGER.error("Unsupported swing_mode: %s" % swing_mode)
return
await self._device.set_dp(
self._conf_hvac_swing_mode_set[swing_mode], self._conf_hvac_swing_mode_dp
)
async def async_turn_on(self) -> None:
"""Turn the entity on."""
await self._device.set_dp(True, self._dp_id)
@@ -340,22 +424,14 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
"""Return the minimum temperature."""
if self.has_config(CONF_MIN_TEMP_DP):
return self.dps_conf(CONF_MIN_TEMP_DP)
# DEFAULT_MIN_TEMP is in C
if self.temperature_unit == UnitOfTemperature.FAHRENHEIT:
return DEFAULT_MIN_TEMP * 1.8 + 32
else:
return DEFAULT_MIN_TEMP
return self._config[CONF_TEMP_MIN]
@property
def max_temp(self):
"""Return the maximum temperature."""
if self.has_config(CONF_MAX_TEMP_DP):
return self.dps_conf(CONF_MAX_TEMP_DP)
# DEFAULT_MAX_TEMP is in C
if self.temperature_unit == UnitOfTemperature.FAHRENHEIT:
return DEFAULT_MAX_TEMP * 1.8 + 32
else:
return DEFAULT_MAX_TEMP
return self._config[CONF_TEMP_MAX]
def status_updated(self):
"""Device status was updated."""
@@ -388,7 +464,7 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
# Update the HVAC status
if self.has_config(CONF_HVAC_MODE_DP):
if not self._state:
self._hvac_mode = HVAC_MODE_OFF
self._hvac_mode = HVACMode.OFF
else:
for mode, value in self._conf_hvac_mode_set.items():
if self.dps_conf(CONF_HVAC_MODE_DP) == value:
@@ -396,7 +472,28 @@ class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
break
else:
# in case hvac mode and preset share the same dp
self._hvac_mode = HVAC_MODE_AUTO
self._hvac_mode = HVACMode.AUTO
# Update the fan status
if self.has_config(CONF_HVAC_FAN_MODE_DP):
for mode, value in self._conf_hvac_fan_mode_set.items():
if self.dps_conf(CONF_HVAC_FAN_MODE_DP) == value:
self._fan_mode = mode
break
else:
# in case fan mode and preset share the same dp
_LOGGER.debug("Unknown fan mode %s" % self.dps_conf(CONF_HVAC_FAN_MODE_DP))
self._fan_mode = FAN_AUTO
# Update the swing status
if self.has_config(CONF_HVAC_SWING_MODE_DP):
for mode, value in self._conf_hvac_swing_mode_set.items():
if self.dps_conf(CONF_HVAC_SWING_MODE_DP) == value:
self._swing_mode = mode
break
else:
_LOGGER.debug("Unknown swing mode %s" % self.dps_conf(CONF_HVAC_SWING_MODE_DP))
self._swing_mode = SWING_OFF
# Update the current action
for action, value in self._conf_hvac_action_set.items():