mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 07:51:46 -05:00
Add swing mode support to LG Infrared AC climate (#177191)
This commit is contained in:
@@ -2,21 +2,26 @@
|
||||
|
||||
from typing import Any, override
|
||||
|
||||
from infrared_protocols.codes.lg.ac import LGACCode
|
||||
from infrared_protocols.commands.lg_ac import (
|
||||
MAX_TEMP,
|
||||
MIN_TEMP,
|
||||
LgAcCommand,
|
||||
LgAcFanSpeed,
|
||||
LgAcFixedCommand,
|
||||
LgAcMode,
|
||||
)
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_FAN_MODE,
|
||||
ATTR_HVAC_MODE,
|
||||
ATTR_SWING_HORIZONTAL_MODE,
|
||||
ATTR_SWING_MODE,
|
||||
FAN_AUTO,
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
SWING_OFF,
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
@@ -75,6 +80,51 @@ _LIB_MODE_TO_HA: dict[LgAcMode, HVACMode] = {v: k for k, v in _HA_MODE_TO_LIB.it
|
||||
# Only these modes carry a temperature in the LG AC protocol frame.
|
||||
_TEMPERATURE_MODES = (LgAcMode.COOL, LgAcMode.HEAT)
|
||||
|
||||
SWING_LOWEST = "lowest"
|
||||
SWING_LOW = "low"
|
||||
SWING_MIDDLE_LOW = "middle_low"
|
||||
SWING_MIDDLE_HIGH = "middle_high"
|
||||
SWING_HIGH = "high"
|
||||
SWING_HIGHEST = "highest"
|
||||
SWING_OSCILLATE = "swing"
|
||||
|
||||
SWING_LEFT = "left"
|
||||
SWING_MIDDLE_LEFT = "middle_left"
|
||||
SWING_MIDDLE = "middle"
|
||||
SWING_MIDDLE_RIGHT = "middle_right"
|
||||
SWING_RIGHT = "right"
|
||||
|
||||
# Oscillates in one half
|
||||
SWING_LEFT_HALF = "left_half"
|
||||
SWING_RIGHT_HALF = "right_half"
|
||||
|
||||
# The six vane positions plus off and the oscillating "swing" mode share the vertical
|
||||
# swing dropdown. There is no true centre position on this ladder.
|
||||
_HA_SWING_TO_LIB: dict[str, LGACCode] = {
|
||||
SWING_OFF: LGACCode.SWING_V_OFF,
|
||||
SWING_HIGHEST: LGACCode.SWING_V_HIGHEST,
|
||||
SWING_HIGH: LGACCode.SWING_V_HIGH,
|
||||
SWING_MIDDLE_HIGH: LGACCode.SWING_V_MIDDLE_HIGH,
|
||||
SWING_MIDDLE_LOW: LGACCode.SWING_V_MIDDLE_LOW,
|
||||
SWING_LOW: LGACCode.SWING_V_LOW,
|
||||
SWING_LOWEST: LGACCode.SWING_V_LOWEST,
|
||||
SWING_OSCILLATE: LGACCode.SWING_V_SWING,
|
||||
}
|
||||
_LIB_SWING_TO_HA: dict[LGACCode, str] = {v: k for k, v in _HA_SWING_TO_LIB.items()}
|
||||
|
||||
_HA_SWING_H_TO_LIB: dict[str, LGACCode] = {
|
||||
SWING_OFF: LGACCode.SWING_H_OFF,
|
||||
SWING_LEFT: LGACCode.SWING_H_LEFT,
|
||||
SWING_MIDDLE_LEFT: LGACCode.SWING_H_MIDDLE_LEFT,
|
||||
SWING_MIDDLE: LGACCode.SWING_H_MIDDLE,
|
||||
SWING_MIDDLE_RIGHT: LGACCode.SWING_H_MIDDLE_RIGHT,
|
||||
SWING_RIGHT: LGACCode.SWING_H_RIGHT,
|
||||
SWING_LEFT_HALF: LGACCode.SWING_H_MIDDLE_TO_LEFT,
|
||||
SWING_RIGHT_HALF: LGACCode.SWING_H_MIDDLE_TO_RIGHT,
|
||||
SWING_OSCILLATE: LGACCode.SWING_H_SWING,
|
||||
}
|
||||
_LIB_SWING_H_TO_HA: dict[LGACCode, str] = {v: k for k, v in _HA_SWING_H_TO_LIB.items()}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -116,6 +166,8 @@ class LgAcClimateEntity(
|
||||
FAN_MEDIUM_HIGH,
|
||||
FAN_HIGH,
|
||||
]
|
||||
_attr_swing_modes = list(_HA_SWING_TO_LIB)
|
||||
_attr_swing_horizontal_modes = list(_HA_SWING_H_TO_LIB)
|
||||
|
||||
def __init__(self, entry: ConfigEntry, emitter_entity_id: str) -> None:
|
||||
"""Initialize LG AC climate entity."""
|
||||
@@ -129,8 +181,14 @@ class LgAcClimateEntity(
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_target_temperature = float(MIN_TEMP)
|
||||
self._attr_fan_mode = FAN_AUTO
|
||||
self._attr_swing_mode = SWING_OFF
|
||||
self._attr_swing_horizontal_mode = SWING_OFF
|
||||
|
||||
self._attr_supported_features = ClimateEntityFeature.FAN_MODE
|
||||
self._attr_supported_features = (
|
||||
ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.SWING_MODE
|
||||
| ClimateEntityFeature.SWING_HORIZONTAL_MODE
|
||||
)
|
||||
# Without a temperature-carrying mode no target temperature can ever be sent.
|
||||
if any(
|
||||
_HA_MODE_TO_LIB[mode] in _TEMPERATURE_MODES
|
||||
@@ -156,6 +214,12 @@ class LgAcClimateEntity(
|
||||
self._attr_fan_mode = fan_mode
|
||||
if (temperature := last_state.attributes.get(ATTR_TEMPERATURE)) is not None:
|
||||
self._attr_target_temperature = float(temperature)
|
||||
if (swing := last_state.attributes.get(ATTR_SWING_MODE)) in _HA_SWING_TO_LIB:
|
||||
self._attr_swing_mode = swing
|
||||
if (
|
||||
swing_h := last_state.attributes.get(ATTR_SWING_HORIZONTAL_MODE)
|
||||
) in _HA_SWING_H_TO_LIB:
|
||||
self._attr_swing_horizontal_mode = swing_h
|
||||
|
||||
@override
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
@@ -202,6 +266,24 @@ class LgAcClimateEntity(
|
||||
self._attr_fan_mode = fan_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
@override
|
||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||
"""Set the vertical swing mode.
|
||||
|
||||
Each vane position is a self-contained fixed code, so it is sent directly
|
||||
rather than folded into the current state frame.
|
||||
"""
|
||||
await self._send_command(_HA_SWING_TO_LIB[swing_mode].to_command())
|
||||
self._attr_swing_mode = swing_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
@override
|
||||
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
|
||||
"""Set the horizontal swing mode."""
|
||||
await self._send_command(_HA_SWING_H_TO_LIB[swing_horizontal_mode].to_command())
|
||||
self._attr_swing_horizontal_mode = swing_horizontal_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _build_command(self, mode: LgAcMode, temp: int, fan_mode: str) -> LgAcCommand:
|
||||
"""Build a command from a mode, a temperature and a fan mode.
|
||||
|
||||
@@ -227,6 +309,7 @@ class LgAcClimateWithReceiver(LgAcClimateEntity, InfraredReceiverConsumerEntity)
|
||||
"""Update state from a physical remote signal."""
|
||||
command = LgAcCommand.from_raw_timings(signal.timings)
|
||||
if command is None:
|
||||
self._handle_fixed_signal(signal)
|
||||
return
|
||||
|
||||
hvac_mode = _LIB_MODE_TO_HA[command.mode]
|
||||
@@ -241,3 +324,21 @@ class LgAcClimateWithReceiver(LgAcClimateEntity, InfraredReceiverConsumerEntity)
|
||||
self._attr_target_temperature = float(command.temperature)
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _handle_fixed_signal(self, signal: InfraredReceivedSignal) -> None:
|
||||
"""Update the swing state from a fixed-code frame (e.g. a vane button)."""
|
||||
command = LgAcFixedCommand.from_raw_timings(signal.timings)
|
||||
if command is None:
|
||||
return
|
||||
try:
|
||||
code = LGACCode(command.code)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
if (swing := _LIB_SWING_TO_HA.get(code)) is not None:
|
||||
self._attr_swing_mode = swing
|
||||
self.async_write_ha_state()
|
||||
elif (swing_h := _LIB_SWING_H_TO_HA.get(code)) is not None:
|
||||
self._attr_swing_horizontal_mode = swing_h
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -88,6 +88,39 @@
|
||||
"up": {
|
||||
"default": "mdi:arrow-up"
|
||||
}
|
||||
},
|
||||
"climate": {
|
||||
"lg_ac": {
|
||||
"state_attributes": {
|
||||
"swing_horizontal_mode": {
|
||||
"default": "mdi:circle-medium",
|
||||
"state": {
|
||||
"left": "mdi:chevron-double-left",
|
||||
"left_half": "mdi:arrow-left",
|
||||
"middle": "mdi:circle-small",
|
||||
"middle_left": "mdi:chevron-left",
|
||||
"middle_right": "mdi:chevron-right",
|
||||
"off": "mdi:arrow-oscillating-off",
|
||||
"right": "mdi:chevron-double-right",
|
||||
"right_half": "mdi:arrow-right",
|
||||
"swing": "mdi:arrow-expand-horizontal"
|
||||
}
|
||||
},
|
||||
"swing_mode": {
|
||||
"default": "mdi:circle-medium",
|
||||
"state": {
|
||||
"high": "mdi:chevron-up",
|
||||
"highest": "mdi:chevron-double-up",
|
||||
"low": "mdi:chevron-down",
|
||||
"lowest": "mdi:chevron-double-down",
|
||||
"middle_high": "mdi:arrow-up-thin",
|
||||
"middle_low": "mdi:arrow-down-thin",
|
||||
"off": "mdi:arrow-oscillating-off",
|
||||
"swing": "mdi:arrow-oscillating"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,10 +93,7 @@ rules:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not raise exceptions.
|
||||
icon-translations:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not use custom icons.
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
status: exempt
|
||||
|
||||
@@ -144,6 +144,31 @@
|
||||
"medium_low": "Medium low",
|
||||
"quiet": "Quiet"
|
||||
}
|
||||
},
|
||||
"swing_horizontal_mode": {
|
||||
"state": {
|
||||
"left": "Left",
|
||||
"left_half": "Left half",
|
||||
"middle": "Middle",
|
||||
"middle_left": "Middle left",
|
||||
"middle_right": "Middle right",
|
||||
"off": "Off",
|
||||
"right": "Right",
|
||||
"right_half": "Right half",
|
||||
"swing": "Swing"
|
||||
}
|
||||
},
|
||||
"swing_mode": {
|
||||
"state": {
|
||||
"high": "High",
|
||||
"highest": "Highest",
|
||||
"low": "Low",
|
||||
"lowest": "Lowest",
|
||||
"middle_high": "Middle high",
|
||||
"middle_low": "Middle low",
|
||||
"off": "Off",
|
||||
"swing": "Swing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,27 @@
|
||||
]),
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16.0,
|
||||
<ClimateEntityCapabilityAttribute.SWING_HORIZONTAL_MODES: 'swing_horizontal_modes'>: list([
|
||||
'off',
|
||||
'left',
|
||||
'middle_left',
|
||||
'middle',
|
||||
'middle_right',
|
||||
'right',
|
||||
'left_half',
|
||||
'right_half',
|
||||
'swing',
|
||||
]),
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'highest',
|
||||
'high',
|
||||
'middle_high',
|
||||
'middle_low',
|
||||
'low',
|
||||
'lowest',
|
||||
'swing',
|
||||
]),
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -48,7 +69,7 @@
|
||||
'platform': 'lg_infrared',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 9>,
|
||||
'supported_features': <ClimateEntityFeature: 553>,
|
||||
'translation_key': 'lg_ac',
|
||||
'unique_id': '01JTEST0000000000000000000_climate',
|
||||
'unit_of_measurement': None,
|
||||
@@ -77,7 +98,30 @@
|
||||
]),
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16.0,
|
||||
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <ClimateEntityFeature: 9>,
|
||||
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <ClimateEntityFeature: 553>,
|
||||
<ClimateEntityStateAttribute.SWING_HORIZONTAL_MODE: 'swing_horizontal_mode'>: 'off',
|
||||
<ClimateEntityCapabilityAttribute.SWING_HORIZONTAL_MODES: 'swing_horizontal_modes'>: list([
|
||||
'off',
|
||||
'left',
|
||||
'middle_left',
|
||||
'middle',
|
||||
'middle_right',
|
||||
'right',
|
||||
'left_half',
|
||||
'right_half',
|
||||
'swing',
|
||||
]),
|
||||
<ClimateEntityStateAttribute.SWING_MODE: 'swing_mode'>: 'off',
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'highest',
|
||||
'high',
|
||||
'middle_high',
|
||||
'middle_low',
|
||||
'low',
|
||||
'lowest',
|
||||
'swing',
|
||||
]),
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1.0,
|
||||
<ClimateEntityStateAttribute.TARGET_TEMPERATURE: 'temperature'>: 16.0,
|
||||
}),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from infrared_protocols.codes.lg.ac import LGACCode
|
||||
from infrared_protocols.commands.lg_ac import (
|
||||
MIN_TEMP,
|
||||
LgAcCommand,
|
||||
@@ -13,6 +14,8 @@ import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_SWING_HORIZONTAL_MODE,
|
||||
ATTR_SWING_MODE,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
FAN_AUTO,
|
||||
FAN_HIGH,
|
||||
@@ -20,6 +23,8 @@ from homeassistant.components.climate import (
|
||||
FAN_MEDIUM,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
SERVICE_SET_SWING_HORIZONTAL_MODE,
|
||||
SERVICE_SET_SWING_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
@@ -441,12 +446,17 @@ async def test_receiver_ignores_non_lg_ac_signal(
|
||||
[
|
||||
pytest.param(
|
||||
[HVACMode.COOL, HVACMode.DRY],
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.SWING_MODE
|
||||
| ClimateEntityFeature.SWING_HORIZONTAL_MODE,
|
||||
id="with_temperature_mode",
|
||||
),
|
||||
pytest.param(
|
||||
[HVACMode.DRY, HVACMode.FAN_ONLY],
|
||||
ClimateEntityFeature.FAN_MODE,
|
||||
ClimateEntityFeature.FAN_MODE
|
||||
| ClimateEntityFeature.SWING_MODE
|
||||
| ClimateEntityFeature.SWING_HORIZONTAL_MODE,
|
||||
id="without_temperature_mode",
|
||||
),
|
||||
],
|
||||
@@ -467,20 +477,25 @@ async def test_target_temperature_feature_follows_configured_modes(
|
||||
[
|
||||
pytest.param(
|
||||
HVACMode.COOL,
|
||||
{"fan_mode": FAN_HIGH, "temperature": 29.0},
|
||||
(HVACMode.COOL, FAN_HIGH, 29.0),
|
||||
{
|
||||
"fan_mode": FAN_HIGH,
|
||||
"temperature": 29.0,
|
||||
ATTR_SWING_MODE: "high",
|
||||
ATTR_SWING_HORIZONTAL_MODE: "left",
|
||||
},
|
||||
(HVACMode.COOL, FAN_HIGH, 29.0, "high", "left"),
|
||||
id="full_state",
|
||||
),
|
||||
pytest.param(
|
||||
STATE_UNAVAILABLE,
|
||||
{},
|
||||
(HVACMode.OFF, FAN_AUTO, float(MIN_TEMP)),
|
||||
(HVACMode.OFF, FAN_AUTO, float(MIN_TEMP), "off", "off"),
|
||||
id="unavailable_falls_back_to_defaults",
|
||||
),
|
||||
pytest.param(
|
||||
HVACMode.HEAT,
|
||||
{"fan_mode": FAN_HIGH, "temperature": 29.0},
|
||||
(HVACMode.OFF, FAN_HIGH, 29.0),
|
||||
(HVACMode.OFF, FAN_HIGH, 29.0, "off", "off"),
|
||||
id="mode_no_longer_configured_is_ignored",
|
||||
),
|
||||
],
|
||||
@@ -492,7 +507,7 @@ async def test_state_restored_on_restart(
|
||||
platforms: list[Platform],
|
||||
restored_state: str,
|
||||
restored_attributes: dict[str, Any],
|
||||
expected: tuple[HVACMode, str, float],
|
||||
expected: tuple[HVACMode, str, float, str, str],
|
||||
) -> None:
|
||||
"""Test the assumed state is restored, since infrared cannot read it back."""
|
||||
mock_restore_cache(
|
||||
@@ -506,10 +521,14 @@ async def test_state_restored_on_restart(
|
||||
|
||||
state = hass.states.get(_CLIMATE_ENTITY_ID)
|
||||
assert state is not None
|
||||
expected_mode, expected_fan, expected_temp = expected
|
||||
expected_mode, expected_fan, expected_temp, expected_swing, expected_swing_h = (
|
||||
expected
|
||||
)
|
||||
assert state.state == expected_mode
|
||||
assert state.attributes["fan_mode"] == expected_fan
|
||||
assert state.attributes["temperature"] == expected_temp
|
||||
assert state.attributes[ATTR_SWING_MODE] == expected_swing
|
||||
assert state.attributes[ATTR_SWING_HORIZONTAL_MODE] == expected_swing_h
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@@ -563,3 +582,112 @@ async def test_set_temperature_with_unsupported_hvac_mode(
|
||||
)
|
||||
|
||||
assert len(mock_infrared_emitter_entity.send_command_calls) == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize(
|
||||
("swing_mode", "expected_button"),
|
||||
[
|
||||
pytest.param("off", LGACCode.SWING_V_OFF, id="off"),
|
||||
pytest.param("lowest", LGACCode.SWING_V_LOWEST, id="lowest"),
|
||||
pytest.param("low", LGACCode.SWING_V_LOW, id="low"),
|
||||
pytest.param("middle_low", LGACCode.SWING_V_MIDDLE_LOW, id="middle_low"),
|
||||
pytest.param("middle_high", LGACCode.SWING_V_MIDDLE_HIGH, id="middle_high"),
|
||||
pytest.param("high", LGACCode.SWING_V_HIGH, id="high"),
|
||||
pytest.param("highest", LGACCode.SWING_V_HIGHEST, id="highest"),
|
||||
pytest.param("swing", LGACCode.SWING_V_SWING, id="swing"),
|
||||
],
|
||||
)
|
||||
async def test_set_swing_mode(
|
||||
hass: HomeAssistant,
|
||||
mock_infrared_emitter_entity: MockInfraredEmitterEntity,
|
||||
swing_mode: str,
|
||||
expected_button: LGACCode,
|
||||
) -> None:
|
||||
"""Test setting a vertical swing mode sends that vane's fixed code."""
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_SWING_MODE,
|
||||
{ATTR_ENTITY_ID: _CLIMATE_ENTITY_ID, ATTR_SWING_MODE: swing_mode},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_infrared_emitter_entity.send_command_calls) == 1
|
||||
timings = mock_infrared_emitter_entity.send_command_calls[0].get_raw_timings()
|
||||
assert timings == expected_button.to_command().get_raw_timings()
|
||||
|
||||
state = hass.states.get(_CLIMATE_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.attributes[ATTR_SWING_MODE] == swing_mode
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize(
|
||||
("swing_mode", "expected_button"),
|
||||
[
|
||||
pytest.param("off", LGACCode.SWING_H_OFF, id="off"),
|
||||
pytest.param("left", LGACCode.SWING_H_LEFT, id="left"),
|
||||
pytest.param("middle_left", LGACCode.SWING_H_MIDDLE_LEFT, id="middle_left"),
|
||||
pytest.param("middle", LGACCode.SWING_H_MIDDLE, id="middle"),
|
||||
pytest.param("middle_right", LGACCode.SWING_H_MIDDLE_RIGHT, id="middle_right"),
|
||||
pytest.param("right", LGACCode.SWING_H_RIGHT, id="right"),
|
||||
pytest.param("left_half", LGACCode.SWING_H_MIDDLE_TO_LEFT, id="left_half"),
|
||||
pytest.param("right_half", LGACCode.SWING_H_MIDDLE_TO_RIGHT, id="right_half"),
|
||||
pytest.param("swing", LGACCode.SWING_H_SWING, id="swing"),
|
||||
],
|
||||
)
|
||||
async def test_set_swing_horizontal_mode(
|
||||
hass: HomeAssistant,
|
||||
mock_infrared_emitter_entity: MockInfraredEmitterEntity,
|
||||
swing_mode: str,
|
||||
expected_button: LGACCode,
|
||||
) -> None:
|
||||
"""Test setting a horizontal swing mode sends that vane's fixed code."""
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_SWING_HORIZONTAL_MODE,
|
||||
{ATTR_ENTITY_ID: _CLIMATE_ENTITY_ID, ATTR_SWING_HORIZONTAL_MODE: swing_mode},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_infrared_emitter_entity.send_command_calls) == 1
|
||||
timings = mock_infrared_emitter_entity.send_command_calls[0].get_raw_timings()
|
||||
assert timings == expected_button.to_command().get_raw_timings()
|
||||
|
||||
state = hass.states.get(_CLIMATE_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.attributes[ATTR_SWING_HORIZONTAL_MODE] == swing_mode
|
||||
|
||||
|
||||
@pytest.mark.parametrize("has_receiver", [True])
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize(
|
||||
("button", "attribute", "expected"),
|
||||
[
|
||||
pytest.param(
|
||||
LGACCode.SWING_V_HIGH, ATTR_SWING_MODE, "high", id="vertical_position"
|
||||
),
|
||||
pytest.param(
|
||||
LGACCode.SWING_H_RIGHT,
|
||||
ATTR_SWING_HORIZONTAL_MODE,
|
||||
"right",
|
||||
id="horizontal_position",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_receiver_updates_swing_from_fixed_code(
|
||||
hass: HomeAssistant,
|
||||
mock_infrared_receiver_entity: MockInfraredReceiverEntity,
|
||||
button: LGACCode,
|
||||
attribute: str,
|
||||
expected: str,
|
||||
) -> None:
|
||||
"""Test a received vane fixed code updates the matching swing attribute."""
|
||||
mock_infrared_receiver_entity._handle_received_signal(
|
||||
InfraredReceivedSignal(timings=button.to_command().get_raw_timings())
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_CLIMATE_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.attributes[attribute] == expected
|
||||
|
||||
Reference in New Issue
Block a user