python-roborock 5.14.2__tar.gz → 5.15.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {python_roborock-5.14.2 → python_roborock-5.15.1}/PKG-INFO +11 -3
  2. {python_roborock-5.14.2 → python_roborock-5.15.1}/README.md +5 -0
  3. {python_roborock-5.14.2 → python_roborock-5.15.1}/pyproject.toml +13 -5
  4. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/cli.py +12 -6
  5. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/__init__.py +1 -1
  6. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/rooms.py +20 -4
  7. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/web_api.py +31 -0
  8. {python_roborock-5.14.2 → python_roborock-5.15.1}/.gitignore +0 -0
  9. {python_roborock-5.14.2 → python_roborock-5.15.1}/LICENSE +0 -0
  10. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/__init__.py +0 -0
  11. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/broadcast_protocol.py +0 -0
  12. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/callbacks.py +0 -0
  13. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/const.py +0 -0
  14. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/__init__.py +0 -0
  15. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/b01_q10/__init__.py +0 -0
  16. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  17. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  18. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/b01_q7/__init__.py +0 -0
  19. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  20. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  21. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/code_mappings.py +0 -0
  22. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/containers.py +0 -0
  23. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/dyad/__init__.py +0 -0
  24. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  25. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/dyad/dyad_containers.py +0 -0
  26. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/v1/__init__.py +0 -0
  27. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/v1/v1_clean_modes.py +0 -0
  28. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/v1/v1_code_mappings.py +0 -0
  29. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/v1/v1_containers.py +0 -0
  30. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/zeo/__init__.py +0 -0
  31. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  32. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/data/zeo/zeo_containers.py +0 -0
  33. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/device_features.py +0 -0
  34. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/README.md +0 -0
  35. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/__init__.py +0 -0
  36. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/cache.py +0 -0
  37. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/device.py +0 -0
  38. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/device_manager.py +0 -0
  39. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/file_cache.py +0 -0
  40. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/rpc/__init__.py +0 -0
  41. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/rpc/a01_channel.py +0 -0
  42. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/rpc/b01_q10_channel.py +0 -0
  43. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/rpc/b01_q7_channel.py +0 -0
  44. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/rpc/v1_channel.py +0 -0
  45. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/__init__.py +0 -0
  46. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/a01/__init__.py +0 -0
  47. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/__init__.py +0 -0
  48. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q10/__init__.py +0 -0
  49. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q10/command.py +0 -0
  50. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q10/remote.py +0 -0
  51. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q10/status.py +0 -0
  52. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
  53. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q7/__init__.py +0 -0
  54. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
  55. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q7/map.py +0 -0
  56. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/b01/q7/map_content.py +0 -0
  57. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/common.py +0 -0
  58. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/traits_mixin.py +0 -0
  59. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/child_lock.py +0 -0
  60. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/clean_summary.py +0 -0
  61. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/command.py +0 -0
  62. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/common.py +0 -0
  63. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/consumeable.py +0 -0
  64. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/device_features.py +0 -0
  65. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  66. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  67. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  68. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/home.py +0 -0
  69. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/led_status.py +0 -0
  70. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/map_content.py +0 -0
  71. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/maps.py +0 -0
  72. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/network_info.py +0 -0
  73. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/routines.py +0 -0
  74. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  75. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/status.py +0 -0
  76. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  77. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/volume.py +0 -0
  78. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  79. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/transport/__init__.py +0 -0
  80. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/transport/channel.py +0 -0
  81. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/transport/local_channel.py +0 -0
  82. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/devices/transport/mqtt_channel.py +0 -0
  83. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/diagnostics.py +0 -0
  84. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/exceptions.py +0 -0
  85. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/map/__init__.py +0 -0
  86. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/map/b01_map_parser.py +0 -0
  87. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/map/map_parser.py +0 -0
  88. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/map/proto/__init__.py +0 -0
  89. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/map/proto/b01_scmap.proto +0 -0
  90. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/map/proto/b01_scmap_pb2.py +0 -0
  91. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/mqtt/__init__.py +0 -0
  92. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/mqtt/health_manager.py +0 -0
  93. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/mqtt/roborock_session.py +0 -0
  94. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/mqtt/session.py +0 -0
  95. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/protocol.py +0 -0
  96. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/protocols/__init__.py +0 -0
  97. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/protocols/a01_protocol.py +0 -0
  98. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/protocols/b01_q10_protocol.py +0 -0
  99. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/protocols/b01_q7_protocol.py +0 -0
  100. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/protocols/v1_protocol.py +0 -0
  101. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/py.typed +0 -0
  102. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/roborock_message.py +0 -0
  103. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/roborock_typing.py +0 -0
  104. {python_roborock-5.14.2 → python_roborock-5.15.1}/roborock/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 5.14.2
