python-roborock 3.3.3__tar.gz → 3.5.0__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 (93) hide show
  1. {python_roborock-3.3.3 → python_roborock-3.5.0}/PKG-INFO +1 -1
  2. {python_roborock-3.3.3 → python_roborock-3.5.0}/pyproject.toml +1 -1
  3. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/cli.py +11 -8
  4. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/cache.py +2 -2
  5. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/device_manager.py +51 -27
  6. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/home.py +24 -24
  7. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/web_api.py +18 -0
  8. {python_roborock-3.3.3 → python_roborock-3.5.0}/.gitignore +0 -0
  9. {python_roborock-3.3.3 → python_roborock-3.5.0}/LICENSE +0 -0
  10. {python_roborock-3.3.3 → python_roborock-3.5.0}/README.md +0 -0
  11. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/__init__.py +0 -0
  12. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/api.py +0 -0
  13. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/broadcast_protocol.py +0 -0
  14. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/callbacks.py +0 -0
  15. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/cloud_api.py +0 -0
  16. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/command_cache.py +0 -0
  17. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/const.py +0 -0
  18. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/__init__.py +0 -0
  19. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/b01_q10/__init__.py +0 -0
  20. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  21. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  22. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/b01_q7/__init__.py +0 -0
  23. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  24. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  25. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/code_mappings.py +0 -0
  26. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/containers.py +0 -0
  27. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/dyad/__init__.py +0 -0
  28. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  29. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/dyad/dyad_containers.py +0 -0
  30. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/v1/__init__.py +0 -0
  31. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/v1/v1_clean_modes.py +0 -0
  32. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/v1/v1_code_mappings.py +0 -0
  33. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/v1/v1_containers.py +0 -0
  34. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/zeo/__init__.py +0 -0
  35. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  36. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/data/zeo/zeo_containers.py +0 -0
  37. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/device_features.py +0 -0
  38. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/README.md +0 -0
  39. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/__init__.py +0 -0
  40. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/a01_channel.py +0 -0
  41. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/b01_channel.py +0 -0
  42. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/channel.py +0 -0
  43. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/device.py +0 -0
  44. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/local_channel.py +0 -0
  45. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/mqtt_channel.py +0 -0
  46. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/__init__.py +0 -0
  47. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/a01/__init__.py +0 -0
  48. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/b01/__init__.py +0 -0
  49. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/traits_mixin.py +0 -0
  50. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/__init__.py +0 -0
  51. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/child_lock.py +0 -0
  52. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
  53. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/command.py +0 -0
  54. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/common.py +0 -0
  55. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/consumeable.py +0 -0
  56. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/device_features.py +0 -0
  57. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  58. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  59. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  60. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/led_status.py +0 -0
  61. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/map_content.py +0 -0
  62. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/maps.py +0 -0
  63. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/network_info.py +0 -0
  64. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/rooms.py +0 -0
  65. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  66. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/status.py +0 -0
  67. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  68. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/volume.py +0 -0
  69. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  70. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/v1_channel.py +0 -0
  71. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/devices/v1_rpc_channel.py +0 -0
  72. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/exceptions.py +0 -0
  73. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/map/__init__.py +0 -0
  74. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/map/map_parser.py +0 -0
  75. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/mqtt/__init__.py +0 -0
  76. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/mqtt/roborock_session.py +0 -0
  77. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/mqtt/session.py +0 -0
  78. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/protocol.py +0 -0
  79. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/protocols/a01_protocol.py +0 -0
  80. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/protocols/b01_protocol.py +0 -0
  81. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/protocols/v1_protocol.py +0 -0
  82. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/py.typed +0 -0
  83. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/roborock_future.py +0 -0
  84. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/roborock_message.py +0 -0
  85. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/roborock_typing.py +0 -0
  86. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/util.py +0 -0
  87. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/version_1_apis/__init__.py +0 -0
  88. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  89. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/version_1_apis/roborock_local_client_v1.py +1 -1
  90. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  91. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/version_a01_apis/__init__.py +0 -0
  92. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  93. {python_roborock-3.3.3 → python_roborock-3.5.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 3.3.3
3
+ Version: 3.5.0
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Project-URL: Repository, https://github.com/humbertogontijo/python-roborock
6
6
  Project-URL: Documentation, https://python-roborock.readthedocs.io/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-roborock"
3
- version = "3.3.3"
3
+ version = "3.5.0"
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"
@@ -46,7 +46,7 @@ from roborock.data import CombinedMapInfo, DeviceData, HomeData, NetworkInfo, Ro
46
46
  from roborock.device_features import DeviceFeatures
47
47
  from roborock.devices.cache import Cache, CacheData
48
48
  from roborock.devices.device import RoborockDevice
49
- from roborock.devices.device_manager import DeviceManager, create_device_manager, create_home_data_api
49
+ from roborock.devices.device_manager import DeviceManager, UserParams, create_device_manager
50
50
  from roborock.devices.traits import Trait
51
51
  from roborock.devices.traits.v1 import V1TraitMixin
52
52
  from roborock.devices.traits.v1.consumeable import ConsumableAttribute
@@ -118,7 +118,7 @@ class ConnectionCache(RoborockBase):
118
118
  email: str
119
119
  home_data: HomeData | None = None
120
120
  network_info: dict[str, NetworkInfo] | None = None
121
- home_cache: dict[int, CombinedMapInfo] | None = None
121
+ home_map_info: dict[int, CombinedMapInfo] | None = None
122
122
  trait_data: dict[str, Any] | None = None
123
123
 
124
124
 
@@ -135,8 +135,11 @@ class DeviceConnectionManager:
135
135
  """Ensure device manager is initialized."""
136
136
  if self.device_manager is None:
137
137
  cache_data = self.context.cache_data()
138
- home_data_api = create_home_data_api(cache_data.email, cache_data.user_data)
139
- self.device_manager = await create_device_manager(cache_data.user_data, home_data_api, self.context)
138
+ user_params = UserParams(
139
+ username=cache_data.email,
140
+ user_data=cache_data.user_data,
141
+ )
142
+ self.device_manager = await create_device_manager(user_params, cache=self.context)
140
143
  # Cache devices for quick lookup
141
144
  devices = await self.device_manager.get_devices()
142
145
  self._devices = {device.duid: device for device in devices}
@@ -267,7 +270,7 @@ class RoborockContext(Cache):
267
270
  return CacheData(
268
271
  home_data=connection_cache.home_data,
269
272
  network_info=connection_cache.network_info or {},
270
- home_cache=connection_cache.home_cache,
273
+ home_map_info=connection_cache.home_map_info,
271
274
  trait_data=connection_cache.trait_data or {},
272
275
  )
273
276
 
@@ -277,7 +280,7 @@ class RoborockContext(Cache):
277
280
  connection_cache = self.cache_data()
278
281
  connection_cache.home_data = value.home_data
279
282
  connection_cache.network_info = value.network_info
280
- connection_cache.home_cache = value.home_cache
283
+ connection_cache.home_map_info = value.home_map_info
281
284
  connection_cache.trait_data = value.trait_data
282
285
  self.update(connection_cache)
283
286
 
@@ -717,14 +720,14 @@ async def home(ctx, device_id: str, refresh: bool):
717
720
  await home_trait.refresh()
718
721
 
719
722
  # Display the discovered home cache
720
- if home_trait.home_cache:
723
+ if home_trait.home_map_info:
721
724
  cache_summary = {
722
725
  map_flag: {
723
726
  "name": map_data.name,
724
727
  "room_count": len(map_data.rooms),
725
728
  "rooms": [{"segment_id": room.segment_id, "name": room.name} for room in map_data.rooms],
726
729
  }
727
- for map_flag, map_data in home_trait.home_cache.items()
730
+ for map_flag, map_data in home_trait.home_map_info.items()
728
731
  }
729
732
  click.echo(dump_json(cache_summary))
730
733
  else:
@@ -22,8 +22,8 @@ class CacheData:
22
22
  network_info: dict[str, NetworkInfo] = field(default_factory=dict)
23
23
  """Network information indexed by device DUID."""
24
24
 
25
- home_cache: dict[int, CombinedMapInfo] = field(default_factory=dict)
26
- """Home cache information indexed by map_flag."""
25
+ home_map_info: dict[int, CombinedMapInfo] = field(default_factory=dict)
26
+ """Home map information indexed by map_flag."""
27
27
 
28
28
  device_features: DeviceFeatures | None = None
29
29
  """Device features information."""
@@ -3,7 +3,8 @@
3
3
  import asyncio
4
4
  import enum
5
5
  import logging
6
- from collections.abc import Awaitable, Callable
6
+ from collections.abc import Callable
7
+ from dataclasses import dataclass
7
8
 
8
9
  import aiohttp
9
10
 
@@ -18,7 +19,7 @@ from roborock.map.map_parser import MapParserConfig
18
19
  from roborock.mqtt.roborock_session import create_lazy_mqtt_session
19
20
  from roborock.mqtt.session import MqttSession
20
21
  from roborock.protocol import create_mqtt_params
21
- from roborock.web_api import RoborockApiClient
22
+ from roborock.web_api import RoborockApiClient, UserWebApiClient
22
23
 
23
24
  from .cache import Cache, NoCache
24
25
  from .channel import Channel
@@ -30,12 +31,11 @@ _LOGGER = logging.getLogger(__name__)
30
31
 
31
32
  __all__ = [
32
33
  "create_device_manager",
33
- "create_home_data_api",
34
+ "UserParams",
34
35
  "DeviceManager",
35
36
  ]
36
37
 
37
38
 
38
- HomeDataApi = Callable[[], Awaitable[HomeData]]
39
39
  DeviceCreator = Callable[[HomeData, HomeDataDevice, HomeDataProduct], RoborockDevice]
40
40
 
41
41
 
@@ -53,7 +53,7 @@ class DeviceManager:
53
53
 
54
54
  def __init__(
55
55
  self,
56
- home_data_api: HomeDataApi,
56
+ web_api: UserWebApiClient,
57
57
  device_creator: DeviceCreator,
58
58
  mqtt_session: MqttSession,
59
59
  cache: Cache,
@@ -62,7 +62,7 @@ class DeviceManager:
62
62
 
63
63
  This takes ownership of the MQTT session and will close it when the manager is closed.
64
64
  """
65
- self._home_data_api = home_data_api
65
+ self._web_api = web_api
66
66
  self._cache = cache
67
67
  self._device_creator = device_creator
68
68
  self._devices: dict[str, RoborockDevice] = {}
@@ -73,7 +73,7 @@ class DeviceManager:
73
73
  cache_data = await self._cache.get()
74
74
  if not cache_data.home_data:
75
75
  _LOGGER.debug("No cached home data found, fetching from API")
76
- cache_data.home_data = await self._home_data_api()
76
+ cache_data.home_data = await self._web_api.get_home_data()
77
77
  await self._cache.set(cache_data)
78
78
  home_data = cache_data.home_data
79
79
 
@@ -108,45 +108,69 @@ class DeviceManager:
108
108
  await asyncio.gather(*tasks)
109
109
 
110
110
 
111
- def create_home_data_api(
112
- email: str, user_data: UserData, base_url: str | None = None, session: aiohttp.ClientSession | None = None
113
- ) -> HomeDataApi:
114
- """Create a home data API wrapper.
111
+ @dataclass
112
+ class UserParams:
113
+ """Parameters for creating a new session with Roborock devices.
115
114
 
116
- This function creates a wrapper around the Roborock API client to fetch
117
- home data for the user.
115
+ These parameters include the username, user data for authentication,
116
+ and an optional base URL for the Roborock API. The `user_data` and `base_url`
117
+ parameters are obtained from `RoborockApiClient` during the login process.
118
118
  """
119
- # Note: This will auto discover the API base URL. This can be improved
120
- # by caching this next to `UserData` if needed to avoid unnecessary API calls.
121
- client = RoborockApiClient(username=email, base_url=base_url, session=session)
122
119
 
123
- return create_home_data_from_api_client(client, user_data)
120
+ username: str
121
+ """The username (email) used for logging in."""
122
+
123
+ user_data: UserData
124
+ """This is the user data containing authentication information."""
125
+
126
+ base_url: str | None = None
127
+ """Optional base URL for the Roborock API.
128
+
129
+ This is used to speed up connection times by avoiding the need to
130
+ discover the API base URL each time. If not provided, the API client
131
+ will attempt to discover it automatically which may take multiple requests.
132
+ """
124
133
 
125
134
 
126
- def create_home_data_from_api_client(client: RoborockApiClient, user_data: UserData) -> HomeDataApi:
135
+ def create_web_api_wrapper(
136
+ user_params: UserParams,
137
+ *,
138
+ cache: Cache | None = None,
139
+ session: aiohttp.ClientSession | None = None,
140
+ ) -> UserWebApiClient:
127
141
  """Create a home data API wrapper from an existing API client."""
128
142
 
129
- async def home_data_api() -> HomeData:
130
- return await client.get_home_data_v3(user_data)
143
+ # Note: This will auto discover the API base URL. This can be improved
144
+ # by caching this next to `UserData` if needed to avoid unnecessary API calls.
145
+ client = RoborockApiClient(username=user_params.username, base_url=user_params.base_url, session=session)
131
146
 
132
- return home_data_api
147
+ return UserWebApiClient(client, user_params.user_data)
133
148
 
134
149
 
135
150
  async def create_device_manager(
136
- user_data: UserData,
137
- home_data_api: HomeDataApi,
151
+ user_params: UserParams,
152
+ *,
138
153
  cache: Cache | None = None,
139
154
  map_parser_config: MapParserConfig | None = None,
155
+ session: aiohttp.ClientSession | None = None,
140
156
  ) -> DeviceManager:
141
157
  """Convenience function to create and initialize a DeviceManager.
142
158
 
143
- The Home Data is fetched using the provided home_data_api callable which
144
- is exposed this way to allow for swapping out other implementations to
145
- include caching or other optimizations.
159
+ Args:
160
+ user_params: Parameters for creating the user session.
161
+ cache: Optional cache implementation to use for caching device data.
162
+ map_parser_config: Optional configuration for parsing maps.
163
+ session: Optional aiohttp ClientSession to use for HTTP requests.
164
+
165
+ Returns:
166
+ An initialized DeviceManager with discovered devices.
146
167
  """
147
168
  if cache is None:
148
169
  cache = NoCache()
149
170
 
171
+ web_api = create_web_api_wrapper(user_params, session=session, cache=cache)
172
+ user_data = user_params.user_data
173
+
150
174
  mqtt_params = create_mqtt_params(user_data.rriot)
151
175
  mqtt_session = await create_lazy_mqtt_session(mqtt_params)
152
176
 
@@ -176,6 +200,6 @@ async def create_device_manager(
176
200
  raise NotImplementedError(f"Device {device.name} has unsupported version {device.pv}")
177
201
  return RoborockDevice(device, product, channel, trait)
178
202
 
179
- manager = DeviceManager(home_data_api, device_creator, mqtt_session=mqtt_session, cache=cache)
203
+ manager = DeviceManager(web_api, device_creator, mqtt_session=mqtt_session, cache=cache)
180
204
  await manager.discover_devices()
181
205
  return manager
@@ -61,7 +61,7 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
61
61
  self._maps_trait = maps_trait
62
62
  self._rooms_trait = rooms_trait
63
63
  self._cache = cache
64
- self._home_cache: dict[int, CombinedMapInfo] | None = None
64
+ self._home_map_info: dict[int, CombinedMapInfo] | None = None
65
65
 
66
66
  async def discover_home(self) -> None:
67
67
  """Iterate through all maps to discover rooms and cache them.
@@ -72,12 +72,12 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
72
72
  cleaning process. This will raise `RoborockDeviceBusy` if the device is
73
73
  currently cleaning.
74
74
 
75
- After discovery, the home cache will be populated and can be accessed via the `home_cache` property.
75
+ After discovery, the home cache will be populated and can be accessed via the `home_map_info` property.
76
76
  """
77
77
  cache_data = await self._cache.get()
78
- if cache_data.home_cache:
78
+ if cache_data.home_map_info:
79
79
  _LOGGER.debug("Home cache already populated, skipping discovery")
80
- self._home_cache = cache_data.home_cache
80
+ self._home_map_info = cache_data.home_map_info
81
81
  return
82
82
 
83
83
  if self._status_trait.state == RoborockStateCode.cleaning:
@@ -87,11 +87,11 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
87
87
  if self._maps_trait.current_map_info is None:
88
88
  raise RoborockException("Cannot perform home discovery without current map info")
89
89
 
90
- home_cache = await self._build_home_cache()
91
- _LOGGER.debug("Home discovery complete, caching data for %d maps", len(home_cache))
92
- await self._update_home_cache(home_cache)
90
+ home_map_info = await self._build_home_map_info()
91
+ _LOGGER.debug("Home discovery complete, caching data for %d maps", len(home_map_info))
92
+ await self._update_home_map_info(home_map_info)
93
93
 
94
- async def _refresh_map_data(self, map_info) -> CombinedMapInfo:
94
+ async def _refresh_map_info(self, map_info) -> CombinedMapInfo:
95
95
  """Collect room data for a specific map and return CombinedMapInfo."""
96
96
  await self._rooms_trait.refresh()
97
97
  return CombinedMapInfo(
@@ -100,9 +100,9 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
100
100
  rooms=self._rooms_trait.rooms or [],
101
101
  )
102
102
 
103
- async def _build_home_cache(self) -> dict[int, CombinedMapInfo]:
103
+ async def _build_home_map_info(self) -> dict[int, CombinedMapInfo]:
104
104
  """Perform the actual discovery and caching of home data."""
105
- home_cache: dict[int, CombinedMapInfo] = {}
105
+ home_map_info: dict[int, CombinedMapInfo] = {}
106
106
 
107
107
  # Sort map_info to process the current map last, reducing map switching.
108
108
  # False (non-original maps) sorts before True (original map). We ensure
@@ -120,9 +120,9 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
120
120
  await self._maps_trait.set_current_map(map_info.map_flag)
121
121
  await asyncio.sleep(MAP_SLEEP)
122
122
 
123
- map_data = await self._refresh_map_data(map_info)
124
- home_cache[map_info.map_flag] = map_data
125
- return home_cache
123
+ map_data = await self._refresh_map_info(map_info)
124
+ home_map_info[map_info.map_flag] = map_data
125
+ return home_map_info
126
126
 
127
127
  async def refresh(self) -> Self:
128
128
  """Refresh current map's underlying map and room data, updating cache as needed.
@@ -131,7 +131,7 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
131
131
  active maps or re-discover the home. It is expected that this will keep
132
132
  information up to date for the current map as users switch to that map.
133
133
  """
134
- if self._home_cache is None:
134
+ if self._home_map_info is None:
135
135
  raise RoborockException("Cannot refresh home data without home cache, did you call discover_home()?")
136
136
 
137
137
  # Refresh the list of map names/info
@@ -142,33 +142,33 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
142
142
  raise RoborockException("Cannot refresh home data without current map info")
143
143
 
144
144
  # Refresh the current map's room data
145
- current_map_data = self._home_cache.get(map_flag)
145
+ current_map_data = self._home_map_info.get(map_flag)
146
146
  if current_map_data:
147
- map_data = await self._refresh_map_data(current_map_info)
147
+ map_data = await self._refresh_map_info(current_map_info)
148
148
  if map_data != current_map_data:
149
- await self._update_home_cache({**self._home_cache, map_flag: map_data})
149
+ await self._update_home_map_info({**self._home_map_info, map_flag: map_data})
150
150
 
151
151
  return self
152
152
 
153
153
  @property
154
- def home_cache(self) -> dict[int, CombinedMapInfo] | None:
154
+ def home_map_info(self) -> dict[int, CombinedMapInfo] | None:
155
155
  """Returns the map information for all cached maps."""
156
- return self._home_cache
156
+ return self._home_map_info
157
157
 
158
158
  @property
159
159
  def current_map_data(self) -> CombinedMapInfo | None:
160
160
  """Returns the map data for the current map."""
161
161
  current_map_flag = self._maps_trait.current_map
162
- if current_map_flag is None or self._home_cache is None:
162
+ if current_map_flag is None or self._home_map_info is None:
163
163
  return None
164
- return self._home_cache.get(current_map_flag)
164
+ return self._home_map_info.get(current_map_flag)
165
165
 
166
166
  def _parse_response(self, response: common.V1ResponseData) -> Self:
167
167
  """This trait does not parse responses directly."""
168
168
  raise NotImplementedError("HomeTrait does not support direct command responses")
169
169
 
170
- async def _update_home_cache(self, home_cache: dict[int, CombinedMapInfo]) -> None:
170
+ async def _update_home_map_info(self, home_map_info: dict[int, CombinedMapInfo]) -> None:
171
171
  cache_data = await self._cache.get()
172
- cache_data.home_cache = home_cache
172
+ cache_data.home_map_info = home_map_info
173
173
  await self._cache.set(cache_data)
174
- self._home_cache = home_cache
174
+ self._home_map_info = home_map_info
@@ -707,3 +707,21 @@ def _get_hawk_authentication(rriot: RRiot, url: str, formdata: dict | None = Non
707
707
  )
708
708
  mac = base64.b64encode(hmac.new(rriot.h.encode(), prestr.encode(), hashlib.sha256).digest()).decode()
709
709
  return f'Hawk id="{rriot.u}",s="{rriot.s}",ts="{timestamp}",nonce="{nonce}",mac="{mac}"'
710
+
711
+
712
+ class UserWebApiClient:
713
+ """Wrapper around RoborockApiClient to provide information for a specific user.
714
+
715
+ This binds a RoborockApiClient to a specific user context with the
716
+ provided UserData. This allows for easier access to user-specific data,
717
+ to avoid needing to pass UserData around and mock out the web API.
718
+ """
719
+
720
+ def __init__(self, web_api: RoborockApiClient, user_data: UserData) -> None:
721
+ """Initialize the wrapper with the API client and user data."""
722
+ self._web_api = web_api
723
+ self._user_data = user_data
724
+
725
+ async def get_home_data(self) -> HomeData:
726
+ """Fetch home data using the API client."""
727
+ return await self._web_api.get_home_data_v3(self._user_data)
File without changes
@@ -210,6 +210,7 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
210
210
  version=self.local_protocol_version,
211
211
  )
212
212
  self._logger.debug("Building message id %s for method %s", request_message.request_id, method)
213
+ await self._validate_connection()
213
214
  return await self._send_message(
214
215
  roborock_message,
215
216
  request_id=request_message.request_id,
@@ -226,7 +227,6 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
226
227
  method: str | None = None,
227
228
  params: list | dict | int | None = None,
228
229
  ) -> RoborockMessage:
229
- await self._validate_connection()
230
230
  msg = self._encoder(roborock_message)
231
231
  if method:
232
232
  self._logger.debug(f"id={request_id} Requesting method {method} with {params}")