mirror of
https://github.com/home-assistant/core.git
synced 2026-05-04 11:54:35 +02:00
This commit is contained in:
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user