3
+ Version: 5.15.1
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Project-URL: Repository, https://github.com/python-roborock/python-roborock
6
6
  Project-URL: Documentation, https://python-roborock.readthedocs.io/
@@ -17,8 +17,6 @@ Classifier: Topic :: Software Development :: Libraries
17
17
  Requires-Python: <4,>=3.11
18
18
  Requires-Dist: aiohttp<4,>=3.8.2
19
19
  Requires-Dist: aiomqtt<3,>=2.5.0
20
- Requires-Dist: click-shell~=2.1
21
- Requires-Dist: click>=8
22
20
  Requires-Dist: construct<3,>=2.10.57
23
21
  Requires-Dist: paho-mqtt<3.0.0,>=1.6.1
24
22
  Requires-Dist: protobuf<8,>=6.31.1
@@ -26,6 +24,11 @@ Requires-Dist: pycryptodomex~=3.18; sys_platform == 'darwin'
26
24
  Requires-Dist: pycryptodome~=3.18
27
25
  Requires-Dist: pyrate-limiter<5,>=4.0.0
28
26
  Requires-Dist: vacuum-map-parser-roborock
27
+ Provides-Extra: cli
28
+ Requires-Dist: click-shell~=2.1; extra == 'cli'
29
+ Requires-Dist: click>=8; extra == 'cli'
30
+ Requires-Dist: pyshark<0.7,>=0.6; extra == 'cli'
31
+ Requires-Dist: pyyaml>=6.0.3; extra == 'cli'
29
32
  Description-Content-Type: text/markdown
30
33
 
31
34
  # Roborock
@@ -50,6 +53,11 @@ Install this via pip (or your favourite package manager):
50
53
 
51
54
  `pip install python-roborock`
52
55
 
56
+ To use the `roborock` command line tool, install the `cli` extra, which pulls in
57
+ its additional dependencies (click, pyyaml, pyshark):
58
+
59
+ `pip install python-roborock[cli]`
60
+
53
61
  ## Example Usage
54
62
 
55
63
  See [examples/example.py](examples/example.py) for a more full featured example,
@@ -20,6 +20,11 @@ Install this via pip (or your favourite package manager):
20
20
 
21
21
  `pip install python-roborock`
22
22
 
23
+ To use the `roborock` command line tool, install the `cli` extra, which pulls in
24
+ its additional dependencies (click, pyyaml, pyshark):
25
+
26
+ `pip install python-roborock[cli]`
27
+
23
28
  ## Example Usage
24
29
 
25
30
  See [examples/example.py](examples/example.py) for a more full featured example,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-roborock"
3
- version = "5.14.2"
3
+ version = "5.15.1"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
6
6
  requires-python = ">=3.11, <4"
@@ -19,7 +19,6 @@ classifiers = [
19
19
  "Topic :: Software Development :: Libraries",
20
20
  ]
