Support for more features on smartthings AC (#99424)

* ability to set swing mode on samsung AC

* support for windFree mode on samsung AC

* Apply suggestions from code review

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* suggestion from code reviews

* Apply suggestions from code review

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
Jakub Čermák 2023-11-22 20:00:28 +01:00 committed by GitHub
parent 7a727dc3ad
commit 41626ed500
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 151 additions and 13 deletions

View File

@ -13,6 +13,10 @@ from homeassistant.components.climate import (
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
DOMAIN as CLIMATE_DOMAIN,
SWING_BOTH,
SWING_HORIZONTAL,
SWING_OFF,
SWING_VERTICAL,
ClimateEntity,
ClimateEntityFeature,
HVACAction,
@ -71,6 +75,20 @@ STATE_TO_AC_MODE = {
HVACMode.FAN_ONLY: "fanOnly",
}
SWING_TO_FAN_OSCILLATION = {
SWING_BOTH: "all",
SWING_HORIZONTAL: "horizontal",
SWING_VERTICAL: "vertical",
SWING_OFF: "fixed",
}
FAN_OSCILLATION_TO_SWING = {
value: key for key, value in SWING_TO_FAN_OSCILLATION.items()
}
WINDFREE = "windFree"
UNIT_MAP = {"C": UnitOfTemperature.CELSIUS, "F": UnitOfTemperature.FAHRENHEIT}
_LOGGER = logging.getLogger(__name__)
@ -322,18 +340,34 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
"""Define a SmartThings Air Conditioner."""
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
)
_hvac_modes: list[HVACMode]
def __init__(self, device):
def __init__(self, device) -> None:
"""Init the class."""
super().__init__(device)
self._hvac_modes = None
self._hvac_modes = []
self._attr_preset_mode = None
self._attr_preset_modes = self._determine_preset_modes()
self._attr_swing_modes = self._determine_swing_modes()
self._attr_supported_features = self._determine_supported_features()
def _determine_supported_features(self) -> ClimateEntityFeature:
features = (
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
)
if self._device.get_capability(Capability.fan_oscillation_mode):
features |= ClimateEntityFeature.SWING_MODE
if (self._attr_preset_modes is not None) and len(self._attr_preset_modes) > 0:
features |= ClimateEntityFeature.PRESET_MODE
return features
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
await self._device.set_fan_mode(fan_mode, set_status=True)
# setting the fan must reset the preset mode (it deactivates the windFree function)
self._attr_preset_mode = None
# State is set optimistically in the command above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_write_ha_state()
@ -407,12 +441,12 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
self._hvac_modes = list(modes)
@property
def current_temperature(self):
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._device.status.temperature
@property
def extra_state_attributes(self):
def extra_state_attributes(self) -> dict[str, Any]:
"""Return device specific state attributes.
Include attributes from the Demand Response Load Control (drlc)
@ -432,12 +466,12 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
return state_attributes
@property
def fan_mode(self):
def fan_mode(self) -> str:
"""Return the fan setting."""
return self._device.status.fan_mode
@property
def fan_modes(self):
def fan_modes(self) -> list[str]:
"""Return the list of available fan modes."""
return self._device.status.supported_ac_fan_modes
@ -454,11 +488,62 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
return self._hvac_modes
@property
def target_temperature(self):
def target_temperature(self) -> float:
"""Return the temperature we try to reach."""
return self._device.status.cooling_setpoint
@property
def temperature_unit(self):
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return UNIT_MAP.get(self._device.status.attributes[Attribute.temperature].unit)
return UNIT_MAP[self._device.status.attributes[Attribute.temperature].unit]
def _determine_swing_modes(self) -> list[str]:
"""Return the list of available swing modes."""
supported_modes = self._device.status.attributes[
Attribute.supported_fan_oscillation_modes
][0]
supported_swings = [
FAN_OSCILLATION_TO_SWING.get(m, SWING_OFF) for m in supported_modes
]
return supported_swings
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set swing mode."""
fan_oscillation_mode = SWING_TO_FAN_OSCILLATION[swing_mode]
await self._device.set_fan_oscillation_mode(fan_oscillation_mode)
# setting the fan must reset the preset mode (it deactivates the windFree function)
self._attr_preset_mode = None
self.async_schedule_update_ha_state(True)
@property
def swing_mode(self) -> str:
"""Return the swing setting."""
return FAN_OSCILLATION_TO_SWING.get(
self._device.status.fan_oscillation_mode, SWING_OFF
)
def _determine_preset_modes(self) -> list[str] | None:
"""Return a list of available preset modes."""
supported_modes = self._device.status.attributes[
"supportedAcOptionalMode"
].value
if WINDFREE in supported_modes:
return [WINDFREE]
return None
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set special modes (currently only windFree is supported)."""
result = await self._device.command(
"main",
"custom.airConditionerOptionalMode",
"setAcOptionalMode",
[preset_mode],
)
if result:
self._device.status.update_attribute_value("acOptionalMode", preset_mode)
self._attr_preset_mode = preset_mode
self.async_write_ha_state()

View File

@ -15,16 +15,20 @@ from homeassistant.components.climate import (
ATTR_HVAC_ACTION,
ATTR_HVAC_MODE,
ATTR_HVAC_MODES,
ATTR_PRESET_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
DOMAIN as CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE,
ClimateEntityFeature,
HVACAction,
HVACMode,
)
from homeassistant.components.climate.const import ATTR_SWING_MODE
from homeassistant.components.smartthings import climate
from homeassistant.components.smartthings.const import DOMAIN
from homeassistant.const import (
@ -155,6 +159,7 @@ def air_conditioner_fixture(device_factory):
Capability.switch,
Capability.temperature_measurement,
Capability.thermostat_cooling_setpoint,
Capability.fan_oscillation_mode,
],
status={
Attribute.air_conditioner_mode: "auto",
@ -182,6 +187,14 @@ def air_conditioner_fixture(device_factory):
],
Attribute.switch: "on",
Attribute.cooling_setpoint: 23,
"supportedAcOptionalMode": ["windFree"],
Attribute.supported_fan_oscillation_modes: [
"all",
"horizontal",
"vertical",
"fixed",
],
Attribute.fan_oscillation_mode: "vertical",
},
)
device.status.attributes[Attribute.temperature] = Status(24, "C", None)
@ -303,7 +316,10 @@ async def test_air_conditioner_entity_state(
assert state.state == HVACMode.HEAT_COOL
assert (
state.attributes[ATTR_SUPPORTED_FEATURES]
== ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
== ClimateEntityFeature.FAN_MODE
| ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.PRESET_MODE
| ClimateEntityFeature.SWING_MODE
)
assert sorted(state.attributes[ATTR_HVAC_MODES]) == [
HVACMode.COOL,
@ -591,3 +607,40 @@ async def test_entity_and_device_attributes(hass: HomeAssistant, thermostat) ->
assert entry.manufacturer == "Generic manufacturer"
assert entry.hw_version == "v4.56"
assert entry.sw_version == "v7.89"
async def test_set_windfree_off(hass: HomeAssistant, air_conditioner) -> None:
"""Test if the windfree preset can be turned on and is turned off when fan mode is set."""
entity_ids = ["climate.air_conditioner"]
air_conditioner.status.update_attribute_value(Attribute.switch, "on")
await setup_platform(hass, CLIMATE_DOMAIN, devices=[air_conditioner])
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_ids, ATTR_PRESET_MODE: "windFree"},
blocking=True,
)
state = hass.states.get("climate.air_conditioner")
assert state.attributes[ATTR_PRESET_MODE] == "windFree"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
{ATTR_ENTITY_ID: entity_ids, ATTR_FAN_MODE: "low"},
blocking=True,
)
state = hass.states.get("climate.air_conditioner")
assert not state.attributes[ATTR_PRESET_MODE]
async def test_set_swing_mode(hass: HomeAssistant, air_conditioner) -> None:
"""Test the fan swing is set successfully."""
await setup_platform(hass, CLIMATE_DOMAIN, devices=[air_conditioner])
entity_ids = ["climate.air_conditioner"]
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_SWING_MODE,
{ATTR_ENTITY_ID: entity_ids, ATTR_SWING_MODE: "vertical"},
blocking=True,
)
state = hass.states.get("climate.air_conditioner")
assert state.attributes[ATTR_SWING_MODE] == "vertical"