core/tests/components/matter/test_fan.py

444 lines
14 KiB
Python

"""Test Matter Fan platform."""
from unittest.mock import MagicMock, call
from matter_server.client.models.node import MatterNode
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.fan import (
ATTR_DIRECTION,
ATTR_OSCILLATING,
ATTR_PERCENTAGE,
ATTR_PRESET_MODE,
DIRECTION_FORWARD,
DIRECTION_REVERSE,
DOMAIN as FAN_DOMAIN,
SERVICE_OSCILLATE,
SERVICE_SET_DIRECTION,
FanEntityFeature,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .common import (
set_node_attribute,
snapshot_matter_entities,
trigger_subscription_callback,
)
@pytest.mark.usefixtures("matter_devices")
async def test_fans(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test fans."""
snapshot_matter_entities(hass, entity_registry, snapshot, Platform.FAN)
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
async def test_fan_base(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test Fan platform."""
entity_id = "fan.air_purifier"
state = hass.states.get(entity_id)
assert state
assert state.attributes["preset_modes"] == [
"low",
"medium",
"high",
"auto",
"natural_wind",
"sleep_wind",
]
assert state.attributes["direction"] == "forward"
assert state.attributes["oscillating"] is False
assert state.attributes["percentage"] is None
assert state.attributes["percentage_step"] == 10
assert state.attributes["preset_mode"] == "auto"
mask = (
FanEntityFeature.DIRECTION
| FanEntityFeature.OSCILLATE
| FanEntityFeature.PRESET_MODE
| FanEntityFeature.SET_SPEED
)
assert state.attributes["supported_features"] & mask == mask
# handle fan mode update
set_node_attribute(matter_node, 1, 514, 0, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes["preset_mode"] == "low"
# handle direction update
set_node_attribute(matter_node, 1, 514, 11, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes["direction"] == "reverse"
# handle rock/oscillation update
set_node_attribute(matter_node, 1, 514, 8, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes["oscillating"] is True
# handle wind mode active translates to correct preset
set_node_attribute(matter_node, 1, 514, 10, 2)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes["preset_mode"] == "natural_wind"
set_node_attribute(matter_node, 1, 514, 10, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes["preset_mode"] == "sleep_wind"
# set mains power to OFF (OnOff cluster)
set_node_attribute(matter_node, 1, 6, 0, False)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes["preset_mode"] is None
assert state.attributes["percentage"] == 0
# test featuremap update
set_node_attribute(matter_node, 1, 514, 65532, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes["supported_features"] & FanEntityFeature.SET_SPEED
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
async def test_fan_turn_on_with_percentage(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test turning on the fan with a specific percentage."""
entity_id = "fan.air_purifier"
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id, ATTR_PERCENTAGE: 50},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/2",
value=50,
)
# test again where preset_mode is omitted in the service call
# which should select the last active percentage
matter_client.write_attribute.reset_mock()
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/2",
value=255,
)
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("node_fixture", ["fan"])
async def test_fan_turn_on_with_preset_mode(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test turning on the fan with a specific preset mode."""
entity_id = "fan.mocked_fan_switch"
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "medium"},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/0",
value=2,
)
# test again with wind feature as preset mode
for preset_mode, value in (("natural_wind", 2), ("sleep_wind", 1)):
matter_client.write_attribute.reset_mock()
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset_mode},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/10",
value=value,
)
# test again if wind mode is explicitly turned off when we set a new preset mode
matter_client.write_attribute.reset_mock()
set_node_attribute(matter_node, 1, 514, 10, 2)
await trigger_subscription_callback(hass, matter_client)
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "medium"},
blocking=True,
)
assert matter_client.write_attribute.call_count == 2
assert matter_client.write_attribute.call_args_list[0] == call(
node_id=matter_node.node_id,
attribute_path="1/514/10",
value=0,
)
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/0",
value=2,
)
# test again where preset_mode is omitted in the service call
# which should select the last active preset
matter_client.write_attribute.reset_mock()
set_node_attribute(matter_node, 1, 514, 0, 1)
set_node_attribute(matter_node, 1, 514, 10, 0)
await trigger_subscription_callback(hass, matter_client)
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/0",
value=1,
)
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
async def test_fan_turn_off(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test turning off the fan."""
entity_id = "fan.air_purifier"
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/0",
value=0,
)
matter_client.write_attribute.reset_mock()
# test again if wind mode is turned off
set_node_attribute(matter_node, 1, 514, 10, 2)
await trigger_subscription_callback(hass, matter_client)
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert matter_client.write_attribute.call_count == 2
assert matter_client.write_attribute.call_args_list[0] == call(
node_id=matter_node.node_id,
attribute_path="1/514/10",
value=0,
)
assert matter_client.write_attribute.call_args_list[1] == call(
node_id=matter_node.node_id,
attribute_path="1/514/0",
value=0,
)
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
async def test_fan_oscillate(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test oscillating the fan."""
entity_id = "fan.air_purifier"
for oscillating, value in ((True, 1), (False, 0)):
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_OSCILLATE,
{ATTR_ENTITY_ID: entity_id, ATTR_OSCILLATING: oscillating},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/8",
value=value,
)
matter_client.write_attribute.reset_mock()
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
async def test_fan_set_direction(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test oscillating the fan."""
entity_id = "fan.air_purifier"
for direction, value in ((DIRECTION_FORWARD, 0), (DIRECTION_REVERSE, 1)):
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_SET_DIRECTION,
{ATTR_ENTITY_ID: entity_id, ATTR_DIRECTION: direction},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path="1/514/11",
value=value,
)
matter_client.write_attribute.reset_mock()
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize(
("node_fixture", "entity_id", "attributes", "features"),
[
(
"fan",
"fan.mocked_fan_switch",
{
"1/514/65532": 0,
},
(FanEntityFeature.TURN_ON | FanEntityFeature.TURN_OFF),
),
(
"fan",
"fan.mocked_fan_switch",
{
"1/514/65532": 1,
},
(
FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
| FanEntityFeature.SET_SPEED
),
),
(
"fan",
"fan.mocked_fan_switch",
{
"1/514/65532": 4,
},
(
FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
| FanEntityFeature.OSCILLATE
),
),
(
"fan",
"fan.mocked_fan_switch",
{
"1/514/65532": 36,
},
(
FanEntityFeature.TURN_ON
| FanEntityFeature.TURN_OFF
| FanEntityFeature.OSCILLATE
| FanEntityFeature.DIRECTION
),
),
],
)
async def test_fan_supported_features(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
entity_id: str,
features: int,
) -> None:
"""Test if the correct features get discovered from featuremap."""
state = hass.states.get(entity_id)
assert state
assert state.attributes["supported_features"] & features == features
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize(
("node_fixture", "entity_id", "attributes", "preset_modes"),
[
(
"fan",
"fan.mocked_fan_switch",
{"1/514/1": 0, "1/514/65532": 0},
[
"low",
"medium",
"high",
],
),
(
"fan",
"fan.mocked_fan_switch",
{"1/514/1": 1, "1/514/65532": 0},
[
"low",
"high",
],
),
(
"fan",
"fan.mocked_fan_switch",
{"1/514/1": 2, "1/514/65532": 0},
["low", "medium", "high", "auto"],
),
(
"fan",
"fan.mocked_fan_switch",
{"1/514/1": 4, "1/514/65532": 0},
["high", "auto"],
),
(
"fan",
"fan.mocked_fan_switch",
{"1/514/1": 5, "1/514/65532": 0},
["high"],
),
(
"fan",
"fan.mocked_fan_switch",
{"1/514/1": 5, "1/514/65532": 8, "1/514/9": 3},
["high", "natural_wind", "sleep_wind"],
),
],
)
async def test_fan_features(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
entity_id: str,
preset_modes: list[str],
) -> None:
"""Test if the correct presets get discovered from fanmodesequence."""
state = hass.states.get(entity_id)
assert state
assert state.attributes["preset_modes"] == preset_modes