Revert "Add Matter service actions for vacuum area (#151467)" (#152386)

This commit is contained in:
Artur Pragacz
2025-09-15 20:14:09 +02:00
committed by GitHub
parent f5157878c2
commit d51c0e3752
11 changed files with 2 additions and 1070 deletions
-5
View File
@@ -15,8 +15,3 @@ ID_TYPE_DEVICE_ID = "deviceid"
ID_TYPE_SERIAL = "serial"
FEATUREMAP_ATTRIBUTE_ID = 65532
# vacuum entity service actions
SERVICE_GET_AREAS = "get_areas" # get SupportedAreas and SupportedMaps
SERVICE_SELECT_AREAS = "select_areas" # call SelectAreas Matter command
SERVICE_CLEAN_AREAS = "clean_areas" # call SelectAreas Matter command and start RVC
@@ -150,16 +150,5 @@
"default": "mdi:ev-station"
}
}
},
"services": {
"clean_areas": {
"service": "mdi:robot-vacuum"
},
"get_areas": {
"service": "mdi:map"
},
"select_areas": {
"service": "mdi:map"
}
}
}
@@ -1,24 +0,0 @@
# Service descriptions for Matter integration
get_areas:
target:
entity:
domain: vacuum
select_areas:
target:
entity:
domain: vacuum
fields:
areas:
required: true
example: [1, 3]
clean_areas:
target:
entity:
domain: vacuum
fields:
areas:
required: true
example: [1, 3]
@@ -548,30 +548,6 @@
"description": "The Matter device to add to the other Matter network."
}
}
},
"get_areas": {
"name": "Get areas",
"description": "Returns a list of available areas and maps for robot vacuum cleaners."
},
"select_areas": {
"name": "Select areas",
"description": "Selects the specified areas for cleaning. The areas must be specified as a list of area IDs.",
"fields": {
"areas": {
"name": "Areas",
"description": "A list of area IDs to select."
}
}
},
"clean_areas": {
"name": "Clean areas",
"description": "Instructs the Matter vacuum cleaner to clean the specified areas.",
"fields": {
"areas": {
"name": "Areas",
"description": "A list of area IDs to clean."
}
}
}
}
}
+2 -210
View File
@@ -3,12 +3,10 @@
from __future__ import annotations
from enum import IntEnum
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any
from chip.clusters import Objects as clusters
from chip.clusters.Objects import NullValue
from matter_server.client.models import device_types
import voluptuous as vol
from homeassistant.components.vacuum import (
StateVacuumEntity,
@@ -18,25 +16,14 @@ from homeassistant.components.vacuum import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import (
HomeAssistant,
ServiceResponse,
SupportsResponse,
callback,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import SERVICE_CLEAN_AREAS, SERVICE_GET_AREAS, SERVICE_SELECT_AREAS
from .entity import MatterEntity
from .helpers import get_matter
from .models import MatterDiscoverySchema
ATTR_CURRENT_AREA = "current_area"
ATTR_CURRENT_AREA_NAME = "current_area_name"
ATTR_SELECTED_AREAS = "selected_areas"
class OperationalState(IntEnum):
"""Operational State of the vacuum cleaner.
@@ -69,33 +56,6 @@ async def async_setup_entry(
"""Set up Matter vacuum platform from Config Entry."""
matter = get_matter(hass)
matter.register_platform_handler(Platform.VACUUM, async_add_entities)
platform = entity_platform.async_get_current_platform()
# This will call Entity.async_handle_get_areas
platform.async_register_entity_service(
SERVICE_GET_AREAS,
schema=None,
func="async_handle_get_areas",
supports_response=SupportsResponse.ONLY,
)
# This will call Entity.async_handle_clean_areas
platform.async_register_entity_service(
SERVICE_CLEAN_AREAS,
schema={
vol.Required("areas"): vol.All(cv.ensure_list, [cv.positive_int]),
},
func="async_handle_clean_areas",
supports_response=SupportsResponse.ONLY,
)
# This will call Entity.async_handle_select_areas
platform.async_register_entity_service(
SERVICE_SELECT_AREAS,
schema={
vol.Required("areas"): vol.All(cv.ensure_list, [cv.positive_int]),
},
func="async_handle_select_areas",
supports_response=SupportsResponse.ONLY,
)
class MatterVacuum(MatterEntity, StateVacuumEntity):
@@ -105,23 +65,9 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
_supported_run_modes: (
dict[int, clusters.RvcRunMode.Structs.ModeOptionStruct] | None
) = None
_attr_matter_areas: dict[str, Any] | None = None
_attr_current_area: int | None = None
_attr_current_area_name: str | None = None
_attr_selected_areas: list[int] | None = None
_attr_supported_maps: list[dict[str, Any]] | None = None
entity_description: StateVacuumEntityDescription
_platform_translation_key = "vacuum"
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes of the entity."""
return {
ATTR_CURRENT_AREA: self._attr_current_area,
ATTR_CURRENT_AREA_NAME: self._attr_current_area_name,
ATTR_SELECTED_AREAS: self._attr_selected_areas,
}
def _get_run_mode_by_tag(
self, tag: ModeTag
) -> clusters.RvcRunMode.Structs.ModeOptionStruct | None:
@@ -190,160 +136,10 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
"""Pause the cleaning task."""
await self.send_device_command(clusters.RvcOperationalState.Commands.Pause())
def async_get_areas(self, **kwargs: Any) -> dict[str, Any]:
"""Get available area and map IDs from vacuum appliance."""
supported_areas = self.get_matter_attribute_value(
clusters.ServiceArea.Attributes.SupportedAreas
)
if not supported_areas:
raise HomeAssistantError("Can't get areas from the device.")
# Group by area_id: {area_id: {"map_id": ..., "name": ...}}
areas = {}
for area in supported_areas:
area_id = getattr(area, "areaID", None)
map_id = getattr(area, "mapID", None)
location_name = None
area_info = getattr(area, "areaInfo", None)
if area_info is not None:
location_info = getattr(area_info, "locationInfo", None)
if location_info is not None:
location_name = getattr(location_info, "locationName", None)
if area_id is not None:
areas[area_id] = {"map_id": map_id, "name": location_name}
# Optionally, also extract supported maps if available
supported_maps = self.get_matter_attribute_value(
clusters.ServiceArea.Attributes.SupportedMaps
)
maps = []
if supported_maps:
maps = [
{
"map_id": getattr(m, "mapID", None),
"name": getattr(m, "name", None),
}
for m in supported_maps
]
return {
"areas": areas,
"maps": maps,
}
async def async_handle_get_areas(self, **kwargs: Any) -> ServiceResponse:
"""Get available area and map IDs from vacuum appliance."""
# Group by area_id: {area_id: {"map_id": ..., "name": ...}}
areas = {}
if self._attr_matter_areas is not None:
for area in self._attr_matter_areas:
area_id = getattr(area, "areaID", None)
map_id = getattr(area, "mapID", None)
location_name = None
area_info = getattr(area, "areaInfo", None)
if area_info is not None:
location_info = getattr(area_info, "locationInfo", None)
if location_info is not None:
location_name = getattr(location_info, "locationName", None)
if area_id is not None:
if map_id is NullValue:
areas[area_id] = {"name": location_name}
else:
areas[area_id] = {"map_id": map_id, "name": location_name}
# Optionally, also extract supported maps if available
supported_maps = self.get_matter_attribute_value(
clusters.ServiceArea.Attributes.SupportedMaps
)
maps = []
if supported_maps != NullValue: # chip.clusters.Types.Nullable
maps = [
{
"map_id": getattr(m, "mapID", None)
if getattr(m, "mapID", None) != NullValue
else None,
"name": getattr(m, "name", None),
}
for m in supported_maps
]
return cast(
ServiceResponse,
{
"areas": areas,
"maps": maps,
},
)
return None
async def async_handle_select_areas(
self, areas: list[int], **kwargs: Any
) -> ServiceResponse:
"""Select areas to clean."""
selected_areas = areas
# Matter command to the vacuum cleaner to select the areas.
await self.send_device_command(
clusters.ServiceArea.Commands.SelectAreas(newAreas=selected_areas)
)
# Return response indicating selected areas.
return cast(
ServiceResponse, {"status": "areas selected", "areas": selected_areas}
)
async def async_handle_clean_areas(
self, areas: list[int], **kwargs: Any
) -> ServiceResponse:
"""Start cleaning the specified areas."""
# Matter command to the vacuum cleaner to select the areas.
await self.send_device_command(
clusters.ServiceArea.Commands.SelectAreas(newAreas=areas)
)
# Start the vacuum cleaner after selecting areas.
await self.async_start()
# Return response indicating selected areas.
return cast(
ServiceResponse, {"status": "cleaning areas selected", "areas": areas}
)
@callback
def _update_from_device(self) -> None:
"""Update from device."""
self._calculate_features()
# ServiceArea: get areas from the device
self._attr_matter_areas = self.get_matter_attribute_value(
clusters.ServiceArea.Attributes.SupportedAreas
)
# optional CurrentArea attribute
# pylint: disable=too-many-nested-blocks
if self.get_matter_attribute_value(clusters.ServiceArea.Attributes.CurrentArea):
current_area = self.get_matter_attribute_value(
clusters.ServiceArea.Attributes.CurrentArea
)
# get areaInfo.locationInfo.locationName for current_area in SupportedAreas list
area_name = None
if self._attr_matter_areas:
for area in self._attr_matter_areas:
if getattr(area, "areaID", None) == current_area:
area_info = getattr(area, "areaInfo", None)
if area_info is not None:
location_info = getattr(area_info, "locationInfo", None)
if location_info is not None:
area_name = getattr(location_info, "locationName", None)
break
self._attr_current_area = current_area
self._attr_current_area_name = area_name
else:
self._attr_current_area = None
self._attr_current_area_name = None
# optional SelectedAreas attribute
if self.get_matter_attribute_value(
clusters.ServiceArea.Attributes.SelectedAreas
):
self._attr_selected_areas = self.get_matter_attribute_value(
clusters.ServiceArea.Attributes.SelectedAreas
)
# derive state from the run mode + operational state
run_mode_raw: int = self.get_matter_attribute_value(
clusters.RvcRunMode.Attributes.CurrentMode
@@ -424,10 +220,6 @@ DISCOVERY_SCHEMAS = [
clusters.RvcRunMode.Attributes.CurrentMode,
clusters.RvcOperationalState.Attributes.OperationalState,
),
optional_attributes=(
clusters.ServiceArea.Attributes.SelectedAreas,
clusters.ServiceArea.Attributes.CurrentArea,
),
device_type=(device_types.RoboticVacuumCleaner,),
allow_none_value=True,
),
-1
View File
@@ -121,7 +121,6 @@ async def integration_fixture(
"smoke_detector",
"solar_power",
"switch_unit",
"switchbot_K11",
"temperature_sensor",
"thermostat",
"vacuum_cleaner",
@@ -1,440 +0,0 @@
{
"node_id": 97,
"date_commissioned": "2025-08-21T16:38:31.165712",
"last_interview": "2025-08-21T16:38:31.165730",
"interview_version": 6,
"available": true,
"is_bridge": false,
"attributes": {
"0/29/0": [
{
"0": 22,
"1": 1
}
],
"0/29/1": [29, 31, 40, 48, 51, 60, 62, 63],
"0/29/2": [],
"0/29/3": [1],
"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": 3
}
],
"0/31/1": [],
"0/31/2": 4,
"0/31/3": 3,
"0/31/4": 4,
"0/31/65532": 1,
"0/31/65533": 2,
"0/31/65528": [],
"0/31/65529": [],
"0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"0/40/0": 18,
"0/40/1": "SwitchBot",
"0/40/2": 5015,
"0/40/3": "K11+",
"0/40/4": 2043,
"0/40/5": "",
"0/40/6": "**REDACTED**",
"0/40/7": 8,
"0/40/8": "8",
"0/40/9": 2,
"0/40/10": "2.0",
"0/40/11": "20200101",
"0/40/15": "SY612505261610300E",
"0/40/16": false,
"0/40/18": "5E441F48C89E75F4",
"0/40/19": {
"0": 3,
"1": 65535
},
"0/40/21": 17039616,
"0/40/22": 1,
"0/40/65532": 0,
"0/40/65533": 4,
"0/40/65528": [],
"0/40/65529": [],
"0/40/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 15, 16, 18, 19, 21, 22, 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": 2,
"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/51/0": [
{
"0": "wlan0",
"1": true,
"2": null,
"3": null,
"4": "sOn+hWUk",
"5": ["wKgBow=="],
"6": ["KgEOCgKzOZBGmN+UianfsA==", "/oAAAAAAAACEb4xWVmm9jw=="],
"7": 1
},
{
"0": "lo",
"1": true,
"2": null,
"3": null,
"4": "AAAAAAAA",
"5": ["fwAAAQ=="],
"6": ["AAAAAAAAAAAAAAAAAAAAAQ=="],
"7": 0
}
],
"0/51/1": 8,
"0/51/2": 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, 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": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRYRgkBwEkCAEwCUEED3gG83T4fgQ8mJi4UtxYHdce62io4H76mdpHCQluYUJ3zb4ahgxgT9tz7eNDwOooSPo985+iv5hDEEYsuVUu1TcKNQEoARgkAgE2AwQCBAEYMAQUGDYBbm6GdsqVhw7HwYXe2fWNMXIwBRS5+zzv8ZPGnI9mC3wH9vq10JnwlhgwC0DuruGO/yh7HLCuMeBxe6kBbjeStJ+VJAdWHiXBEyE1x2LZPcgX1LXpIwjshY5ACCNFRTuwtIH9GwSt9iVKZc7/GA==",
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE/DujEcdTsX19xbxX+KuKKWiMaA5D9u99P/pVxIOmscd2BA2PadEMNnjvtPOpf+WE2Zxar4rby1IfAClGUUuQrTcKNQEpARgkAmAwBBS5+zzv8ZPGnI9mC3wH9vq10JnwljAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQGkPpvsbkAFEbfPN6H3Kf23R0zzmW/gpAA3kgaL6wKB2Ofm+Tmylw22qM536Kj8mOMwaV0EL1dCCGcuxF98aL6gY",
"254": 3
}
],
"0/62/1": [
{
"1": "***********",
"2": 4939,
"3": 2,
"4": 97,
"5": "SSID",
"254": 3
}
],
"0/62/2": 16,
"0/62/3": 5,
"0/62/4": ["***********"],
"0/62/5": 3,
"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": 5,
"1/3/65528": [],
"1/3/65529": [0],
"1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/29/0": [
{
"0": 116,
"1": 1
}
],
"1/29/1": [3, 29, 84, 85, 97, 336],
"1/29/2": [],
"1/29/3": [],
"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/84/0": [
{
"0": "Idle",
"1": 0,
"2": [
{
"1": 16384
}
]
},
{
"0": "Cleaning",
"1": 1,
"2": [
{
"1": 16385
}
]
},
{
"0": "Mapping",
"1": 2,
"2": [
{
"1": 16386
}
]
},
{
"0": "Pause",
"1": 3,
"2": [
{
"1": 32769
},
{
"1": 0
}
]
},
{
"0": "Resume",
"1": 4,
"2": [
{
"1": 32770
},
{
"1": 0
}
]
},
{
"0": "Docking",
"1": 5,
"2": [
{
"1": 32771
},
{
"1": 0
}
]
}
],
"1/84/1": 0,
"1/84/65532": 0,
"1/84/65533": 3,
"1/84/65528": [1],
"1/84/65529": [0],
"1/84/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/85/0": [
{
"0": "Quick",
"1": 0,
"2": [
{
"1": 16385
},
{
"1": 1
}
]
},
{
"0": "Auto",
"1": 1,
"2": [
{
"1": 16385
},
{
"1": 0
}
]
},
{
"0": "Deep Clean",
"1": 2,
"2": [
{
"1": 16385
},
{
"1": 16384
}
]
},
{
"0": "Quiet",
"1": 3,
"2": [
{
"1": 16385
},
{
"1": 2
}
]
},
{
"0": "Max Vac",
"1": 4,
"2": [
{
"1": 16385
},
{
"1": 7
}
]
}
],
"1/85/1": 0,
"1/85/65532": 0,
"1/85/65533": 3,
"1/85/65528": [1],
"1/85/65529": [0],
"1/85/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/97/0": null,
"1/97/1": null,
"1/97/3": [
{
"0": 0
},
{
"0": 1
},
{
"0": 2
},
{
"0": 3
},
{
"0": 64
},
{
"0": 65
},
{
"0": 66
}
],
"1/97/4": 0,
"1/97/5": {
"0": 0
},
"1/97/65532": 0,
"1/97/65533": 2,
"1/97/65528": [4],
"1/97/65529": [0, 3, 128],
"1/97/65531": [0, 1, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
"1/336/0": [
{
"0": 1,
"1": null,
"2": {
"0": {
"0": "Bedroom #3",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 2,
"1": null,
"2": {
"0": {
"0": "Stairs",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 3,
"1": null,
"2": {
"0": {
"0": "Bedroom #1",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 4,
"1": null,
"2": {
"0": {
"0": "Bedroom #2",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 5,
"1": null,
"2": {
"0": {
"0": "Corridor",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 6,
"1": null,
"2": {
"0": {
"0": "Bathroom",
"1": null,
"2": null
},
"1": null
}
}
],
"1/336/1": [],
"1/336/2": [4, 3],
"1/336/3": null,
"1/336/4": null,
"1/336/5": [],
"1/336/65532": 6,
"1/336/65533": 1,
"1/336/65528": [1, 3],
"1/336/65529": [0, 2],
"1/336/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533]
},
"attribute_subscriptions": []
}
@@ -2676,69 +2676,6 @@
'state': 'previous',
})
# ---
# name: test_selects[switchbot_K11][select.k11_clean_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'Quick',
'Auto',
'Deep Clean',
'Quiet',
'Max Vac',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.k11_clean_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Clean mode',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'clean_mode',
'unique_id': '00000000000004D2-0000000000000061-MatterNodeDevice-1-MatterRvcCleanMode-85-1',
'unit_of_measurement': None,
})
# ---
# name: test_selects[switchbot_K11][select.k11_clean_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'K11+ Clean mode',
'options': list([
'Quick',
'Auto',
'Deep Clean',
'Quiet',
'Max Vac',
]),
}),
'context': <ANY>,
'entity_id': 'select.k11_clean_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'Quick',
})
# ---
# name: test_selects[thermostat][select.longan_link_hvac_temperature_display_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -6791,74 +6791,6 @@
'state': '234.899',
})
# ---
# name: test_sensors[switchbot_K11][sensor.k11_operational_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'stopped',
'running',
'paused',
'error',
'seeking_charger',
'charging',
'docked',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.k11_operational_state',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational state',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_state',
'unique_id': '00000000000004D2-0000000000000061-MatterNodeDevice-1-RvcOperationalState-97-4',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[switchbot_K11][sensor.k11_operational_state-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'K11+ Operational state',
'options': list([
'stopped',
'running',
'paused',
'error',
'seeking_charger',
'charging',
'docked',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.k11_operational_state',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'stopped',
})
# ---
# name: test_sensors[temperature_sensor][sensor.mock_temperature_sensor_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -1,59 +1,4 @@
# serializer version: 1
# name: test_vacuum[switchbot_K11][vacuum.k11-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'vacuum',
'entity_category': None,
'entity_id': 'vacuum.k11',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <VacuumEntityFeature: 12316>,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000061-MatterNodeDevice-1-MatterVacuumCleaner-84-1',
'unit_of_measurement': None,
})
# ---
# name: test_vacuum[switchbot_K11][vacuum.k11-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_area': None,
'current_area_name': None,
'friendly_name': 'K11+',
'selected_areas': list([
4,
3,
]),
'supported_features': <VacuumEntityFeature: 12316>,
}),
'context': <ANY>,
'entity_id': 'vacuum.k11',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---
# name: test_vacuum[vacuum_cleaner][vacuum.mock_vacuum-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -92,10 +37,7 @@
# name: test_vacuum[vacuum_cleaner][vacuum.mock_vacuum-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_area': 7,
'current_area_name': 'My Location A',
'friendly_name': 'Mock Vacuum',
'selected_areas': None,
'supported_features': <VacuumEntityFeature: 12316>,
}),
'context': <ANY>,
-166
View File
@@ -7,16 +7,6 @@ from matter_server.client.models.node import MatterNode
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.matter.const import (
SERVICE_CLEAN_AREAS,
SERVICE_GET_AREAS,
SERVICE_SELECT_AREAS,
)
from homeassistant.components.matter.vacuum import (
ATTR_CURRENT_AREA,
ATTR_CURRENT_AREA_NAME,
ATTR_SELECTED_AREAS,
)
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
@@ -147,162 +137,6 @@ async def test_vacuum_actions(
matter_client.send_device_command.reset_mock()
@pytest.mark.parametrize("node_fixture", ["switchbot_K11"])
async def test_k11_vacuum_actions(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test Matter ServiceArea cluster actions."""
# Fetch translations
await async_setup_component(hass, "homeassistant", {})
entity_id = "vacuum.k11"
state = hass.states.get(entity_id)
# test selected_areas action
assert state
selected_areas = [1, 2, 3]
await hass.services.async_call(
"matter",
SERVICE_SELECT_AREAS,
{
"entity_id": entity_id,
"areas": selected_areas,
},
blocking=True,
return_response=True,
)
assert matter_client.send_device_command.call_count == 1
assert matter_client.send_device_command.call_args == call(
node_id=matter_node.node_id,
endpoint_id=1,
command=clusters.ServiceArea.Commands.SelectAreas(newAreas=selected_areas),
)
matter_client.send_device_command.reset_mock()
# test clean_areasss action
assert state
selected_areas = [1, 2, 3]
await hass.services.async_call(
"matter",
SERVICE_CLEAN_AREAS,
{
"entity_id": entity_id,
"areas": selected_areas,
},
blocking=True,
return_response=True,
)
assert matter_client.send_device_command.call_count == 2
assert matter_client.send_device_command.call_args_list[0] == call(
node_id=matter_node.node_id,
endpoint_id=1,
command=clusters.ServiceArea.Commands.SelectAreas(newAreas=selected_areas),
)
assert matter_client.send_device_command.call_args_list[1] == call(
node_id=matter_node.node_id,
endpoint_id=1,
command=clusters.RvcRunMode.Commands.ChangeToMode(newMode=1),
)
matter_client.send_device_command.reset_mock()
# test get_areas action
response = await hass.services.async_call(
"matter",
SERVICE_GET_AREAS,
{
"entity_id": entity_id,
},
blocking=True,
return_response=True,
)
# check the response data
expected_data = {
"vacuum.k11": {
"areas": {
1: {"name": "Bedroom #3"},
2: {"name": "Stairs"},
3: {"name": "Bedroom #1"},
4: {"name": "Bedroom #2"},
5: {"name": "Corridor"},
6: {"name": "Bathroom"},
},
"maps": [],
}
}
assert response == expected_data
@pytest.mark.parametrize("node_fixture", ["switchbot_K11"])
async def test_k11_vacuum_service_area(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test Matter ServiceArea cluster attributes."""
# Fetch translations
await async_setup_component(hass, "homeassistant", {})
entity_id = "vacuum.k11"
state = hass.states.get(entity_id)
# SupportedAreas attribute ID is 2 (1/336/0)
supported_areas = [
{
"0": 1,
"1": None,
"2": {
"0": {
"0": "Bedroom #1",
"1": None,
"2": None,
},
"1": None,
},
},
{
"0": 3,
"1": None,
"2": {
"0": {
"0": "Bedroom #2",
"1": None,
"2": None,
},
"1": None,
},
},
{
"0": 4,
"1": None,
"2": {
"0": {
"0": "Bedroom #3",
"1": None,
"2": None,
},
"1": None,
},
},
]
set_node_attribute(matter_node, 1, 336, 0, supported_areas)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
selected_areas = [1, 3]
set_node_attribute(matter_node, 1, 336, 2, selected_areas)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_SELECTED_AREAS] == selected_areas
# ServiceArea.Attributes.CurrentArea (1/336/3)
set_node_attribute(matter_node, 1, 336, 3, 4)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_CURRENT_AREA] == 4
assert state.attributes[ATTR_CURRENT_AREA_NAME] == "Bedroom #3"
@pytest.mark.parametrize("node_fixture", ["vacuum_cleaner"])
async def test_vacuum_updates(
hass: HomeAssistant,