mirror of https://github.com/home-assistant/core
444 lines
14 KiB
Python
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
|