21
21
  dependencies = [
22
- "click>=8",
23
22
  "aiohttp>=3.8.2,<4",
24
23
  "pycryptodome~=3.18",
25
24
  "pycryptodomex~=3.18 ; sys_platform == 'darwin'",
@@ -29,7 +28,16 @@ dependencies = [
29
28
  "vacuum-map-parser-roborock",
30
29
  "pyrate-limiter>=4.0.0,<5",
31
30
  "aiomqtt>=2.5.0,<3",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ # Dependencies required by the `roborock` command line tool (roborock.cli).
35
+ # Install with: pip install python-roborock[cli]
36
+ cli = [
37
+ "click>=8",
32
38
  "click-shell~=2.1",
39
+ "pyyaml>=6.0.3",
40
+ "pyshark>=0.6,<0.7",
33
41
  ]
34
42
 
35
43
  [project.urls]
@@ -41,20 +49,20 @@ roborock = "roborock.cli:main"
41
49
 
42
50
  [dependency-groups]
43
51
  dev = [
52
+ # Pull in the CLI dependencies so maintainers can run the `roborock`
53
+ # command and pdoc can import roborock.cli for docs generation.
54
+ "python-roborock[cli]",
44
55
  "pytest-asyncio>=1.1.0",
45
56
  "pytest",
46
57
  "pre-commit>=3.5,<5.0",
47
58
  "mypy",
48
59
  "ruff==0.14.11",
49
60
  "codespell",
50
- "pyshark>=0.6,<0.7",
51
61
  "aioresponses>=0.7.7,<0.8",
52
62
  "freezegun>=1.5.1,<2",
53
63
  "pytest-timeout>=2.3.1,<3",
54
64
  "syrupy>=4.9.1,<6",
55
65
  "pdoc>=15.0.4,<17",
56
- "pyyaml>=6.0.3",
57
- "pyshark>=0.6",
58
66
  "pytest-cov>=7.0.0",
59
67
  ]
60
68
 
@@ -34,12 +34,18 @@ from dataclasses import asdict, dataclass
34
34
  from pathlib import Path
35
35
  from typing import Any, cast
36
36
 
37
- import click
38
- import click_shell
39
- import yaml
40
- from pyshark import FileCapture # type: ignore
41
- from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException # type: ignore
42
- from pyshark.packet.packet import Packet # type: ignore
37
+ try:
38
+ import click
39
+ import click_shell
40
+ import yaml
41
+ from pyshark import FileCapture # type: ignore
42
+ from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException # type: ignore
43
+ from pyshark.packet.packet import Packet # type: ignore
44
+ except ImportError as err:
45
+ raise SystemExit(
46
+ f"The 'roborock' command line tool requires extra dependencies that are not installed ({err.name}).\n"
47
+ "Install them with:\n\n pip install python-roborock[cli]\n"
48
+ ) from err
43
49
 
44
50
  from roborock import RoborockCommand
45
51
  from roborock.data import RoborockBase, UserData
@@ -199,7 +199,7 @@ class PropertiesApi(Trait):
199
199
  self.device_features = DeviceFeaturesTrait(product, self._device_cache)
200
200
  self.status = StatusTrait(self.device_features, region=self._region)
201
201
  self.consumables = ConsumableTrait()
202
- self.rooms = RoomsTrait(home_data, web_api)
202
+ self.rooms = RoomsTrait(home_data, device_uid, web_api)
203
203
  self.maps = MapsTrait(self.status)
204
204
  self.map_content = MapContentTrait(map_parser_config)
205
205
  self.home = HomeTrait(self.status, self.maps, self.map_content, self.rooms, self._device_cache)
@@ -84,12 +84,21 @@ class RoomsTrait(Rooms, common.V1TraitMixin):
84
84
  command = RoborockCommand.GET_ROOM_MAPPING
85
85
  converter = RoomsConverter()
86
86
 
87
- def __init__(self, home_data: HomeData, web_api: UserWebApiClient) -> None:
87
+ def __init__(self, home_data: HomeData, device_uid: str, web_api: UserWebApiClient) -> None:
88
88
  """Initialize the RoomsTrait."""
89
89
  super().__init__()
90
90
  self._home_data = home_data
91
+ self._device_uid = device_uid
92
+ self._shared_device_uid = next(
93
+ (device.duid for device in home_data.received_devices if device.duid == device_uid), None
94
+ )
91
95
  self._web_api = web_api
92
96
  self._discovered_iot_ids: set[str] = set()
97
+ self._room_names: dict[str, str] = dict(home_data.rooms_name_map)
98
+
99
+ @property
100
+ def _room_name_map(self) -> dict[str, str]:
101
+ return self._room_names
93
102
 
94
103
  async def refresh(self) -> None:
95
104
  """Refresh room mappings and backfill unknown room names from the web API."""
@@ -104,12 +113,14 @@ class RoomsTrait(Rooms, common.V1TraitMixin):
104
113
 
105
114
  segment_map = RoomsConverter.extract_segment_map(response)
106
115
  # Track all iot ids seen before. Refresh the room list when new ids are found.
107
- new_iot_ids = set(segment_map.values()) - set(self._home_data.rooms_map.keys())
116
+ new_iot_ids = set(segment_map.values()) - set(self._room_name_map.keys())
108
117
  if new_iot_ids - self._discovered_iot_ids:
109
118
  _LOGGER.debug("Refreshing room list to discover new room names")
110
119
  if updated_rooms := await self._refresh_rooms():
111
120
  _LOGGER.debug("Updating rooms: %s", list(updated_rooms))
112
- self._home_data.rooms = updated_rooms
121
+ self._room_names = {room.iot_id: room.name for room in updated_rooms}
122
+ if self._shared_device_uid is None:
123
+ self._home_data.rooms = updated_rooms
113
124
  self._discovered_iot_ids.update(new_iot_ids)
114
125
  try:
115
126
  rooms = self.converter.convert(response)
@@ -121,12 +132,17 @@ class RoomsTrait(Rooms, common.V1TraitMixin):
121
132
  inner_error=err,
122
133
  ) from err
123
134
 
124
- rooms = rooms.with_room_names(self._home_data.rooms_name_map)
135
+ rooms = rooms.with_room_names(self._room_name_map)
125
136
  common.merge_trait_values(self, rooms)
126
137
 
127
138
  async def _refresh_rooms(self) -> list[HomeDataRoom]:
128
139
  """Fetch the latest rooms from the web API."""
129
140
  try:
141
+ if self._shared_device_uid is not None:
142
+ rooms_by_id = {room.iot_id: room for room in self._home_data.rooms}
143
+ shared_rooms = await self._web_api.get_shared_device_rooms(self._shared_device_uid)
144
+ rooms_by_id.update({room.iot_id: room for room in shared_rooms})
145
+ return list(rooms_by_id.values())
130
146
  return await self._web_api.get_rooms()
131
147
  except Exception:
132
148
  _LOGGER.debug("Failed to fetch rooms from web API", exc_info=True)
@@ -558,6 +558,33 @@ class RoborockApiClient:
558
558
  else:
559
559
  raise RoborockException("home_response result was an unexpected type")
560
560
 
561
+ async def get_shared_device_rooms(self, user_data: UserData, device_id: str) -> list[HomeDataRoom]:
562
+ """Fetch room names for a shared (received) device."""
563
+ rriot = user_data.rriot
564
+ if rriot is None:
565
+ raise RoborockException("rriot is none")
566
+ if rriot.r.a is None:
567
+ raise RoborockException("Missing field 'a' in rriot reference")
568
+ path = f"/user/deviceshare/query/{device_id}/rooms"
569
+ room_request = PreparedRequest(
570
+ rriot.r.a,
571
+ self.session,
572
+ {"Authorization": _get_hawk_authentication(rriot, path)},
573
+ )
574
+ room_response = await room_request.request("get", path)
575
+ if not room_response.get("success"):
576
+ raise RoborockException(room_response)
577
+ rooms = room_response.get("result")
578
+ if isinstance(rooms, list):
579
+ output_list = []
580
+ for room in rooms:
581
+ normalized_room = room
582
+ if isinstance(room, dict) and "id" not in room and "roomId" in room:
583
+ normalized_room = {**room, "id": room["roomId"]}
584
+ output_list.append(HomeDataRoom.from_dict(normalized_room))
585
+ return output_list
586
+ raise RoborockException("get_shared_device_rooms result was an unexpected type")
587
+
561
588
  async def get_scenes(self, user_data: UserData, device_id: str) -> list[HomeDataScene]:
562
589
  rriot = user_data.rriot
563
590
  if rriot is None:
@@ -775,6 +802,10 @@ class UserWebApiClient:
775
802
  self._unauthorized_hook()
776
803
  raise
777
804
 
805
+ async def get_shared_device_rooms(self, device_id: str) -> list[HomeDataRoom]:
806
+ """Fetch shared-device rooms using the API client."""
807
+ return await self._web_api.get_shared_device_rooms(self._user_data, device_id)
808
+
778
809
  async def execute_routine(self, scene_id: int) -> None:
779
810
  """Execute a specific routine (scene) by its ID."""
780
811
  try: