python-roborock 2.43.0__tar.gz → 2.44.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 (59) hide show
  1. {python_roborock-2.43.0 → python_roborock-2.44.0}/PKG-INFO +1 -1
  2. {python_roborock-2.43.0 → python_roborock-2.44.0}/pyproject.toml +1 -1
  3. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/cli.py +43 -1
  4. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/device_manager.py +2 -0
  5. python_roborock-2.44.0/roborock/devices/traits/sound_volume.py +31 -0
  6. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/v1_rpc_channel.py +4 -1
  7. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/protocols/v1_protocol.py +28 -12
  8. {python_roborock-2.43.0 → python_roborock-2.44.0}/LICENSE +0 -0
  9. {python_roborock-2.43.0 → python_roborock-2.44.0}/README.md +0 -0
  10. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/__init__.py +0 -0
  11. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/api.py +0 -0
  12. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/b01_containers.py +0 -0
  13. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/broadcast_protocol.py +0 -0
  14. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/callbacks.py +0 -0
  15. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/clean_modes.py +0 -0
  16. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/cloud_api.py +0 -0
  17. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/code_mappings.py +0 -0
  18. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/command_cache.py +0 -0
  19. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/const.py +0 -0
  20. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/containers.py +0 -0
  21. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/device_features.py +0 -0
  22. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/README.md +0 -0
  23. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/__init__.py +0 -0
  24. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/a01_channel.py +0 -0
  25. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/b01_channel.py +0 -0
  26. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/cache.py +0 -0
  27. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/channel.py +0 -0
  28. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/device.py +0 -0
  29. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/local_channel.py +0 -0
  30. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/mqtt_channel.py +0 -0
  31. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/traits/b01/__init__.py +0 -0
  32. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/traits/b01/props.py +0 -0
  33. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/traits/clean_summary.py +0 -0
  34. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/traits/dnd.py +0 -0
  35. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/traits/dyad.py +0 -0
  36. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/traits/status.py +0 -0
  37. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/traits/trait.py +0 -0
  38. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/traits/zeo.py +0 -0
  39. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/devices/v1_channel.py +0 -0
  40. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/exceptions.py +0 -0
  41. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/mqtt/__init__.py +0 -0
  42. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/mqtt/roborock_session.py +0 -0
  43. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/mqtt/session.py +0 -0
  44. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/protocol.py +0 -0
  45. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/protocols/a01_protocol.py +0 -0
  46. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/protocols/b01_protocol.py +0 -0
  47. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/py.typed +0 -0
  48. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/roborock_future.py +0 -0
  49. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/roborock_message.py +0 -0
  50. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/roborock_typing.py +0 -0
  51. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/util.py +0 -0
  52. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/version_1_apis/__init__.py +0 -0
  53. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  54. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
  55. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  56. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/version_a01_apis/__init__.py +0 -0
  57. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  58. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
  59. {python_roborock-2.43.0 → python_roborock-2.44.0}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-roborock
3
- Version: 2.43.0
3
+ Version: 2.44.0
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Home-page: https://github.com/humbertogontijo/python-roborock
6
6
  License: GPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-roborock"
3
- version = "2.43.0"
3
+ version = "2.44.0"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
6
6
  license = "GPL-3.0-only"
@@ -382,7 +382,7 @@ async def execute_scene(ctx, scene_id):
382
382
  @click.pass_context
383
383
  @async_command
384
384
  async def status(ctx, device_id: str):
385
- """Get device status - unified implementation for both modes."""
385
+ """Get device status."""
386
386
  context: RoborockContext = ctx.obj
387
387
 
388
388
  device_manager = await context.get_device_manager()
@@ -414,6 +414,46 @@ async def clean_summary(ctx, device_id: str):
414
414
  click.echo(dump_json(clean_summary_result.as_dict()))
415
415
 
416
416
 
