python-roborock 5.35.0__tar.gz → 5.36.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 (126) hide show
  1. {python_roborock-5.35.0 → python_roborock-5.36.0}/PKG-INFO +1 -1
  2. {python_roborock-5.35.0 → python_roborock-5.36.0}/pyproject.toml +1 -1
  3. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/rpc/a01_channel.py +13 -2
  4. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/transport/mqtt_channel.py +7 -3
  5. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/mqtt/roborock_session.py +12 -6
  6. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/mqtt/session.py +25 -1
  7. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/protocols/a01_protocol.py +5 -1
  8. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/testing/channel.py +6 -1
  9. {python_roborock-5.35.0 → python_roborock-5.36.0}/.gitignore +0 -0
  10. {python_roborock-5.35.0 → python_roborock-5.36.0}/LICENSE +0 -0
  11. {python_roborock-5.35.0 → python_roborock-5.36.0}/README.md +0 -0
  12. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/__init__.py +0 -0
  13. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/broadcast_protocol.py +0 -0
  14. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/callbacks.py +0 -0
  15. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/cli.py +0 -0
  16. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/const.py +0 -0
  17. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/__init__.py +0 -0
  18. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/b01_q10/__init__.py +0 -0
  19. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  20. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  21. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/b01_q7/__init__.py +0 -0
  22. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  23. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  24. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/code_mappings.py +0 -0
  25. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/containers.py +0 -0
  26. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/dyad/__init__.py +0 -0
  27. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  28. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/dyad/dyad_containers.py +0 -0
  29. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/mower/__init__.py +0 -0
  30. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/mower/mower_containers.py +0 -0
  31. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/v1/__init__.py +0 -0
  32. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/v1/v1_clean_modes.py +0 -0
  33. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/v1/v1_code_mappings.py +0 -0
  34. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/v1/v1_containers.py +0 -0
  35. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/zeo/__init__.py +0 -0
  36. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  37. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/data/zeo/zeo_containers.py +0 -0
  38. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/device_features.py +0 -0
  39. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/README.md +0 -0
  40. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/__init__.py +0 -0
  41. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/cache.py +0 -0
  42. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/device.py +0 -0
  43. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/device_manager.py +0 -0
  44. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/file_cache.py +0 -0
  45. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/rpc/__init__.py +0 -0
  46. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
  47. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/rpc/b01_q7_channel.py +0 -0
  48. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/rpc/v1_channel.py +0 -0
  49. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/__init__.py +0 -0
  50. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/a01/__init__.py +0 -0
  51. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/__init__.py +0 -0
  52. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
  53. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/button_light.py +0 -0
  54. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
  55. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
  56. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/command.py +0 -0
  57. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/common.py +0 -0
  58. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/consumable.py +0 -0
  59. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
  60. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
  61. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/map.py +0 -0
  62. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/network_info.py +0 -0
  63. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/remote.py +0 -0
  64. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/status.py +0 -0
  65. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
  66. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q10/volume.py +0 -0
  67. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
  68. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
  69. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q7/map.py +0 -0
  70. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/b01/q7/map_content.py +0 -0
  71. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/common.py +0 -0
  72. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/traits_mixin.py +0 -0
  73. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/__init__.py +0 -0
  74. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/child_lock.py +0 -0
  75. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
  76. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/command.py +0 -0
  77. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/common.py +0 -0
  78. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/consumeable.py +0 -0
  79. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/device_features.py +0 -0
  80. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  81. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  82. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  83. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/home.py +0 -0
  84. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/led_status.py +0 -0
  85. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/map_content.py +0 -0
  86. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/maps.py +0 -0
  87. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/network_info.py +0 -0
  88. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/rooms.py +0 -0
  89. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/routines.py +0 -0
  90. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  91. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/status.py +0 -0
  92. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  93. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/volume.py +0 -0
  94. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  95. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/transport/__init__.py +0 -0
  96. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/transport/channel.py +0 -0
  97. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/devices/transport/local_channel.py +0 -0
  98. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/diagnostics.py +0 -0
  99. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/exceptions.py +0 -0
  100. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/__init__.py +0 -0
  101. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/b01_grid_layers.py +0 -0
  102. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/b01_map_parser.py +0 -0
  103. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/b01_q10_map_parser.py +0 -0
  104. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/b01_q10_overlays.py +0 -0
  105. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/b01_q10_render.py +0 -0
  106. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/map_parser.py +0 -0
  107. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/proto/__init__.py +0 -0
  108. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/proto/b01_scmap.proto +0 -0
  109. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/map/proto/b01_scmap_pb2.py +0 -0
  110. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/mqtt/__init__.py +0 -0
  111. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/mqtt/health_manager.py +0 -0
  112. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/protocol.py +0 -0
  113. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/protocols/__init__.py +0 -0
  114. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/protocols/b01_q10_protocol.py +0 -0
  115. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/protocols/b01_q7_protocol.py +0 -0
  116. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/protocols/v1_protocol.py +0 -0
  117. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/py.typed +0 -0
  118. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/roborock_message.py +0 -0
  119. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/roborock_typing.py +0 -0
  120. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/testing/__init__.py +0 -0
  121. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/testing/b01_q10_simulator.py +0 -0
  122. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/testing/cloud.py +0 -0
  123. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/testing/simulator.py +0 -0
  124. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/testing/v1_simulator.py +0 -0
  125. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/util.py +0 -0
  126. {python_roborock-5.35.0 → python_roborock-5.36.0}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 5.35.0
