mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Catch and convert MatterError when sending device commands (#136635)
This commit is contained in:
parent
3984565084
commit
557b9d88b5
@ -49,11 +49,7 @@ class MatterCommandButton(MatterEntity, ButtonEntity):
|
||||
"""Handle the button press leveraging a Matter command."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.entity_description.command is not None
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=self.entity_description.command(),
|
||||
)
|
||||
await self.send_device_command(self.entity_description.command())
|
||||
|
||||
|
||||
# Discovery schema(s) to map Matter Attributes to HA entities
|
||||
|
@ -212,57 +212,45 @@ class MatterClimate(MatterEntity, ClimateEntity):
|
||||
matter_attribute = (
|
||||
clusters.Thermostat.Attributes.OccupiedHeatingSetpoint
|
||||
)
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
matter_attribute,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=int(target_temperature * TEMPERATURE_SCALING_FACTOR),
|
||||
matter_attribute=matter_attribute,
|
||||
)
|
||||
return
|
||||
|
||||
if target_temperature_low is not None:
|
||||
# multi setpoint control - low setpoint (heat)
|
||||
if self.target_temperature_low != target_temperature_low:
|
||||
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.Thermostat.Attributes.OccupiedHeatingSetpoint,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=int(target_temperature_low * TEMPERATURE_SCALING_FACTOR),
|
||||
matter_attribute=clusters.Thermostat.Attributes.OccupiedHeatingSetpoint,
|
||||
)
|
||||
|
||||
if target_temperature_high is not None:
|
||||
# multi setpoint control - high setpoint (cool)
|
||||
if self.target_temperature_high != target_temperature_high:
|
||||
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.Thermostat.Attributes.OccupiedCoolingSetpoint,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=int(target_temperature_high * TEMPERATURE_SCALING_FACTOR),
|
||||
matter_attribute=clusters.Thermostat.Attributes.OccupiedCoolingSetpoint,
|
||||
)
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set new target hvac mode."""
|
||||
system_mode_path = create_attribute_path_from_attribute(
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
attribute=clusters.Thermostat.Attributes.SystemMode,
|
||||
)
|
||||
|
||||
system_mode_value = HVAC_SYSTEM_MODE_MAP.get(hvac_mode)
|
||||
if system_mode_value is None:
|
||||
raise ValueError(f"Unsupported hvac mode {hvac_mode} in Matter")
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=system_mode_path,
|
||||
await self.write_attribute(
|
||||
value=system_mode_value,
|
||||
matter_attribute=clusters.Thermostat.Attributes.SystemMode,
|
||||
)
|
||||
# we need to optimistically update the attribute's value here
|
||||
# to prevent a race condition when adjusting the mode and temperature
|
||||
# in the same call
|
||||
system_mode_path = create_attribute_path_from_attribute(
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
attribute=clusters.Thermostat.Attributes.SystemMode,
|
||||
)
|
||||
self._endpoint.set_attribute_value(system_mode_path, system_mode_value)
|
||||
self._update_from_device()
|
||||
|
||||
|
@ -102,14 +102,6 @@ class MatterCover(MatterEntity, CoverEntity):
|
||||
clusters.WindowCovering.Commands.GoToTiltPercentage((100 - position) * 100)
|
||||
)
|
||||
|
||||
async def send_device_command(self, command: Any) -> None:
|
||||
"""Send device command."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=command,
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
|
@ -2,18 +2,24 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
import functools
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from typing import TYPE_CHECKING, Any, Concatenate, cast
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Objects import ClusterAttributeDescriptor, NullValue
|
||||
from matter_server.common.helpers.util import create_attribute_path
|
||||
from chip.clusters.Objects import ClusterAttributeDescriptor, ClusterCommand, NullValue
|
||||
from matter_server.common.errors import MatterError
|
||||
from matter_server.common.helpers.util import (
|
||||
create_attribute_path,
|
||||
create_attribute_path_from_attribute,
|
||||
)
|
||||
from matter_server.common.models import EventType, ServerInfoMessage
|
||||
from propcache.api import cached_property
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
@ -31,6 +37,23 @@ if TYPE_CHECKING:
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def catch_matter_error[_R, **P](
|
||||
func: Callable[Concatenate[MatterEntity, P], Coroutine[Any, Any, _R]],
|
||||
) -> Callable[Concatenate[MatterEntity, P], Coroutine[Any, Any, _R]]:
|
||||
"""Catch Matter errors and convert to Home Assistant error."""
|
||||
|
||||
@functools.wraps(func)
|
||||
async def wrapper(self: MatterEntity, *args: P.args, **kwargs: P.kwargs) -> _R:
|
||||
"""Catch Matter errors and convert to Home Assistant error."""
|
||||
try:
|
||||
return await func(self, *args, **kwargs)
|
||||
except MatterError as err:
|
||||
error_msg = str(err) or err.__class__.__name__
|
||||
raise HomeAssistantError(error_msg) from err
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MatterEntityDescription(EntityDescription):
|
||||
"""Describe the Matter entity."""
|
||||
@ -218,3 +241,38 @@ class MatterEntity(Entity):
|
||||
return create_attribute_path(
|
||||
self._endpoint.endpoint_id, attribute.cluster_id, attribute.attribute_id
|
||||
)
|
||||
|
||||
@catch_matter_error
|
||||
async def send_device_command(
|
||||
self,
|
||||
command: ClusterCommand,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Send device command on the primary attribute's endpoint."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=command,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@catch_matter_error
|
||||
async def write_attribute(
|
||||
self,
|
||||
value: Any,
|
||||
matter_attribute: type[ClusterAttributeDescriptor] | None = None,
|
||||
) -> Any:
|
||||
"""Write an attribute(value) on the primary endpoint.
|
||||
|
||||
If matter_attribute is not provided, the primary attribute of the entity is used.
|
||||
"""
|
||||
if matter_attribute is None:
|
||||
matter_attribute = self._entity_info.primary_attribute
|
||||
return await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
matter_attribute,
|
||||
),
|
||||
value=value,
|
||||
)
|
||||
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, 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,
|
||||
@ -97,24 +96,16 @@ class MatterFan(MatterEntity, FanEntity):
|
||||
# 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,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=clusters.FanControl.Enums.FanModeEnum.kOff,
|
||||
matter_attribute=clusters.FanControl.Attributes.FanMode,
|
||||
)
|
||||
|
||||
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,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=percentage,
|
||||
matter_attribute=clusters.FanControl.Attributes.PercentSetting,
|
||||
)
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
@ -128,41 +119,33 @@ class MatterFan(MatterEntity, FanEntity):
|
||||
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,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=FAN_MODE_MAP[preset_mode],
|
||||
matter_attribute=clusters.FanControl.Attributes.FanMode,
|
||||
)
|
||||
|
||||
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,
|
||||
await self.write_attribute(
|
||||
value=(
|
||||
self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.RockSupport
|
||||
)
|
||||
if oscillating
|
||||
else 0
|
||||
),
|
||||
value=self.get_matter_attribute_value(
|
||||
clusters.FanControl.Attributes.RockSupport
|
||||
)
|
||||
if oscillating
|
||||
else 0,
|
||||
matter_attribute=clusters.FanControl.Attributes.RockSetting,
|
||||
)
|
||||
|
||||
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,
|
||||
await self.write_attribute(
|
||||
value=(
|
||||
clusters.FanControl.Enums.AirflowDirectionEnum.kReverse
|
||||
if direction == DIRECTION_REVERSE
|
||||
else clusters.FanControl.Enums.AirflowDirectionEnum.kForward
|
||||
),
|
||||
value=clusters.FanControl.Enums.AirflowDirectionEnum.kReverse
|
||||
if direction == DIRECTION_REVERSE
|
||||
else clusters.FanControl.Enums.AirflowDirectionEnum.kForward,
|
||||
matter_attribute=clusters.FanControl.Attributes.AirflowDirection,
|
||||
)
|
||||
|
||||
async def _set_wind_mode(self, wind_mode: str | None) -> None:
|
||||
@ -173,13 +156,9 @@ class MatterFan(MatterEntity, FanEntity):
|
||||
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,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=wind_setting,
|
||||
matter_attribute=clusters.FanControl.Attributes.WindSetting,
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@ -282,14 +282,6 @@ class MatterLight(MatterEntity, LightEntity):
|
||||
|
||||
return ha_color_mode
|
||||
|
||||
async def send_device_command(self, command: Any) -> None:
|
||||
"""Send device command."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=command,
|
||||
)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn light on."""
|
||||
|
||||
|
@ -62,19 +62,6 @@ class MatterLock(MatterEntity, LockEntity):
|
||||
|
||||
return None
|
||||
|
||||
async def send_device_command(
|
||||
self,
|
||||
command: clusters.ClusterCommand,
|
||||
timed_request_timeout_ms: int = 1000,
|
||||
) -> None:
|
||||
"""Send a command to the device."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=command,
|
||||
timed_request_timeout_ms=timed_request_timeout_ms,
|
||||
)
|
||||
|
||||
async def async_lock(self, **kwargs: Any) -> None:
|
||||
"""Lock the lock with pin if needed."""
|
||||
if not self._attr_is_locked:
|
||||
@ -89,7 +76,8 @@ class MatterLock(MatterEntity, LockEntity):
|
||||
code: str | None = kwargs.get(ATTR_CODE)
|
||||
code_bytes = code.encode() if code else None
|
||||
await self.send_device_command(
|
||||
command=clusters.DoorLock.Commands.LockDoor(code_bytes)
|
||||
command=clusters.DoorLock.Commands.LockDoor(code_bytes),
|
||||
timed_request_timeout_ms=1000,
|
||||
)
|
||||
|
||||
async def async_unlock(self, **kwargs: Any) -> None:
|
||||
@ -110,11 +98,13 @@ class MatterLock(MatterEntity, LockEntity):
|
||||
# the unlock command should unbolt only on the unlock command
|
||||
# and unlatch on the HA 'open' command.
|
||||
await self.send_device_command(
|
||||
command=clusters.DoorLock.Commands.UnboltDoor(code_bytes)
|
||||
command=clusters.DoorLock.Commands.UnboltDoor(code_bytes),
|
||||
timed_request_timeout_ms=1000,
|
||||
)
|
||||
else:
|
||||
await self.send_device_command(
|
||||
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes)
|
||||
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes),
|
||||
timed_request_timeout_ms=1000,
|
||||
)
|
||||
|
||||
async def async_open(self, **kwargs: Any) -> None:
|
||||
@ -130,7 +120,8 @@ class MatterLock(MatterEntity, LockEntity):
|
||||
code: str | None = kwargs.get(ATTR_CODE)
|
||||
code_bytes = code.encode() if code else None
|
||||
await self.send_device_command(
|
||||
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes)
|
||||
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes),
|
||||
timed_request_timeout_ms=1000,
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@ -6,7 +6,6 @@ from dataclasses import dataclass
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from matter_server.common import custom_clusters
|
||||
from matter_server.common.helpers.util import create_attribute_path_from_attribute
|
||||
|
||||
from homeassistant.components.number import (
|
||||
NumberDeviceClass,
|
||||
@ -52,16 +51,10 @@ class MatterNumber(MatterEntity, NumberEntity):
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Update the current value."""
|
||||
matter_attribute = self._entity_info.primary_attribute
|
||||
sendvalue = int(value)
|
||||
if value_convert := self.entity_description.ha_to_native_value:
|
||||
sendvalue = value_convert(value)
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
matter_attribute,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=sendvalue,
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, cast
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.ClusterObjects import ClusterAttributeDescriptor, ClusterCommand
|
||||
from chip.clusters.Types import Nullable
|
||||
from matter_server.common.helpers.util import create_attribute_path_from_attribute
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -70,11 +69,7 @@ class MatterAttributeSelectEntity(MatterEntity, SelectEntity):
|
||||
value_convert = self.entity_description.ha_to_native_value
|
||||
if TYPE_CHECKING:
|
||||
assert value_convert is not 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, self._entity_info.primary_attribute
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=value_convert(option),
|
||||
)
|
||||
|
||||
@ -101,10 +96,8 @@ class MatterModeSelectEntity(MatterAttributeSelectEntity):
|
||||
for mode in cluster.supportedModes:
|
||||
if mode.label != option:
|
||||
continue
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=cluster.Commands.ChangeToMode(newMode=mode.mode),
|
||||
await self.send_device_command(
|
||||
cluster.Commands.ChangeToMode(newMode=mode.mode),
|
||||
)
|
||||
break
|
||||
|
||||
@ -132,10 +125,8 @@ class MatterListSelectEntity(MatterEntity, SelectEntity):
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
option_id = self._attr_options.index(option)
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=self.entity_description.command(option_id),
|
||||
await self.send_device_command(
|
||||
self.entity_description.command(option_id),
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@ -7,7 +7,6 @@ from typing import Any
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from matter_server.client.models import device_types
|
||||
from matter_server.common.helpers.util import create_attribute_path_from_attribute
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
@ -41,18 +40,14 @@ class MatterSwitch(MatterEntity, SwitchEntity):
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn switch on."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=clusters.OnOff.Commands.On(),
|
||||
await self.send_device_command(
|
||||
clusters.OnOff.Commands.On(),
|
||||
)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn switch off."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=clusters.OnOff.Commands.Off(),
|
||||
await self.send_device_command(
|
||||
clusters.OnOff.Commands.Off(),
|
||||
)
|
||||
|
||||
@callback
|
||||
@ -77,15 +72,9 @@ class MatterNumericSwitch(MatterSwitch):
|
||||
|
||||
async def _async_set_native_value(self, value: bool) -> None:
|
||||
"""Update the current value."""
|
||||
matter_attribute = self._entity_info.primary_attribute
|
||||
if value_convert := self.entity_description.ha_to_native_value:
|
||||
send_value = value_convert(value)
|
||||
await self.matter_client.write_attribute(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
attribute_path=create_attribute_path_from_attribute(
|
||||
self._endpoint.endpoint_id,
|
||||
matter_attribute,
|
||||
),
|
||||
await self.write_attribute(
|
||||
value=send_value,
|
||||
)
|
||||
|
||||
|
@ -69,15 +69,15 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
|
||||
|
||||
async def async_stop(self, **kwargs: Any) -> None:
|
||||
"""Stop the vacuum cleaner."""
|
||||
await self._send_device_command(clusters.OperationalState.Commands.Stop())
|
||||
await self.send_device_command(clusters.OperationalState.Commands.Stop())
|
||||
|
||||
async def async_return_to_base(self, **kwargs: Any) -> None:
|
||||
"""Set the vacuum cleaner to return to the dock."""
|
||||
await self._send_device_command(clusters.RvcOperationalState.Commands.GoHome())
|
||||
await self.send_device_command(clusters.RvcOperationalState.Commands.GoHome())
|
||||
|
||||
async def async_locate(self, **kwargs: Any) -> None:
|
||||
"""Locate the vacuum cleaner."""
|
||||
await self._send_device_command(clusters.Identify.Commands.Identify())
|
||||
await self.send_device_command(clusters.Identify.Commands.Identify())
|
||||
|
||||
async def async_start(self) -> None:
|
||||
"""Start or resume the cleaning task."""
|
||||
@ -87,26 +87,15 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
|
||||
clusters.RvcOperationalState.Commands.Resume.command_id
|
||||
in self._last_accepted_commands
|
||||
):
|
||||
await self._send_device_command(
|
||||
await self.send_device_command(
|
||||
clusters.RvcOperationalState.Commands.Resume()
|
||||
)
|
||||
else:
|
||||
await self._send_device_command(clusters.OperationalState.Commands.Start())
|
||||
await self.send_device_command(clusters.OperationalState.Commands.Start())
|
||||
|
||||
async def async_pause(self) -> None:
|
||||
"""Pause the cleaning task."""
|
||||
await self._send_device_command(clusters.OperationalState.Commands.Pause())
|
||||
|
||||
async def _send_device_command(
|
||||
self,
|
||||
command: clusters.ClusterCommand,
|
||||
) -> None:
|
||||
"""Send a command to the device."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=command,
|
||||
)
|
||||
await self.send_device_command(clusters.OperationalState.Commands.Pause())
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
|
@ -42,17 +42,6 @@ class MatterValve(MatterEntity, ValveEntity):
|
||||
entity_description: ValveEntityDescription
|
||||
_platform_translation_key = "valve"
|
||||
|
||||
async def send_device_command(
|
||||
self,
|
||||
command: clusters.ClusterCommand,
|
||||
) -> None:
|
||||
"""Send a command to the device."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._endpoint.node.node_id,
|
||||
endpoint_id=self._endpoint.endpoint_id,
|
||||
command=command,
|
||||
)
|
||||
|
||||
async def async_open_valve(self) -> None:
|
||||
"""Open the valve."""
|
||||
await self.send_device_command(ValveConfigurationAndControl.Commands.Open())
|
||||
|
@ -4,12 +4,14 @@ from unittest.mock import MagicMock, call
|
||||
|
||||
from matter_server.client.models.node import MatterNode
|
||||
from matter_server.common import custom_clusters
|
||||
from matter_server.common.errors import MatterError
|
||||
from matter_server.common.helpers.util import create_attribute_path_from_attribute
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .common import (
|
||||
@ -97,3 +99,25 @@ async def test_eve_weather_sensor_altitude(
|
||||
),
|
||||
value=500,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["dimmable_light"])
|
||||
async def test_matter_exception_on_write_attribute(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test if a MatterError gets converted to HomeAssistantError by using a dimmable_light fixture."""
|
||||
state = hass.states.get("number.mock_dimmable_light_on_level")
|
||||
assert state
|
||||
matter_client.write_attribute.side_effect = MatterError("Boom")
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
"number",
|
||||
"set_value",
|
||||
{
|
||||
"entity_id": "number.mock_dimmable_light_on_level",
|
||||
"value": 500,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -4,12 +4,14 @@ from unittest.mock import MagicMock, call
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from matter_server.client.models.node import MatterNode
|
||||
from matter_server.common.errors import MatterError
|
||||
from matter_server.common.helpers.util import create_attribute_path_from_attribute
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .common import (
|
||||
@ -165,3 +167,24 @@ async def test_numeric_switch(
|
||||
),
|
||||
value=0,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["on_off_plugin_unit"])
|
||||
async def test_matter_exception_on_command(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test if a MatterError gets converted to HomeAssistantError by using a switch fixture."""
|
||||
state = hass.states.get("switch.mock_onoffpluginunit")
|
||||
assert state
|
||||
matter_client.send_device_command.side_effect = MatterError("Boom")
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": "switch.mock_onoffpluginunit",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user