417
+ @session.command()
418
+ @click.option("--device_id", required=True)
419
+ @click.pass_context
420
+ @async_command
421
+ async def volume(ctx, device_id: str):
422
+ """Get device volume."""
423
+ context: RoborockContext = ctx.obj
424
+
425
+ device_manager = await context.get_device_manager()
426
+ device = await device_manager.get_device(device_id)
427
+
428
+ if not (volume_trait := device.traits.get("sound_volume")):
429
+ click.echo(f"Device {device.name} does not have a volume trait")
430
+ return
431
+
432
+ volume_result = await volume_trait.get_volume()
433
+ click.echo(f"Device {device_id} volume:")
434
+ click.echo(volume_result)
435
+
436
+
437
+ @session.command()
438
+ @click.option("--device_id", required=True)
439
+ @click.option("--volume", required=True, type=int)
440
+ @click.pass_context
441
+ @async_command
442
+ async def set_volume(ctx, device_id: str, volume: int):
443
+ """Set the devicevolume."""
444
+ context: RoborockContext = ctx.obj
445
+
446
+ device_manager = await context.get_device_manager()
447
+ device = await device_manager.get_device(device_id)
448
+
449
+ if not (volume_trait := device.traits.get("sound_volume")):
450
+ click.echo(f"Device {device.name} does not have a volume trait")
451
+ return
452
+
453
+ await volume_trait.set_volume(volume)
454
+ click.echo(f"Set Device {device_id} volume to {volume}")
455
+
456
+
417
457
  @click.command()
418
458
  @click.option("--device_id", required=True)
419
459
  @click.option("--cmd", required=True)
@@ -653,6 +693,8 @@ cli.add_command(session)
653
693
  cli.add_command(get_device_info)
654
694
  cli.add_command(update_docs)
655
695
  cli.add_command(clean_summary)
696
+ cli.add_command(volume)
697
+ cli.add_command(set_volume)
656
698
 
657
699
 
658
700
  def main():
@@ -25,6 +25,7 @@ from .traits.b01.props import B01PropsApi
25
25
  from .traits.clean_summary import CleanSummaryTrait
26
26
  from .traits.dnd import DoNotDisturbTrait
27
27
  from .traits.dyad import DyadApi
28
+ from .traits.sound_volume import SoundVolumeTrait
28
29
  from .traits.status import StatusTrait
29
30
  from .traits.trait import Trait
30
31
  from .traits.zeo import ZeoApi