3
+ Version: 5.36.0
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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-roborock"
3
- version = "5.35.0"
3
+ version = "5.36.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"
@@ -7,6 +7,7 @@ from typing import Any, overload
7
7
 
8
8
  from roborock.devices.transport.mqtt_channel import MqttChannel
9
9
  from roborock.exceptions import RoborockException
10
+ from roborock.mqtt.session import MqttQos
10
11
  from roborock.protocols.a01_protocol import (
11
12
  decode_rpc_response,
12
13
  encode_mqtt_payload,
@@ -30,6 +31,7 @@ async def send_decoded_command(
30
31
  mqtt_channel: MqttChannel,
31
32
  params: dict[RoborockDyadDataProtocol, Any],
32
33
  value_encoder: Callable[[Any], Any] | None = None,
34
+ qos: MqttQos = MqttQos.AT_MOST_ONCE,
33
35
  ) -> dict[RoborockDyadDataProtocol, Any]: ...
34
36
 
35
37
 
@@ -38,6 +40,7 @@ async def send_decoded_command(
38
40
  mqtt_channel: MqttChannel,
39
41
  params: dict[RoborockZeoProtocol, Any],
40
42
  value_encoder: Callable[[Any], Any] | None = None,
43
+ qos: MqttQos = MqttQos.AT_MOST_ONCE,
41
44
  ) -> dict[RoborockZeoProtocol, Any]: ...
42
45
 
43
46
 
@@ -45,8 +48,16 @@ async def send_decoded_command(
45
48
  mqtt_channel: MqttChannel,
46
49
  params: dict[RoborockDyadDataProtocol, Any] | dict[RoborockZeoProtocol, Any],
47
50
  value_encoder: Callable[[Any], Any] | None = None,
51
+ qos: MqttQos = MqttQos.AT_MOST_ONCE,
48
52
  ) -> dict[RoborockDyadDataProtocol, Any] | dict[RoborockZeoProtocol, Any]:
49
- """Send a command on the MQTT channel and get a decoded response."""
53
+ """Send a command on the MQTT channel and get a decoded response.
54
+
55
+ Args:
56
+ mqtt_channel: The MQTT channel to send the command on.
57
+ params: The parameters to send.
58
+ value_encoder: A function to encode the values of the dictionary.
59
+ qos: The MQTT QoS level. Defaults to AT_MOST_ONCE.
60
+ """
50
61
  _LOGGER.debug("Sending MQTT command: %s", params)
51
62
  roborock_message = encode_mqtt_payload(params, value_encoder)
52
63
 
@@ -54,7 +65,7 @@ async def send_decoded_command(
54
65
  # block waiting for a response. Queries are handled below.
55
66
  param_values = {int(k): v for k, v in params.items()}
56
67
  if not (query_values := param_values.get(_ID_QUERY)):
57
- await mqtt_channel.publish(roborock_message)
68
+ await mqtt_channel.publish(roborock_message, qos=qos)
58
69
  return {}
59
70
 
60
71
  # Merge any results together than contain the requested data. This
@@ -8,7 +8,7 @@ from roborock.callbacks import decoder_callback
8
8
  from roborock.data import HomeDataDevice, RRiot, UserData
9
9
  from roborock.exceptions import RoborockException
10
10
  from roborock.mqtt.health_manager import HealthManager
11
- from roborock.mqtt.session import MqttParams, MqttSession, MqttSessionException
11
+ from roborock.mqtt.session import MqttParams, MqttQos, MqttSession, MqttSessionException
12
12
  from roborock.protocol import create_mqtt_decoder, create_mqtt_encoder
13
13
  from roborock.roborock_message import RoborockMessage
14
14
  from roborock.util import RoborockLoggerAdapter
@@ -89,11 +89,15 @@ class MqttChannel(Channel):
89
89
  finally:
90
90
  unsub()
91
91
 
92
- async def publish(self, message: RoborockMessage) -> None:
92
+ async def publish(self, message: RoborockMessage, qos: MqttQos = MqttQos.AT_MOST_ONCE) -> None:
93
93
  """Publish a command message.
94
94
 
95
95
  The caller is responsible for handling any responses and associating them
96
96
  with the incoming request.
97
+
98
+ Args:
99
+ message: The message to publish.
100
+ qos: The MQTT QoS level. Defaults to AT_MOST_ONCE.
97
101
  """
98
102
  try:
99
103
  encoded_msg = self._encoder(message)
@@ -101,7 +105,7 @@ class MqttChannel(Channel):
101
105
  self._logger.exception("Error encoding MQTT message: %s", e)
102
106
  raise RoborockException(f"Failed to encode MQTT message: {e}") from e
103
107
  try:
104
- return await self._mqtt_session.publish(self._publish_topic, encoded_msg)
108
+ return await self._mqtt_session.publish(self._publish_topic, encoded_msg, qos=qos)
105
109
  except MqttSessionException as e:
106
110
  self._logger.debug("Error publishing MQTT message: %s", e)
107
111
  raise RoborockException(f"Failed to publish MQTT message: {e}") from e
@@ -22,7 +22,7 @@ from roborock.callbacks import CallbackMap
22
22
  from roborock.diagnostics import Diagnostics, redact_topic_name
23
23
 
24
24
  from .health_manager import HealthManager
25
- from .session import MqttParams, MqttSession, MqttSessionException, MqttSessionUnauthorized
25
+ from .session import MqttParams, MqttQos, MqttSession, MqttSessionException, MqttSessionUnauthorized
26
26
 
27
27
  _LOGGER = logging.getLogger(__name__)
28
28
  _MQTT_LOGGER = logging.getLogger(f"{__name__}.aiomqtt")
@@ -361,8 +361,14 @@ class RoborockMqttSession(MqttSession):
361
361
 
362
362
  return delayed_unsub
363
363
 
364
- async def publish(self, topic: str, message: bytes) -> None:
365
- """Publish a message on the topic."""
364
+ async def publish(self, topic: str, message: bytes, qos: MqttQos = MqttQos.AT_MOST_ONCE) -> None:
365
+ """Publish a message on the topic.
366
+
367
+ Args:
368
+ topic: The MQTT topic to publish to.
369
+ message: The message payload.
370
+ qos: The MQTT QoS level. Defaults to AT_MOST_ONCE.
371
+ """
366
372
  _LOGGER.debug("Sending message to topic %s: %s", topic, message)
367
373
  client: aiomqtt.Client
368
374
  async with self._client_lock:
@@ -371,7 +377,7 @@ class RoborockMqttSession(MqttSession):
371
377
  client = self._client
372
378
  try:
373
379
  with self._diagnostics.timer("publish"):
374
- await client.publish(topic, message)
380
+ await client.publish(topic, message, qos=qos)
375
381
  except MqttError as err:
376
382
  raise MqttSessionException(f"Error publishing message: {err}") from err
377
383
 
@@ -417,13 +423,13 @@ class LazyMqttSession(MqttSession):
417
423
  await self._maybe_start()
418
424
  return await self._session.subscribe(device_id, callback)
419
425
 
420
- async def publish(self, topic: str, message: bytes) -> None:
426
+ async def publish(self, topic: str, message: bytes, qos: MqttQos = MqttQos.AT_MOST_ONCE) -> None:
421
427
  """Publish a message on the specified topic.
422
428
 
423
429
  This will raise an exception if the message could not be sent.
424
430
  """
425
431
  await self._maybe_start()
426
- return await self._session.publish(topic, message)
432
+ return await self._session.publish(topic, message, qos=qos)
427
433
 
428
434
  async def close(self) -> None:
429
435
  """Cancels the mqtt loop.
@@ -3,6 +3,7 @@
3
3
  from abc import ABC, abstractmethod
4
4
  from collections.abc import Callable
5
5
  from dataclasses import dataclass, field
6
+ from enum import IntEnum
6
7
 
7
8
  from roborock.diagnostics import Diagnostics
8
9
  from roborock.exceptions import RoborockException
@@ -10,6 +11,24 @@ from roborock.mqtt.health_manager import HealthManager
10
11
 
11
12
  DEFAULT_TIMEOUT = 30.0
12
13
 
14
+
15
+ class MqttQos(IntEnum):
16
+ """MQTT Quality of Service levels.
17
+
18
+ A01 devices (Zeo, Dyad) require ``AT_LEAST_ONCE`` for DP200 (start)
19
+ commands. Other protocol versions use ``AT_MOST_ONCE``.
20
+ """
21
+
22
+ AT_MOST_ONCE = 0
23
+ """Fire-and-forget. No acknowledgment required."""
24
+
25
+ AT_LEAST_ONCE = 1
26
+ """Guaranteed delivery with possible duplicates. Broker sends PUBACK."""
27
+
28
+ EXACTLY_ONCE = 2
29
+ """Guaranteed delivery with no duplicates. Broker sends PUBREC/PUBREL/PUBCOMP."""
30
+
31
+
13
32
  SessionUnauthorizedHook = Callable[[], None]
14
33
 
15
34
 
@@ -76,10 +95,15 @@ class MqttSession(ABC):
76
95
  """
77
96
 
78
97
  @abstractmethod
79
- async def publish(self, topic: str, message: bytes) -> None:
98
+ async def publish(self, topic: str, message: bytes, qos: MqttQos = MqttQos.AT_MOST_ONCE) -> None:
80
99
  """Publish a message on the specified topic.
81
100
 
82
101
  This will raise an exception if the message could not be sent.
102
+
103
+ Args:
104
+ topic: The MQTT topic to publish to.
105
+ message: The message payload.
106
+ qos: The MQTT QoS level. Defaults to AT_MOST_ONCE.
83
107
  """
84
108
 
85
109
  @abstractmethod
@@ -2,6 +2,7 @@
2
2
 
3
3
  import json
4
4
  import logging
5
+ import time
5
6
  from collections.abc import Callable
6
7
  from typing import Any
7
8
 
@@ -42,7 +43,10 @@ def encode_mqtt_payload(
42
43
  """
43
44
  if value_encoder is None:
44
45
  value_encoder = _no_encode
45
- dps_data = {"dps": {key: value_encoder(value) for key, value in data.items()}}
46
+ dps_data = {
47
+ "dps": {key: value_encoder(value) for key, value in data.items()},
48
+ "t": int(time.time()),
49
+ }
46
50
  payload = pad(json.dumps(dps_data).encode("utf-8"), AES.block_size)
47
51
  return RoborockMessage(
48
52
  protocol=RoborockMessageProtocol.RPC_REQUEST,
@@ -12,6 +12,7 @@ from unittest.mock import AsyncMock, MagicMock
12
12
 
13
13
  from roborock.devices.transport.channel import Channel
14
14
  from roborock.mqtt.health_manager import HealthManager
15
+ from roborock.mqtt.session import MqttQos
15
16
  from roborock.protocols.v1_protocol import LocalProtocolVersion
16
17
  from roborock.roborock_message import RoborockMessage
17
18
 
@@ -83,6 +84,7 @@ class FakeChannel(Channel):
83
84
  self.close = MagicMock(side_effect=self._close)
84
85
 
85
86
  self.protocol_version = LocalProtocolVersion.V1
87
+
86
88
  self.restart = AsyncMock()
87
89
  self.health_manager = HealthManager(self.restart)
88
90
 
@@ -102,10 +104,13 @@ class FakeChannel(Channel):
102
104
  """Return true if locally connected."""
103
105
  return self._is_connected and self._is_local
104
106
 
105
- async def _publish(self, message: RoborockMessage) -> None:
107
+ async def _publish(self, message: RoborockMessage, qos: MqttQos = MqttQos.AT_MOST_ONCE) -> None:
106
108
  """Default publish implementation.
107
109
 
108
110
  Records the message in ``published_messages`` and executes ``publish_handler``.
111
+
112
+ The ``qos`` parameter is accepted for compatibility with
113
+ ``MqttChannel.publish`` but not simulated by the fake channel.
109
114
  """
110
115
  self.published_messages.append(message)
111
116
  if self.publish_side_effect: