mirror of
https://github.com/home-assistant/core.git
synced 2025-04-26 18:27:51 +00:00
Handle Auto Fan and MyFan in Advantage Air (#95594)
* Decouple climate from MyFan * Add tests * Prepare for auto change * Handle both modes * Fix import * Remove reference to FAN map * Correct auto fan logic * Remove multiline ternary operator Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Fix coverage * fix tests * ruff * Test auto fan mode with snapshot * add more testing * Add switch testing * Fix a bug caught by new tests * Remove ineffective snapshot tests * Readd snapshots but use args --------- Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
parent
615cd56f03
commit
9dde42a023
@ -21,6 +21,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
ADVANTAGE_AIR_AUTOFAN_ENABLED,
|
||||
ADVANTAGE_AIR_STATE_CLOSE,
|
||||
ADVANTAGE_AIR_STATE_OFF,
|
||||
ADVANTAGE_AIR_STATE_ON,
|
||||
@ -39,16 +40,6 @@ ADVANTAGE_AIR_HVAC_MODES = {
|
||||
}
|
||||
HASS_HVAC_MODES = {v: k for k, v in ADVANTAGE_AIR_HVAC_MODES.items()}
|
||||
|
||||
ADVANTAGE_AIR_FAN_MODES = {
|
||||
"autoAA": FAN_AUTO,
|
||||
"low": FAN_LOW,
|
||||
"medium": FAN_MEDIUM,
|
||||
"high": FAN_HIGH,
|
||||
}
|
||||
HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
|
||||
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
|
||||
|
||||
ADVANTAGE_AIR_AUTOFAN = "aaAutoFanModeEnabled"
|
||||
ADVANTAGE_AIR_MYZONE = "MyZone"
|
||||
ADVANTAGE_AIR_MYAUTO = "MyAuto"
|
||||
ADVANTAGE_AIR_MYAUTO_ENABLED = "myAutoModeEnabled"
|
||||
@ -56,6 +47,7 @@ ADVANTAGE_AIR_MYTEMP = "MyTemp"
|
||||
ADVANTAGE_AIR_MYTEMP_ENABLED = "climateControlModeEnabled"
|
||||
ADVANTAGE_AIR_HEAT_TARGET = "myAutoHeatTargetTemp"
|
||||
ADVANTAGE_AIR_COOL_TARGET = "myAutoCoolTargetTemp"
|
||||
ADVANTAGE_AIR_MYFAN = "autoAA"
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@ -85,27 +77,25 @@ async def async_setup_entry(
|
||||
class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
"""AdvantageAir AC unit."""
|
||||
|
||||
_attr_fan_modes = [FAN_LOW, FAN_MEDIUM, FAN_HIGH]
|
||||
_attr_fan_modes = [FAN_LOW, FAN_MEDIUM, FAN_HIGH, FAN_AUTO]
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_attr_name = None
|
||||
|
||||
_attr_hvac_modes = [
|
||||
HVACMode.OFF,
|
||||
HVACMode.COOL,
|
||||
HVACMode.HEAT,
|
||||
HVACMode.FAN_ONLY,
|
||||
HVACMode.DRY,
|
||||
]
|
||||
|
||||
_attr_supported_features = ClimateEntityFeature.FAN_MODE
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
|
||||
"""Initialize an AdvantageAir AC unit."""
|
||||
super().__init__(instance, ac_key)
|
||||
|
||||
self._attr_supported_features = ClimateEntityFeature.FAN_MODE
|
||||
self._attr_hvac_modes = [
|
||||
HVACMode.OFF,
|
||||
HVACMode.COOL,
|
||||
HVACMode.HEAT,
|
||||
HVACMode.FAN_ONLY,
|
||||
HVACMode.DRY,
|
||||
]
|
||||
# Set supported features and HVAC modes based on current operating mode
|
||||
if self._ac.get(ADVANTAGE_AIR_MYAUTO_ENABLED):
|
||||
# MyAuto
|
||||
@ -118,10 +108,6 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
# MyZone
|
||||
self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
|
||||
# Add "ezfan" mode if supported
|
||||
if self._ac.get(ADVANTAGE_AIR_AUTOFAN):
|
||||
self._attr_fan_modes += [FAN_AUTO]
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Return the selected zones current temperature."""
|
||||
@ -151,7 +137,7 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
@property
|
||||
def fan_mode(self) -> str | None:
|
||||
"""Return the current fan modes."""
|
||||
return ADVANTAGE_AIR_FAN_MODES.get(self._ac["fan"])
|
||||
return FAN_AUTO if self._ac["fan"] == ADVANTAGE_AIR_MYFAN else self._ac["fan"]
|
||||
|
||||
@property
|
||||
def target_temperature_high(self) -> float | None:
|
||||
@ -189,7 +175,11 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set the Fan Mode."""
|
||||
await self.async_update_ac({"fan": HASS_FAN_MODES.get(fan_mode)})
|
||||
if fan_mode == FAN_AUTO and self._ac.get(ADVANTAGE_AIR_AUTOFAN_ENABLED):
|
||||
mode = ADVANTAGE_AIR_MYFAN
|
||||
else:
|
||||
mode = fan_mode
|
||||
await self.async_update_ac({"fan": mode})
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set the Temperature."""
|
||||
|
@ -5,3 +5,4 @@ ADVANTAGE_AIR_STATE_OPEN = "open"
|
||||
ADVANTAGE_AIR_STATE_CLOSE = "close"
|
||||
ADVANTAGE_AIR_STATE_ON = "on"
|
||||
ADVANTAGE_AIR_STATE_OFF = "off"
|
||||
ADVANTAGE_AIR_AUTOFAN_ENABLED = "aaAutoFanModeEnabled"
|
||||
|
@ -7,6 +7,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
ADVANTAGE_AIR_AUTOFAN_ENABLED,
|
||||
ADVANTAGE_AIR_STATE_OFF,
|
||||
ADVANTAGE_AIR_STATE_ON,
|
||||
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
||||
@ -29,6 +30,8 @@ async def async_setup_entry(
|
||||
for ac_key, ac_device in aircons.items():
|
||||
if ac_device["info"]["freshAirStatus"] != "none":
|
||||
entities.append(AdvantageAirFreshAir(instance, ac_key))
|
||||
if ADVANTAGE_AIR_AUTOFAN_ENABLED in ac_device["info"]:
|
||||
entities.append(AdvantageAirMyFan(instance, ac_key))
|
||||
if things := instance.coordinator.data.get("myThings"):
|
||||
for thing in things["things"].values():
|
||||
if thing["channelDipState"] == 8: # 8 = Other relay
|
||||
@ -62,6 +65,32 @@ class AdvantageAirFreshAir(AdvantageAirAcEntity, SwitchEntity):
|
||||
await self.async_update_ac({"freshAirStatus": ADVANTAGE_AIR_STATE_OFF})
|
||||
|
||||
|
||||
class AdvantageAirMyFan(AdvantageAirAcEntity, SwitchEntity):
|
||||
"""Representation of Advantage Air MyFan control."""
|
||||
|
||||
_attr_icon = "mdi:fan-auto"
|
||||
_attr_name = "MyFan"
|
||||
_attr_device_class = SwitchDeviceClass.SWITCH
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
|
||||
"""Initialize an Advantage Air MyFan control."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_unique_id += "-myfan"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the MyFan status."""
|
||||
return self._ac[ADVANTAGE_AIR_AUTOFAN_ENABLED]
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn MyFan on."""
|
||||
await self.async_update_ac({ADVANTAGE_AIR_AUTOFAN_ENABLED: True})
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn MyFan off."""
|
||||
await self.async_update_ac({ADVANTAGE_AIR_AUTOFAN_ENABLED: False})
|
||||
|
||||
|
||||
class AdvantageAirRelay(AdvantageAirThingEntity, SwitchEntity):
|
||||
"""Representation of Advantage Air Thing."""
|
||||
|
||||
|
55
tests/components/advantage_air/snapshots/test_climate.ambr
Normal file
55
tests/components/advantage_air/snapshots/test_climate.ambr
Normal file
@ -0,0 +1,55 @@
|
||||
# serializer version: 1
|
||||
# name: test_climate_myauto_main[climate.myauto-fanmode]
|
||||
dict({
|
||||
'ac3': dict({
|
||||
'info': dict({
|
||||
'fan': 'autoAA',
|
||||
}),
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_climate_myauto_main[climate.myauto-settemp]
|
||||
dict({
|
||||
'ac3': dict({
|
||||
'info': dict({
|
||||
'myAutoCoolTargetTemp': 23.0,
|
||||
'myAutoHeatTargetTemp': 21.0,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_climate_myauto_main[climate.myauto]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': None,
|
||||
'fan_mode': 'auto',
|
||||
'fan_modes': list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto',
|
||||
]),
|
||||
'friendly_name': 'myauto',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.DRY: 'dry'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_temp': 32,
|
||||
'min_temp': 16,
|
||||
'supported_features': <ClimateEntityFeature: 11>,
|
||||
'target_temp_high': 24,
|
||||
'target_temp_low': 20,
|
||||
'target_temp_step': 1,
|
||||
'temperature': 24,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.myauto',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'heat_cool',
|
||||
})
|
||||
# ---
|
33
tests/components/advantage_air/snapshots/test_switch.ambr
Normal file
33
tests/components/advantage_air/snapshots/test_switch.ambr
Normal file
@ -0,0 +1,33 @@
|
||||
# serializer version: 1
|
||||
# name: test_cover_async_setup_entry[switch.myzone_myfan-turnoff]
|
||||
dict({
|
||||
'ac1': dict({
|
||||
'info': dict({
|
||||
'aaAutoFanModeEnabled': False,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_async_setup_entry[switch.myzone_myfan-turnon]
|
||||
dict({
|
||||
'ac1': dict({
|
||||
'info': dict({
|
||||
'aaAutoFanModeEnabled': True,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_async_setup_entry[switch.myzone_myfan]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'switch',
|
||||
'friendly_name': 'myzone MyFan',
|
||||
'icon': 'mdi:fan-auto',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.myzone_myfan',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
@ -4,6 +4,7 @@ from unittest.mock import AsyncMock
|
||||
|
||||
from advantage_air import ApiError
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_CURRENT_TEMPERATURE,
|
||||
@ -14,6 +15,7 @@ from homeassistant.components.climate import (
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
FAN_AUTO,
|
||||
FAN_LOW,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
@ -27,7 +29,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import add_mock_config, patch_update
|
||||
from . import add_mock_config
|
||||
|
||||
|
||||
async def test_climate_myzone_main(
|
||||
@ -182,6 +184,7 @@ async def test_climate_myauto_main(
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_get: AsyncMock,
|
||||
mock_update: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test climate platform zone entity."""
|
||||
|
||||
@ -189,27 +192,35 @@ async def test_climate_myauto_main(
|
||||
|
||||
# Test MyAuto Climate Entity
|
||||
entity_id = "climate.myauto"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 20
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 24
|
||||
assert hass.states.get(entity_id) == snapshot(name=entity_id)
|
||||
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.unique_id == "uniqueid-ac3"
|
||||
|
||||
with patch_update() as mock_update:
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
ATTR_ENTITY_ID: [entity_id],
|
||||
ATTR_TARGET_TEMP_LOW: 21,
|
||||
ATTR_TARGET_TEMP_HIGH: 23,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_update.assert_called_once()
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
ATTR_ENTITY_ID: [entity_id],
|
||||
ATTR_TARGET_TEMP_LOW: 21,
|
||||
ATTR_TARGET_TEMP_HIGH: 23,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_update.assert_called_once()
|
||||
assert mock_update.call_args[0][0] == snapshot(name=f"{entity_id}-settemp")
|
||||
mock_update.reset_mock()
|
||||
|
||||
# Test AutoFanMode
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
{ATTR_ENTITY_ID: [entity_id], ATTR_FAN_MODE: FAN_AUTO},
|
||||
blocking=True,
|
||||
)
|
||||
mock_update.assert_called_once()
|
||||
assert mock_update.call_args[0][0] == snapshot(name=f"{entity_id}-fanmode")
|
||||
|
||||
|
||||
async def test_climate_async_failed_update(
|
||||
|
@ -1,8 +1,9 @@
|
||||
"""Test the Advantage Air Switch Platform."""
|
||||
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@ -20,12 +21,15 @@ async def test_cover_async_setup_entry(
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_get: AsyncMock,
|
||||
mock_update: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test switch platform."""
|
||||
|
||||
await add_mock_config(hass)
|
||||
|
||||
# Test Switch Entity
|
||||
registry = er.async_get(hass)
|
||||
|
||||
# Test Fresh Air Switch Entity
|
||||
entity_id = "switch.myzone_fresh_air"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
@ -51,6 +55,34 @@ async def test_cover_async_setup_entry(
|
||||
blocking=True,
|
||||
)
|
||||
mock_update.assert_called_once()
|
||||
mock_update.reset_mock()
|
||||
|
||||
# Test MyFan Switch Entity
|
||||
entity_id = "switch.myzone_myfan"
|
||||
assert hass.states.get(entity_id) == snapshot(name=entity_id)
|
||||
|
||||
entry = registry.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.unique_id == "uniqueid-ac1-myfan"
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
mock_update.assert_called_once()
|
||||
assert mock_update.call_args[0][0] == snapshot(name=f"{entity_id}-turnon")
|
||||
mock_update.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
mock_update.assert_called_once()
|
||||
assert mock_update.call_args[0][0] == snapshot(name=f"{entity_id}-turnoff")
|
||||
|
||||
|
||||
async def test_things_switch(
|
||||
|
Loading…
x
Reference in New Issue
Block a user