@@ -156,6 +157,7 @@ async def create_device_manager(
156
157
  traits.append(StatusTrait(product, channel.rpc_channel))
157
158
  traits.append(DoNotDisturbTrait(channel.rpc_channel))
158
159
  traits.append(CleanSummaryTrait(channel.rpc_channel))
160
+ traits.append(SoundVolumeTrait(channel.rpc_channel))
159
161
  case DeviceVersion.A01:
160
162
  mqtt_channel = create_mqtt_channel(user_data, mqtt_params, mqtt_session, device)
161
163
  match product.category:
@@ -0,0 +1,31 @@
1
+ """Module for controlling the sound volume of Roborock devices."""
2
+
3
+ from roborock.devices.traits.trait import Trait
4
+ from roborock.devices.v1_rpc_channel import V1RpcChannel
5
+ from roborock.exceptions import RoborockException
6
+ from roborock.roborock_typing import RoborockCommand
7
+
8
+ __all__ = [
9
+ "SoundVolumeTrait",
10
+ ]
11
+
12
+
13
+ class SoundVolumeTrait(Trait):
14
+ """Trait for controlling the sound volume of a Roborock device."""
15
+
16
+ name = "sound_volume"
17
+
18
+ def __init__(self, rpc_channel: V1RpcChannel) -> None:
19
+ """Initialize the SoundVolumeTrait."""
20
+ self._rpc_channel = rpc_channel
21
+
22
+ async def get_volume(self) -> int:
23
+ """Get the current sound volume of the device."""
24
+ response = await self._rpc_channel.send_command(RoborockCommand.GET_SOUND_VOLUME)
25
+ if not isinstance(response, list) or not response:
26
+ raise RoborockException(f"Unexpected volume format: {response!r}")
27
+ return int(response[0])
28
+
29
+ async def set_volume(self, volume: int) -> None:
30
+ """Set the sound volume of the device."""
31
+ await self._rpc_channel.send_command(RoborockCommand.CHANGE_SOUND_VOLUME, params=[volume])
@@ -149,7 +149,10 @@ class PayloadEncodedV1RpcChannel(BaseV1RpcChannel):
149
149
  return
150
150
  _LOGGER.debug("Received response (request_id=%s): %s", self._name, decoded.request_id)
151
151
  if decoded.request_id == request_message.request_id:
152
- future.set_result(decoded.data)
152
+ if decoded.api_error:
153
+ future.set_exception(decoded.api_error)
154
+ else:
155
+ future.set_result(decoded.data)
153
156
 
154
157
  unsub = await self._channel.subscribe(find_response)
155
158
  try:
@@ -108,9 +108,18 @@ class ResponseMessage:
108
108
  data: ResponseData
109
109
  """The data of the response, where the type depends on the command."""
110
110
 
111
+ api_error: RoborockException | None = None
112
+ """The API error message of the response if any."""
113
+
111
114
 
112
115
  def decode_rpc_response(message: RoborockMessage) -> ResponseMessage:
113
- """Decode a V1 RPC_RESPONSE message."""
116
+ """Decode a V1 RPC_RESPONSE message.
117
+
118
+ This will raise a RoborockException if the message cannot be parsed. A
119
+ response object will be returned even if there is an error in the
120
+ response, as long as we can extract the request ID. This is so we can
121
+ associate an API response with a request even if there was an error.
122
+ """
114
123
  if not message.payload:
115
124
  return ResponseMessage(request_id=message.seq, data={})
116
125
  try:
@@ -136,19 +145,26 @@ def decode_rpc_response(message: RoborockMessage) -> ResponseMessage:
136
145
  ) from e
137
146
 
138
147
  request_id: int | None = data_point_response.get("id")
148
+ exc: RoborockException | None = None
139
149
  if error := data_point_response.get("error"):
140
- raise RoborockException(f"Error in message: {error}")
141
-
150
+ exc = RoborockException(error)
142
151
  if not (result := data_point_response.get("result")):
143
- raise RoborockException(f"Invalid V1 message format: missing 'result' in data point for {message.payload!r}")
144
- _LOGGER.debug("Decoded V1 message result: %s", result)
145
- if isinstance(result, str) and result == "ok":
146
- result = {}
147
- if not isinstance(result, (dict, list, int)):
148
- raise RoborockException(
149
- f"Invalid V1 message format: 'result' was unexpected type {type(result)}. {message.payload!r}"
150
- )
151
- return ResponseMessage(request_id=request_id, data=result)
152
+ exc = RoborockException(f"Invalid V1 message format: missing 'result' in data point for {message.payload!r}")
153
+ else:
154
+ _LOGGER.debug("Decoded V1 message result: %s", result)
155
+ if isinstance(result, str):
156
+ if result == "unknown_method":
157
+ exc = RoborockException("The method called is not recognized by the device.")
158
+ elif result != "ok":
159
+ exc = RoborockException(f"Unexpected API Result: {result}")
160
+ result = {}
161
+ if not isinstance(result, (dict, list, int)):
162
+ raise RoborockException(
163
+ f"Invalid V1 message format: 'result' was unexpected type {type(result)}. {message.payload!r}"
164
+ )
165
+ if not request_id and exc:
166
+ raise exc
167
+ return ResponseMessage(request_id=request_id, data=result, api_error=exc)
152
168
 
153
169
 
154
170
  @dataclass