mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +00:00
Add Matter fan platform (#111212)
Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com>
This commit is contained in:
parent
7f934bafc2
commit
f44dfe8fef
@ -752,6 +752,7 @@ omit =
|
||||
homeassistant/components/matrix/__init__.py
|
||||
homeassistant/components/matrix/notify.py
|
||||
homeassistant/components/matter/__init__.py
|
||||
homeassistant/components/matter/fan.py
|
||||
homeassistant/components/meater/__init__.py
|
||||
homeassistant/components/meater/sensor.py
|
||||
homeassistant/components/medcom_ble/__init__.py
|
||||
|
@ -14,6 +14,7 @@ from .binary_sensor import DISCOVERY_SCHEMAS as BINARY_SENSOR_SCHEMAS
|
||||
from .climate import DISCOVERY_SCHEMAS as CLIMATE_SENSOR_SCHEMAS
|
||||
from .cover import DISCOVERY_SCHEMAS as COVER_SCHEMAS
|
||||
from .event import DISCOVERY_SCHEMAS as EVENT_SCHEMAS
|
||||
from .fan import DISCOVERY_SCHEMAS as FAN_SCHEMAS
|
||||
from .light import DISCOVERY_SCHEMAS as LIGHT_SCHEMAS
|
||||
from .lock import DISCOVERY_SCHEMAS as LOCK_SCHEMAS
|
||||
from .models import MatterDiscoverySchema, MatterEntityInfo
|
||||
@ -25,6 +26,7 @@ DISCOVERY_SCHEMAS: dict[Platform, list[MatterDiscoverySchema]] = {
|
||||
Platform.CLIMATE: CLIMATE_SENSOR_SCHEMAS,
|
||||
Platform.COVER: COVER_SCHEMAS,
|
||||
Platform.EVENT: EVENT_SCHEMAS,
|
||||
Platform.FAN: FAN_SCHEMAS,
|
||||
Platform.LIGHT: LIGHT_SCHEMAS,
|
||||
Platform.LOCK: LOCK_SCHEMAS,
|
||||
Platform.SENSOR: SENSOR_SCHEMAS,
|
||||
|
304
homeassistant/components/matter/fan.py
Normal file
304
homeassistant/components/matter/fan.py
Normal file
@ -0,0 +1,304 @@
|
||||
"""Matter Fan platform support."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from matter_server.common.helpers.util import create_attribute_path_from_attribute
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
DIRECTION_FORWARD,
|
||||
DIRECTION_REVERSE,
|
||||
FanEntity,
|
||||
FanEntityDescription,
|
||||
FanEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .entity import MatterEntity
|
||||
from .helpers import get_matter
|
||||
from .models import MatterDiscoverySchema
|
||||
|
||||
FanControlFeature = clusters.FanControl.Bitmaps.Feature
|
||||
WindBitmap = clusters.FanControl.Bitmaps.WindBitmap
|
||||
FanModeSequenceEnum = clusters.FanControl.Enums.FanModeSequenceEnum
|
||||
|
||||
PRESET_LOW = "low"
|
||||
PRESET_MEDIUM = "medium"
|
||||
PRESET_HIGH = "high"
|
||||
PRESET_AUTO = "auto"
|
||||
FAN_MODE_MAP = {
|
||||
PRESET_LOW: clusters.FanControl.Enums.FanModeEnum.kLow,
|
||||
PRESET_MEDIUM: clusters.FanControl.Enums.FanModeEnum.kMedium,
|
||||
PRESET_HIGH: clusters.FanControl.Enums.FanModeEnum.kHigh,
|
||||
PRESET_AUTO: clusters.FanControl.Enums.FanModeEnum.kAuto,
|
||||
}
|
||||
FAN_MODE_MAP_REVERSE = {v: k for k, v in FAN_MODE_MAP.items()}
|
||||
# special preset modes for wind feature
|
||||
PRESET_NATURAL_WIND = "natural_wind"
|
||||
PRESET_SLEEP_WIND = "sleep_wind"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Matter fan from Config Entry."""
|
||||
matter = get_matter(hass)
|
||||
matter.register_platform_handler(Platform.FAN, async_add_entities)
|
||||
|
||||
|
||||
class MatterFan(MatterEntity, FanEntity):
|
||||
"""Representation of a Matter fan."""
|
||||
|
||||
_last_known_preset_mode: str | None = None
|
||||
|
||||
async def async_turn_on(
|
||||
self,
|
||||
percentage: int | None = None,
|
||||
preset_mode: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if percentage is not None:
|
||||
# handle setting fan speed by percentage
|
||||
await self.async_set_percentage(percentage)
|
||||
return
|
||||
# handle setting fan mode by preset
|
||||
if preset_mode is None:
|
||||
# no preset given, try to handle this with the last known value
|
||||
preset_mode = self._last_known_preset_mode or PRESET_AUTO
|
||||
await self.async_set_preset_mode(preset_mode)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn fan off."""
|
||||
# clear the wind setting if its currently set
|
||||
if self._attr_preset_mode in [PRESET_NATURAL_WIND, PRESET_SLEEP_WIND]:
|
||||
await self._set_wind_mode(None)
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
clusters.FanControl.Attributes.FanMode,
|
||||
),
|
||||
value=clusters.FanControl.Enums.FanModeEnum.kOff,
|
||||
)
|
||||
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed of the fan, as a percentage."""
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
clusters.FanControl.Attributes.PercentSetting,
|
||||
),
|
||||
value=percentage,
|
||||
)
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
# handle wind as preset
|
||||
if preset_mode in [PRESET_NATURAL_WIND, PRESET_SLEEP_WIND]:
|
||||
await self._set_wind_mode(preset_mode)
|
||||
return
|
||||
|
||||
# clear the wind setting if its currently set
|
||||
if self._attr_preset_mode in [PRESET_NATURAL_WIND, PRESET_SLEEP_WIND]:
|
||||
await self._set_wind_mode(None)
|
||||
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
clusters.FanControl.Attributes.FanMode,
|
||||
),
|
||||
value=FAN_MODE_MAP[preset_mode],
|
||||
)
|
||||
|
||||
async def async_oscillate(self, oscillating: bool) -> None:
|
||||
"""Oscillate the fan."""
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
clusters.FanControl.Attributes.RockSetting,
|
||||
),
|
||||
value=self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.RockSupport
|
||||
)
|
||||
if oscillating
|
||||
else 0,
|
||||
)
|
||||
|
||||
async def async_set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
clusters.FanControl.Attributes.AirflowDirection,
|
||||
),
|
||||
value=clusters.FanControl.Enums.AirflowDirectionEnum.kReverse
|
||||
if direction == DIRECTION_REVERSE
|
||||
else clusters.FanControl.Enums.AirflowDirectionEnum.kForward,
|
||||
)
|
||||
|
||||
async def _set_wind_mode(self, wind_mode: str | None) -> None:
|
||||
"""Set wind mode."""
|
||||
if wind_mode == PRESET_NATURAL_WIND:
|
||||
wind_setting = WindBitmap.kNaturalWind
|
||||
elif wind_mode == PRESET_SLEEP_WIND:
|
||||
wind_setting = WindBitmap.kSleepWind
|
||||
else:
|
||||
wind_setting = 0
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
clusters.FanControl.Attributes.WindSetting,
|
||||
),
|
||||
value=wind_setting,
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
if not hasattr(self, "_attr_preset_modes"):
|
||||
self._calculate_features()
|
||||
if self._attr_supported_features & FanEntityFeature.DIRECTION:
|
||||
direction_value = self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.AirflowDirection
|
||||
)
|
||||
self._attr_current_direction = (
|
||||
DIRECTION_REVERSE
|
||||
if direction_value
|
||||
== clusters.FanControl.Enums.AirflowDirectionEnum.kReverse
|
||||
else DIRECTION_FORWARD
|
||||
)
|
||||
if self._attr_supported_features & FanEntityFeature.OSCILLATE:
|
||||
self._attr_oscillating = (
|
||||
self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.RockSetting
|
||||
)
|
||||
!= 0
|
||||
)
|
||||
|
||||
# speed percentage is always provided
|
||||
current_percent = self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.PercentCurrent
|
||||
)
|
||||
# NOTE that a device may give back 255 as a special value to indicate that
|
||||
# the speed is under automatic control and not set to a specific value.
|
||||
self._attr_percentage = None if current_percent == 255 else current_percent
|
||||
|
||||
# get preset mode from fan mode (and wind feature if available)
|
||||
wind_setting = self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.WindSetting
|
||||
)
|
||||
if (
|
||||
self._attr_preset_modes
|
||||
and PRESET_NATURAL_WIND in self._attr_preset_modes
|
||||
and wind_setting & WindBitmap.kNaturalWind
|
||||
):
|
||||
self._attr_preset_mode = PRESET_NATURAL_WIND
|
||||
elif (
|
||||
self._attr_preset_modes
|
||||
and PRESET_SLEEP_WIND in self._attr_preset_modes
|
||||
and wind_setting & WindBitmap.kSleepWind
|
||||
):
|
||||
self._attr_preset_mode = PRESET_SLEEP_WIND
|
||||
else:
|
||||
fan_mode = self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.FanMode
|
||||
)
|
||||
self._attr_preset_mode = FAN_MODE_MAP_REVERSE.get(fan_mode)
|
||||
|
||||
# keep track of the last known mode for turn_on commands without preset
|
||||
if self._attr_preset_mode is not None:
|
||||
self._last_known_preset_mode = self._attr_preset_mode
|
||||
|
||||
@callback
|
||||
def _calculate_features(
|
||||
self,
|
||||
) -> None:
|
||||
"""Calculate features and preset modes for HA Fan platform from Matter attributes.."""
|
||||
# work out supported features and presets from matter featuremap
|
||||
feature_map = int(
|
||||
self.get_matter_attribute_value(clusters.FanControl.Attributes.FeatureMap)
|
||||
)
|
||||
if feature_map & FanControlFeature.kMultiSpeed:
|
||||
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
||||
self._attr_speed_count = int(
|
||||
self.get_matter_attribute_value(clusters.FanControl.Attributes.SpeedMax)
|
||||
)
|
||||
if feature_map & FanControlFeature.kRocking:
|
||||
# NOTE: the Matter model allows that a device can have multiple/different
|
||||
# rock directions while HA doesn't allow this in the entity model.
|
||||
# For now we just assume that a device has a single rock direction and the
|
||||
# Matter spec is just future proofing for devices that might have multiple
|
||||
# rock directions. As soon as devices show up that actually support multiple
|
||||
# directions, we need to either update the HA Fan entity model or maybe add
|
||||
# this as a separate entity.
|
||||
self._attr_supported_features |= FanEntityFeature.OSCILLATE
|
||||
|
||||
# figure out supported preset modes
|
||||
preset_modes = []
|
||||
fan_mode_seq = int(
|
||||
self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.FanModeSequence
|
||||
)
|
||||
)
|
||||
if fan_mode_seq == FanModeSequenceEnum.kOffLowHigh:
|
||||
preset_modes = [PRESET_LOW, PRESET_HIGH]
|
||||
elif fan_mode_seq == FanModeSequenceEnum.kOffLowHighAuto:
|
||||
preset_modes = [PRESET_LOW, PRESET_HIGH, PRESET_AUTO]
|
||||
elif fan_mode_seq == FanModeSequenceEnum.kOffLowMedHigh:
|
||||
preset_modes = [PRESET_LOW, PRESET_MEDIUM, PRESET_HIGH]
|
||||
elif fan_mode_seq == FanModeSequenceEnum.kOffLowMedHighAuto:
|
||||
preset_modes = [PRESET_LOW, PRESET_MEDIUM, PRESET_HIGH, PRESET_AUTO]
|
||||
elif fan_mode_seq == FanModeSequenceEnum.kOffOnAuto:
|
||||
preset_modes = [PRESET_AUTO]
|
||||
# treat Matter Wind feature as additional preset(s)
|
||||
if feature_map & FanControlFeature.kWind:
|
||||
wind_support = int(
|
||||
self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.WindSupport
|
||||
)
|
||||
)
|
||||
if wind_support & WindBitmap.kNaturalWind:
|
||||
preset_modes.append(PRESET_NATURAL_WIND)
|
||||
if wind_support & WindBitmap.kSleepWind:
|
||||
preset_modes.append(PRESET_SLEEP_WIND)
|
||||
if len(preset_modes) > 0:
|
||||
self._attr_supported_features |= FanEntityFeature.PRESET_MODE
|
||||
self._attr_preset_modes = preset_modes
|
||||
if feature_map & FanControlFeature.kAirflowDirection:
|
||||
self._attr_supported_features |= FanEntityFeature.DIRECTION
|
||||
|
||||
|
||||
# Discovery schema(s) to map Matter Attributes to HA entities
|
||||
DISCOVERY_SCHEMAS = [
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.FAN,
|
||||
entity_description=FanEntityDescription(
|
||||
key="MatterFan", name=None, translation_key="fan"
|
||||
),
|
||||
entity_class=MatterFan,
|
||||
# FanEntityFeature
|
||||
required_attributes=(
|
||||
clusters.FanControl.Attributes.FanMode,
|
||||
clusters.FanControl.Attributes.PercentCurrent,
|
||||
),
|
||||
optional_attributes=(
|
||||
clusters.FanControl.Attributes.SpeedSetting,
|
||||
clusters.FanControl.Attributes.RockSetting,
|
||||
clusters.FanControl.Attributes.WindSetting,
|
||||
clusters.FanControl.Attributes.AirflowDirection,
|
||||
),
|
||||
),
|
||||
]
|
706
tests/components/matter/fixtures/nodes/air-purifier.json
Normal file
706
tests/components/matter/fixtures/nodes/air-purifier.json
Normal file
@ -0,0 +1,706 @@
|
||||
{
|
||||
"node_id": 143,
|
||||
"date_commissioned": "2024-05-27T08:56:55.931757",
|
||||
"last_interview": "2024-05-27T08:56:55.931762",
|
||||
"interview_version": 6,
|
||||
"available": true,
|
||||
"is_bridge": false,
|
||||
"attributes": {
|
||||
"0/29/0": [
|
||||
{
|
||||
"0": 22,
|
||||
"1": 1
|
||||
}
|
||||
],
|
||||
"0/29/1": [29, 31, 40, 42, 48, 49, 50, 51, 60, 62, 63],
|
||||
"0/29/2": [41],
|
||||
"0/29/3": [1, 2, 3, 4, 5],
|
||||
"0/29/65532": 0,
|
||||
"0/29/65533": 2,
|
||||
"0/29/65528": [],
|
||||
"0/29/65529": [],
|
||||
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/31/0": [
|
||||
{
|
||||
"1": 5,
|
||||
"2": 2,
|
||||
"3": [112233],
|
||||
"4": null,
|
||||
"254": 2
|
||||
}
|
||||
],
|
||||
"0/31/1": [],
|
||||
"0/31/2": 4,
|
||||
"0/31/3": 3,
|
||||
"0/31/4": 4,
|
||||
"0/31/65532": 0,
|
||||
"0/31/65533": 1,
|
||||
"0/31/65528": [],
|
||||
"0/31/65529": [],
|
||||
"0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/40/0": 17,
|
||||
"0/40/1": "TEST_VENDOR",
|
||||
"0/40/2": 65521,
|
||||
"0/40/3": "Air Purifier",
|
||||
"0/40/4": 32769,
|
||||
"0/40/5": "",
|
||||
"0/40/6": "**REDACTED**",
|
||||
"0/40/7": 0,
|
||||
"0/40/8": "TEST_VERSION",
|
||||
"0/40/9": 1,
|
||||
"0/40/10": "1.0",
|
||||
"0/40/11": "20200101",
|
||||
"0/40/12": "",
|
||||
"0/40/13": "",
|
||||
"0/40/14": "",
|
||||
"0/40/15": "TEST_SN",
|
||||
"0/40/16": false,
|
||||
"0/40/18": "29E3B8A925484953",
|
||||
"0/40/19": {
|
||||
"0": 3,
|
||||
"1": 65535
|
||||
},
|
||||
"0/40/21": 16973824,
|
||||
"0/40/22": 1,
|
||||
"0/40/65532": 0,
|
||||
"0/40/65533": 3,
|
||||
"0/40/65528": [],
|
||||
"0/40/65529": [],
|
||||
"0/40/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 21, 22,
|
||||
65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"0/42/0": [],
|
||||
"0/42/1": true,
|
||||
"0/42/2": 0,
|
||||
"0/42/3": 0,
|
||||
"0/42/65532": 0,
|
||||
"0/42/65533": 1,
|
||||
"0/42/65528": [],
|
||||
"0/42/65529": [0],
|
||||
"0/42/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/48/0": 0,
|
||||
"0/48/1": {
|
||||
"0": 60,
|
||||
"1": 900
|
||||
},
|
||||
"0/48/2": 0,
|
||||
"0/48/3": 2,
|
||||
"0/48/4": true,
|
||||
"0/48/65532": 0,
|
||||
"0/48/65533": 1,
|
||||
"0/48/65528": [1, 3, 5],
|
||||
"0/48/65529": [0, 2, 4],
|
||||
"0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/49/0": 1,
|
||||
"0/49/1": [
|
||||
{
|
||||
"0": "ZW5kMA==",
|
||||
"1": true
|
||||
}
|
||||
],
|
||||
"0/49/2": 0,
|
||||
"0/49/3": 0,
|
||||
"0/49/4": true,
|
||||
"0/49/5": null,
|
||||
"0/49/6": null,
|
||||
"0/49/7": null,
|
||||
"0/49/65532": 4,
|
||||
"0/49/65533": 2,
|
||||
"0/49/65528": [],
|
||||
"0/49/65529": [],
|
||||
"0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/50/65532": 0,
|
||||
"0/50/65533": 1,
|
||||
"0/50/65528": [1],
|
||||
"0/50/65529": [0],
|
||||
"0/50/65531": [65528, 65529, 65531, 65532, 65533],
|
||||
"0/51/0": [
|
||||
{
|
||||
"0": "veth90ad201",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "niHggbas",
|
||||
"5": [],
|
||||
"6": ["/oAAAAAAAACcIeD//oG2rA=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "veth5a7d8ed",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "nn997EzL",
|
||||
"5": [],
|
||||
"6": ["/oAAAAAAAACcf33//uxMyw=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "veth3408146",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "XqhU7ti3",
|
||||
"5": [],
|
||||
"6": ["/oAAAAAAAABcqFT//u7Ytw=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "veth3f3d040",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "Vlz/o96u",
|
||||
"5": [],
|
||||
"6": ["/oAAAAAAAABUXP///qPerg=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "vethf3a8950",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "Ikj8iJ0V",
|
||||
"5": [],
|
||||
"6": ["/oAAAAAAAAAgSPz//oidFQ=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "vethb3a8e95",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "Pm3ij+z4",
|
||||
"5": [],
|
||||
"6": ["/oAAAAAAAAA8beL//o/s+A=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "veth02a8c45",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "xlbQTHOq",
|
||||
"5": [],
|
||||
"6": ["/oAAAAAAAADEVtD//kxzqg=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "veth2daa408",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "ZucpYWOy",
|
||||
"5": [],
|
||||
"6": ["/oAAAAAAAABk5yn//mFjsg=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "hassio",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "AkKEd951",
|
||||
"5": ["rB4gAQ=="],
|
||||
"6": ["/oAAAAAAAAAAQoT//nfedQ=="],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "docker0",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "AkI4C0xe",
|
||||
"5": ["rB7oAQ=="],
|
||||
"6": [],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "end0",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "redacted",
|
||||
"5": [],
|
||||
"6": [],
|
||||
"7": 2
|
||||
},
|
||||
{
|
||||
"0": "lo",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "AAAAAAAA",
|
||||
"5": [],
|
||||
"6": [],
|
||||
"7": 0
|
||||
}
|
||||
],
|
||||
"0/51/1": 2,
|
||||
"0/51/2": 22,
|
||||
"0/51/3": 0,
|
||||
"0/51/4": 0,
|
||||
"0/51/5": [],
|
||||
"0/51/6": [],
|
||||
"0/51/7": [],
|
||||
"0/51/8": false,
|
||||
"0/51/65532": 0,
|
||||
"0/51/65533": 2,
|
||||
"0/51/65528": [2],
|
||||
"0/51/65529": [0, 1],
|
||||
"0/51/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"0/60/0": 0,
|
||||
"0/60/1": null,
|
||||
"0/60/2": null,
|
||||
"0/60/65532": 0,
|
||||
"0/60/65533": 1,
|
||||
"0/60/65528": [],
|
||||
"0/60/65529": [0, 2],
|
||||
"0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/62/0": [
|
||||
{
|
||||
"1": "redacted",
|
||||
"2": "redacted",
|
||||
"254": 2
|
||||
}
|
||||
],
|
||||
"0/62/1": [
|
||||
{
|
||||
"1": "redacted",
|
||||
"2": 65521,
|
||||
"3": 1,
|
||||
"4": 143,
|
||||
"5": "",
|
||||
"254": 2
|
||||
}
|
||||
],
|
||||
"0/62/2": 16,
|
||||
"0/62/3": 1,
|
||||
"0/62/4": ["redacted"],
|
||||
"0/62/5": 2,
|
||||
"0/62/65532": 0,
|
||||
"0/62/65533": 1,
|
||||
"0/62/65528": [1, 3, 5, 8],
|
||||
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11],
|
||||
"0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/63/0": [],
|
||||
"0/63/1": [],
|
||||
"0/63/2": 4,
|
||||
"0/63/3": 3,
|
||||
"0/63/65532": 0,
|
||||
"0/63/65533": 2,
|
||||
"0/63/65528": [2, 5],
|
||||
"0/63/65529": [0, 1, 3, 4],
|
||||
"0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/3/0": 0,
|
||||
"1/3/1": 0,
|
||||
"1/3/65532": 0,
|
||||
"1/3/65533": 4,
|
||||
"1/3/65528": [],
|
||||
"1/3/65529": [0, 64],
|
||||
"1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/29/0": [
|
||||
{
|
||||
"0": 45,
|
||||
"1": 1
|
||||
}
|
||||
],
|
||||
"1/29/1": [3, 29, 113, 114, 514],
|
||||
"1/29/2": [],
|
||||
"1/29/3": [2, 3, 4, 5],
|
||||
"1/29/65532": 0,
|
||||
"1/29/65533": 2,
|
||||
"1/29/65528": [],
|
||||
"1/29/65529": [],
|
||||
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/113/0": 100,
|
||||
"1/113/1": 1,
|
||||
"1/113/2": 0,
|
||||
"1/113/3": true,
|
||||
"1/113/4": null,
|
||||
"1/113/5": [
|
||||
{
|
||||
"0": 0,
|
||||
"1": "111112222233"
|
||||
},
|
||||
{
|
||||
"0": 1,
|
||||
"1": "gtin8xxx"
|
||||
},
|
||||
{
|
||||
"0": 2,
|
||||
"1": "4444455555666"
|
||||
},
|
||||
{
|
||||
"0": 3,
|
||||
"1": "gtin14xxxxxxxx"
|
||||
},
|
||||
{
|
||||
"0": 4,
|
||||
"1": "oem20xxxxxxxxxxxxxxx"
|
||||
}
|
||||
],
|
||||
"1/113/65532": 7,
|
||||
"1/113/65533": 1,
|
||||
"1/113/65528": [],
|
||||
"1/113/65529": [0],
|
||||
"1/113/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/114/0": 100,
|
||||
"1/114/1": 1,
|
||||
"1/114/2": 0,
|
||||
"1/114/3": true,
|
||||
"1/114/4": null,
|
||||
"1/114/5": [
|
||||
{
|
||||
"0": 0,
|
||||
"1": "111112222233"
|
||||
},
|
||||
{
|
||||
"0": 1,
|
||||
"1": "gtin8xxx"
|
||||
},
|
||||
{
|
||||
"0": 2,
|
||||
"1": "4444455555666"
|
||||
},
|
||||
{
|
||||
"0": 3,
|
||||
"1": "gtin14xxxxxxxx"
|
||||
},
|
||||
{
|
||||
"0": 4,
|
||||
"1": "oem20xxxxxxxxxxxxxxx"
|
||||
}
|
||||
],
|
||||
"1/114/65532": 7,
|
||||
"1/114/65533": 1,
|
||||
"1/114/65528": [],
|
||||
"1/114/65529": [0],
|
||||
"1/114/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/514/0": 5,
|
||||
"1/514/1": 2,
|
||||
"1/514/2": null,
|
||||
"1/514/3": 255,
|
||||
"1/514/4": 10,
|
||||
"1/514/5": null,
|
||||
"1/514/6": 255,
|
||||
"1/514/7": 1,
|
||||
"1/514/8": 0,
|
||||
"1/514/9": 3,
|
||||
"1/514/10": 0,
|
||||
"1/514/11": 0,
|
||||
"1/514/65532": 63,
|
||||
"1/514/65533": 4,
|
||||
"1/514/65528": [],
|
||||
"1/514/65529": [0],
|
||||
"1/514/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/3/0": 0,
|
||||
"2/3/1": 0,
|
||||
"2/3/65532": 0,
|
||||
"2/3/65533": 4,
|
||||
"2/3/65528": [],
|
||||
"2/3/65529": [0],
|
||||
"2/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
|
||||
"2/29/0": [
|
||||
{
|
||||
"0": 44,
|
||||
"1": 1
|
||||
}
|
||||
],
|
||||
"2/29/1": [
|
||||
3, 29, 91, 1036, 1037, 1043, 1045, 1066, 1067, 1068, 1069, 1070, 1071
|
||||
],
|
||||
"2/29/2": [],
|
||||
"2/29/3": [],
|
||||
"2/29/65532": 0,
|
||||
"2/29/65533": 2,
|
||||
"2/29/65528": [],
|
||||
"2/29/65529": [],
|
||||
"2/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"2/91/0": 1,
|
||||
"2/91/65532": 15,
|
||||
"2/91/65533": 1,
|
||||
"2/91/65528": [],
|
||||
"2/91/65529": [],
|
||||
"2/91/65531": [0, 65528, 65529, 65531, 65532, 65533],
|
||||
"2/1036/0": 2.0,
|
||||
"2/1036/1": 0.0,
|
||||
"2/1036/2": 1000.0,
|
||||
"2/1036/3": 1.0,
|
||||
"2/1036/4": 320,
|
||||
"2/1036/5": 1.0,
|
||||
"2/1036/6": 320,
|
||||
"2/1036/7": 0.0,
|
||||
"2/1036/8": 0,
|
||||
"2/1036/9": 0,
|
||||
"2/1036/10": 1,
|
||||
"2/1036/65532": 63,
|
||||
"2/1036/65533": 3,
|
||||
"2/1036/65528": [],
|
||||
"2/1036/65529": [],
|
||||
"2/1036/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1037/0": 2.0,
|
||||
"2/1037/1": 0.0,
|
||||
"2/1037/2": 1000.0,
|
||||
"2/1037/3": 1.0,
|
||||
"2/1037/4": 320,
|
||||
"2/1037/5": 1.0,
|
||||
"2/1037/6": 320,
|
||||
"2/1037/7": 0.0,
|
||||
"2/1037/8": 0,
|
||||
"2/1037/9": 0,
|
||||
"2/1037/10": 1,
|
||||
"2/1037/65532": 63,
|
||||
"2/1037/65533": 3,
|
||||
"2/1037/65528": [],
|
||||
"2/1037/65529": [],
|
||||
"2/1037/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1043/0": 2.0,
|
||||
"2/1043/1": 0.0,
|
||||
"2/1043/2": 1000.0,
|
||||
"2/1043/3": 1.0,
|
||||
"2/1043/4": 320,
|
||||
"2/1043/5": 1.0,
|
||||
"2/1043/6": 320,
|
||||
"2/1043/7": 0.0,
|
||||
"2/1043/8": 0,
|
||||
"2/1043/9": 0,
|
||||
"2/1043/10": 1,
|
||||
"2/1043/65532": 63,
|
||||
"2/1043/65533": 3,
|
||||
"2/1043/65528": [],
|
||||
"2/1043/65529": [],
|
||||
"2/1043/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1045/0": 2.0,
|
||||
"2/1045/1": 0.0,
|
||||
"2/1045/2": 1000.0,
|
||||
"2/1045/3": 1.0,
|
||||
"2/1045/4": 320,
|
||||
"2/1045/5": 1.0,
|
||||
"2/1045/6": 320,
|
||||
"2/1045/7": 0.0,
|
||||
"2/1045/8": 0,
|
||||
"2/1045/9": 0,
|
||||
"2/1045/10": 1,
|
||||
"2/1045/65532": 63,
|
||||
"2/1045/65533": 3,
|
||||
"2/1045/65528": [],
|
||||
"2/1045/65529": [],
|
||||
"2/1045/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1066/0": 2.0,
|
||||
"2/1066/1": 0.0,
|
||||
"2/1066/2": 1000.0,
|
||||
"2/1066/3": 1.0,
|
||||
"2/1066/4": 320,
|
||||
"2/1066/5": 1.0,
|
||||
"2/1066/6": 320,
|
||||
"2/1066/7": 0.0,
|
||||
"2/1066/8": 0,
|
||||
"2/1066/9": 0,
|
||||
"2/1066/10": 1,
|
||||
"2/1066/65532": 63,
|
||||
"2/1066/65533": 3,
|
||||
"2/1066/65528": [],
|
||||
"2/1066/65529": [],
|
||||
"2/1066/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1067/0": 2.0,
|
||||
"2/1067/1": 0.0,
|
||||
"2/1067/2": 1000.0,
|
||||
"2/1067/3": 1.0,
|
||||
"2/1067/4": 320,
|
||||
"2/1067/5": 1.0,
|
||||
"2/1067/6": 320,
|
||||
"2/1067/7": 0.0,
|
||||
"2/1067/8": 0,
|
||||
"2/1067/9": 0,
|
||||
"2/1067/10": 1,
|
||||
"2/1067/65532": 63,
|
||||
"2/1067/65533": 3,
|
||||
"2/1067/65528": [],
|
||||
"2/1067/65529": [],
|
||||
"2/1067/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1068/0": 2.0,
|
||||
"2/1068/1": 0.0,
|
||||
"2/1068/2": 1000.0,
|
||||
"2/1068/3": 1.0,
|
||||
"2/1068/4": 320,
|
||||
"2/1068/5": 1.0,
|
||||
"2/1068/6": 320,
|
||||
"2/1068/7": 0.0,
|
||||
"2/1068/8": 0,
|
||||
"2/1068/9": 0,
|
||||
"2/1068/10": 1,
|
||||
"2/1068/65532": 63,
|
||||
"2/1068/65533": 3,
|
||||
"2/1068/65528": [],
|
||||
"2/1068/65529": [],
|
||||
"2/1068/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1069/0": 2.0,
|
||||
"2/1069/1": 0.0,
|
||||
"2/1069/2": 1000.0,
|
||||
"2/1069/3": 1.0,
|
||||
"2/1069/4": 320,
|
||||
"2/1069/5": 1.0,
|
||||
"2/1069/6": 320,
|
||||
"2/1069/7": 0.0,
|
||||
"2/1069/8": 0,
|
||||
"2/1069/9": 0,
|
||||
"2/1069/10": 1,
|
||||
"2/1069/65532": 63,
|
||||
"2/1069/65533": 3,
|
||||
"2/1069/65528": [],
|
||||
"2/1069/65529": [],
|
||||
"2/1069/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1070/0": 2.0,
|
||||
"2/1070/1": 0.0,
|
||||
"2/1070/2": 1000.0,
|
||||
"2/1070/3": 1.0,
|
||||
"2/1070/4": 320,
|
||||
"2/1070/5": 1.0,
|
||||
"2/1070/6": 320,
|
||||
"2/1070/7": 0.0,
|
||||
"2/1070/8": 0,
|
||||
"2/1070/9": 0,
|
||||
"2/1070/10": 1,
|
||||
"2/1070/65532": 63,
|
||||
"2/1070/65533": 3,
|
||||
"2/1070/65528": [],
|
||||
"2/1070/65529": [],
|
||||
"2/1070/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"2/1071/0": 2.0,
|
||||
"2/1071/1": 0.0,
|
||||
"2/1071/2": 1000.0,
|
||||
"2/1071/3": 1.0,
|
||||
"2/1071/4": 320,
|
||||
"2/1071/5": 1.0,
|
||||
"2/1071/6": 320,
|
||||
"2/1071/7": 0.0,
|
||||
"2/1071/8": 0,
|
||||
"2/1071/9": 0,
|
||||
"2/1071/10": 1,
|
||||
"2/1071/65532": 63,
|
||||
"2/1071/65533": 3,
|
||||
"2/1071/65528": [],
|
||||
"2/1071/65529": [],
|
||||
"2/1071/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"3/3/0": 0,
|
||||
"3/3/1": 0,
|
||||
"3/3/65532": 0,
|
||||
"3/3/65533": 4,
|
||||
"3/3/65528": [],
|
||||
"3/3/65529": [0, 64],
|
||||
"3/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
|
||||
"3/29/0": [
|
||||
{
|
||||
"0": 770,
|
||||
"1": 2
|
||||
}
|
||||
],
|
||||
"3/29/1": [3, 29, 1026],
|
||||
"3/29/2": [],
|
||||
"3/29/3": [],
|
||||
"3/29/65532": 0,
|
||||
"3/29/65533": 2,
|
||||
"3/29/65528": [],
|
||||
"3/29/65529": [],
|
||||
"3/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"3/1026/0": 2000,
|
||||
"3/1026/1": -500,
|
||||
"3/1026/2": 6000,
|
||||
"3/1026/3": 0,
|
||||
"3/1026/65532": 0,
|
||||
"3/1026/65533": 4,
|
||||
"3/1026/65528": [],
|
||||
"3/1026/65529": [],
|
||||
"3/1026/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"4/3/0": 0,
|
||||
"4/3/1": 0,
|
||||
"4/3/65532": 0,
|
||||
"4/3/65533": 4,
|
||||
"4/3/65528": [],
|
||||
"4/3/65529": [0, 64],
|
||||
"4/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
|
||||
"4/29/0": [
|
||||
{
|
||||
"0": 775,
|
||||
"1": 2
|
||||
}
|
||||
],
|
||||
"4/29/1": [3, 29, 1029],
|
||||
"4/29/2": [],
|
||||
"4/29/3": [],
|
||||
"4/29/65532": 0,
|
||||
"4/29/65533": 2,
|
||||
"4/29/65528": [],
|
||||
"4/29/65529": [],
|
||||
"4/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"4/1029/0": 5000,
|
||||
"4/1029/1": 0,
|
||||
"4/1029/2": 10000,
|
||||
"4/1029/3": 0,
|
||||
"4/1029/65532": 0,
|
||||
"4/1029/65533": 3,
|
||||
"4/1029/65528": [],
|
||||
"4/1029/65529": [],
|
||||
"4/1029/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"5/3/0": 0,
|
||||
"5/3/1": 0,
|
||||
"5/3/65532": 0,
|
||||
"5/3/65533": 4,
|
||||
"5/3/65528": [],
|
||||
"5/3/65529": [0, 64],
|
||||
"5/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
|
||||
"5/29/0": [
|
||||
{
|
||||
"0": 769,
|
||||
"1": 2
|
||||
}
|
||||
],
|
||||
"5/29/1": [3, 29, 513],
|
||||
"5/29/2": [],
|
||||
"5/29/3": [],
|
||||
"5/29/65532": 0,
|
||||
"5/29/65533": 2,
|
||||
"5/29/65528": [],
|
||||
"5/29/65529": [],
|
||||
"5/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"5/513/0": 2000,
|
||||
"5/513/3": 500,
|
||||
"5/513/4": 3000,
|
||||
"5/513/18": 2000,
|
||||
"5/513/27": 2,
|
||||
"5/513/28": 0,
|
||||
"5/513/41": 0,
|
||||
"5/513/65532": 1,
|
||||
"5/513/65533": 6,
|
||||
"5/513/65528": [],
|
||||
"5/513/65529": [0],
|
||||
"5/513/65531": [0, 3, 4, 18, 27, 28, 41, 65528, 65529, 65531, 65532, 65533]
|
||||
},
|
||||
"attribute_subscriptions": []
|
||||
}
|
275
tests/components/matter/test_fan.py
Normal file
275
tests/components/matter/test_fan.py
Normal file
@ -0,0 +1,275 @@
|
||||
"""Test Matter Fan platform."""
|
||||
|
||||
from unittest.mock import MagicMock, call
|
||||
|
||||
from matter_server.client.models.node import MatterNode
|
||||
import pytest
|
||||
|
||||
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
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .common import (
|
||||
set_node_attribute,
|
||||
setup_integration_with_node_fixture,
|
||||
trigger_subscription_callback,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="air_purifier")
|
||||
async def air_purifier_fixture(
|
||||
hass: HomeAssistant, matter_client: MagicMock
|
||||
) -> MatterNode:
|
||||
"""Fixture for a Air Purifier node (containing Fan cluster)."""
|
||||
return await setup_integration_with_node_fixture(
|
||||
hass, "air-purifier", matter_client
|
||||
)
|
||||
|
||||
|
||||
# This tests needs to be adjusted to remove lingering tasks
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
async def test_fan_base(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
air_purifier: 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(air_purifier, 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(air_purifier, 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(air_purifier, 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(air_purifier, 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(air_purifier, 1, 514, 10, 1)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes["preset_mode"] == "sleep_wind"
|
||||
|
||||
|
||||
async def test_fan_turn_on_with_percentage(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
air_purifier: MatterNode,
|
||||
):
|
||||
"""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=air_purifier.node_id,
|
||||
attribute_path="1/514/2",
|
||||
value=50,
|
||||
)
|
||||
|
||||
|
||||
async def test_fan_turn_on_with_preset_mode(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
air_purifier: MatterNode,
|
||||
):
|
||||
"""Test turning on the fan with a specific preset mode."""
|
||||
entity_id = "fan.air_purifier"
|
||||
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=air_purifier.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=air_purifier.node_id,
|
||||
attribute_path="1/514/10",
|
||||
value=value,
|
||||
)
|
||||
# test again where preset_mode is omitted in the service call
|
||||
# which should select a default preset mode
|
||||
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=air_purifier.node_id,
|
||||
attribute_path="1/514/0",
|
||||
value=5,
|
||||
)
|
||||
# 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(air_purifier, 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=air_purifier.node_id,
|
||||
attribute_path="1/514/10",
|
||||
value=0,
|
||||
)
|
||||
assert matter_client.write_attribute.call_args == call(
|
||||
node_id=air_purifier.node_id,
|
||||
attribute_path="1/514/0",
|
||||
value=2,
|
||||
)
|
||||
|
||||
|
||||
async def test_fan_turn_off(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
air_purifier: MatterNode,
|
||||
):
|
||||
"""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=air_purifier.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(air_purifier, 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=air_purifier.node_id,
|
||||
attribute_path="1/514/10",
|
||||
value=0,
|
||||
)
|
||||
assert matter_client.write_attribute.call_args_list[1] == call(
|
||||
node_id=air_purifier.node_id,
|
||||
attribute_path="1/514/0",
|
||||
value=0,
|
||||
)
|
||||
|
||||
|
||||
async def test_fan_oscillate(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
air_purifier: MatterNode,
|
||||
):
|
||||
"""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=air_purifier.node_id,
|
||||
attribute_path="1/514/8",
|
||||
value=value,
|
||||
)
|
||||
matter_client.write_attribute.reset_mock()
|
||||
|
||||
|
||||
async def test_fan_set_direction(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
air_purifier: MatterNode,
|
||||
):
|
||||
"""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=air_purifier.node_id,
|
||||
attribute_path="1/514/11",
|
||||
value=value,
|
||||
)
|
||||
matter_client.write_attribute.reset_mock()
|
Loading…
x
Reference in New Issue
Block a user