Home Assistant Unofficial Reference 2024.12.1
vacuum.py
Go to the documentation of this file.
1 """Matter vacuum platform."""
2 
3 from __future__ import annotations
4 
5 from enum import IntEnum
6 from typing import TYPE_CHECKING, Any
7 
8 from chip.clusters import Objects as clusters
9 from matter_server.client.models import device_types
10 
12  STATE_CLEANING,
13  STATE_DOCKED,
14  STATE_ERROR,
15  STATE_RETURNING,
16  StateVacuumEntity,
17  StateVacuumEntityDescription,
18  VacuumEntityFeature,
19 )
20 from homeassistant.config_entries import ConfigEntry
21 from homeassistant.const import STATE_IDLE, Platform
22 from homeassistant.core import HomeAssistant, callback
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 
25 from .entity import MatterEntity
26 from .helpers import get_matter
27 from .models import MatterDiscoverySchema
28 
29 
30 class OperationalState(IntEnum):
31  """Operational State of the vacuum cleaner.
32 
33  Combination of generic OperationalState and RvcOperationalState.
34  """
35 
36  NO_ERROR = 0x00
37  UNABLE_TO_START_OR_RESUME = 0x01
38  UNABLE_TO_COMPLETE_OPERATION = 0x02
39  COMMAND_INVALID_IN_STATE = 0x03
40  SEEKING_CHARGER = 0x40
41  CHARGING = 0x41
42  DOCKED = 0x42
43 
44 
45 class ModeTag(IntEnum):
46  """Enum with available ModeTag values."""
47 
48  IDLE = 0x4000 # 16384 decimal
49  CLEANING = 0x4001 # 16385 decimal
50  MAPPING = 0x4002 # 16386 decimal
51 
52 
54  hass: HomeAssistant,
55  config_entry: ConfigEntry,
56  async_add_entities: AddEntitiesCallback,
57 ) -> None:
58  """Set up Matter vacuum platform from Config Entry."""
59  matter = get_matter(hass)
60  matter.register_platform_handler(Platform.VACUUM, async_add_entities)
61 
62 
64  """Representation of a Matter Vacuum cleaner entity."""
65 
66  _last_accepted_commands: list[int] | None = None
67  _supported_run_modes: (
68  dict[int, clusters.RvcCleanMode.Structs.ModeOptionStruct] | None
69  ) = None
70  entity_description: StateVacuumEntityDescription
71  _platform_translation_key = "vacuum"
72 
73  async def async_stop(self, **kwargs: Any) -> None:
74  """Stop the vacuum cleaner."""
75  await self._send_device_command_send_device_command(clusters.OperationalState.Commands.Stop())
76 
77  async def async_return_to_base(self, **kwargs: Any) -> None:
78  """Set the vacuum cleaner to return to the dock."""
79  await self._send_device_command_send_device_command(clusters.RvcOperationalState.Commands.GoHome())
80 
81  async def async_locate(self, **kwargs: Any) -> None:
82  """Locate the vacuum cleaner."""
83  await self._send_device_command_send_device_command(clusters.Identify.Commands.Identify())
84 
85  async def async_start(self) -> None:
86  """Start or resume the cleaning task."""
87  if TYPE_CHECKING:
88  assert self._last_accepted_commands_last_accepted_commands is not None
89  if (
90  clusters.RvcOperationalState.Commands.Resume.command_id
91  in self._last_accepted_commands_last_accepted_commands
92  ):
93  await self._send_device_command_send_device_command(
94  clusters.RvcOperationalState.Commands.Resume()
95  )
96  else:
97  await self._send_device_command_send_device_command(clusters.OperationalState.Commands.Start())
98 
99  async def async_pause(self) -> None:
100  """Pause the cleaning task."""
101  await self._send_device_command_send_device_command(clusters.OperationalState.Commands.Pause())
102 
104  self,
105  command: clusters.ClusterCommand,
106  ) -> None:
107  """Send a command to the device."""
108  await self.matter_clientmatter_client.send_device_command(
109  node_id=self._endpoint_endpoint.node.node_id,
110  endpoint_id=self._endpoint_endpoint.endpoint_id,
111  command=command,
112  )
113 
114  @callback
115  def _update_from_device(self) -> None:
116  """Update from device."""
117  self._calculate_features_calculate_features()
118  # optional battery level
119  if VacuumEntityFeature.BATTERY & self._attr_supported_features_attr_supported_features:
120  self._attr_battery_level_attr_battery_level = self.get_matter_attribute_valueget_matter_attribute_value(
121  clusters.PowerSource.Attributes.BatPercentRemaining
122  )
123  # derive state from the run mode + operational state
124  run_mode_raw: int = self.get_matter_attribute_valueget_matter_attribute_value(
125  clusters.RvcRunMode.Attributes.CurrentMode
126  )
127  operational_state: int = self.get_matter_attribute_valueget_matter_attribute_value(
128  clusters.RvcOperationalState.Attributes.OperationalState
129  )
130  state: str | None = None
131  if TYPE_CHECKING:
132  assert self._supported_run_modes_supported_run_modes is not None
133  if operational_state in (OperationalState.CHARGING, OperationalState.DOCKED):
134  state = STATE_DOCKED
135  elif operational_state == OperationalState.SEEKING_CHARGER:
136  state = STATE_RETURNING
137  elif operational_state in (
138  OperationalState.UNABLE_TO_COMPLETE_OPERATION,
139  OperationalState.UNABLE_TO_START_OR_RESUME,
140  ):
141  state = STATE_ERROR
142  elif (run_mode := self._supported_run_modes_supported_run_modes.get(run_mode_raw)) is not None:
143  tags = {x.value for x in run_mode.modeTags}
144  if ModeTag.CLEANING in tags:
145  state = STATE_CLEANING
146  elif ModeTag.IDLE in tags:
147  state = STATE_IDLE
148  self._attr_state_attr_state = state
149 
150  @callback
151  def _calculate_features(self) -> None:
152  """Calculate features for HA Vacuum platform."""
153  accepted_operational_commands: list[int] = self.get_matter_attribute_valueget_matter_attribute_value(
154  clusters.RvcOperationalState.Attributes.AcceptedCommandList
155  )
156  # in principle the feature set should not change, except for the accepted commands
157  if self._last_accepted_commands_last_accepted_commands == accepted_operational_commands:
158  return
159  self._last_accepted_commands_last_accepted_commands = accepted_operational_commands
160  supported_features: VacuumEntityFeature = VacuumEntityFeature(0)
161  supported_features |= VacuumEntityFeature.STATE
162  # optional battery attribute = battery feature
163  if self.get_matter_attribute_valueget_matter_attribute_value(
164  clusters.PowerSource.Attributes.BatPercentRemaining
165  ):
166  supported_features |= VacuumEntityFeature.BATTERY
167  # optional identify cluster = locate feature (value must be not None or 0)
168  if self.get_matter_attribute_valueget_matter_attribute_value(clusters.Identify.Attributes.IdentifyType):
169  supported_features |= VacuumEntityFeature.LOCATE
170  # create a map of supported run modes
171  run_modes: list[clusters.RvcCleanMode.Structs.ModeOptionStruct] = (
172  self.get_matter_attribute_valueget_matter_attribute_value(
173  clusters.RvcRunMode.Attributes.SupportedModes
174  )
175  )
176  self._supported_run_modes_supported_run_modes = {mode.mode: mode for mode in run_modes}
177  # map operational state commands to vacuum features
178  if (
179  clusters.RvcOperationalState.Commands.Pause.command_id
180  in accepted_operational_commands
181  ):
182  supported_features |= VacuumEntityFeature.PAUSE
183  if (
184  clusters.OperationalState.Commands.Stop.command_id
185  in accepted_operational_commands
186  ):
187  supported_features |= VacuumEntityFeature.STOP
188  if (
189  clusters.OperationalState.Commands.Start.command_id
190  in accepted_operational_commands
191  ):
192  # note that start has been replaced by resume in rev2 of the spec
193  supported_features |= VacuumEntityFeature.START
194  if (
195  clusters.RvcOperationalState.Commands.Resume.command_id
196  in accepted_operational_commands
197  ):
198  supported_features |= VacuumEntityFeature.START
199  if (
200  clusters.RvcOperationalState.Commands.GoHome.command_id
201  in accepted_operational_commands
202  ):
203  supported_features |= VacuumEntityFeature.RETURN_HOME
204 
205  self._attr_supported_features_attr_supported_features = supported_features
206 
207 
208 # Discovery schema(s) to map Matter Attributes to HA entities
209 DISCOVERY_SCHEMAS = [
211  platform=Platform.VACUUM,
212  entity_description=StateVacuumEntityDescription(
213  key="MatterVacuumCleaner", name=None
214  ),
215  entity_class=MatterVacuum,
216  required_attributes=(
217  clusters.RvcRunMode.Attributes.CurrentMode,
218  clusters.RvcOperationalState.Attributes.CurrentPhase,
219  ),
220  optional_attributes=(
221  clusters.RvcCleanMode.Attributes.CurrentMode,
222  clusters.PowerSource.Attributes.BatPercentRemaining,
223  ),
224  device_type=(device_types.RoboticVacuumCleaner,),
225  ),
226 ]
Any get_matter_attribute_value(self, type[ClusterAttributeDescriptor] attribute, bool null_as_none=True)
Definition: entity.py:206
None async_return_to_base(self, **Any kwargs)
Definition: vacuum.py:77
None _send_device_command(self, clusters.ClusterCommand command)
Definition: vacuum.py:106
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
MatterAdapter get_matter(HomeAssistant hass)
Definition: helpers.py:35
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: vacuum.py:57