pymammotion 0.2.62__py3-none-any.whl → 0.5.51__py3-none-any.whl

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.

Potentially problematic release.


This version of pymammotion might be problematic. Click here for more details.

Files changed (135) hide show
  1. pymammotion/__init__.py +9 -6
  2. pymammotion/aliyun/client.py +235 -0
  3. pymammotion/aliyun/cloud_gateway.py +320 -69
  4. pymammotion/aliyun/model/aep_response.py +1 -2
  5. pymammotion/aliyun/model/dev_by_account_response.py +170 -23
  6. pymammotion/aliyun/model/login_by_oauth_response.py +2 -3
  7. pymammotion/aliyun/model/regions_response.py +3 -3
  8. pymammotion/aliyun/model/session_by_authcode_response.py +2 -2
  9. pymammotion/aliyun/model/thing_response.py +12 -0
  10. pymammotion/aliyun/regions.py +62 -0
  11. pymammotion/aliyun/tea/core.py +297 -0
  12. pymammotion/bluetooth/ble.py +11 -15
  13. pymammotion/bluetooth/ble_message.py +389 -106
  14. pymammotion/bluetooth/model/atomic_integer.py +54 -0
  15. pymammotion/const.py +3 -0
  16. pymammotion/data/model/__init__.py +1 -2
  17. pymammotion/data/model/device.py +92 -240
  18. pymammotion/data/model/device_config.py +10 -24
  19. pymammotion/data/model/device_info.py +35 -0
  20. pymammotion/data/model/device_limits.py +49 -0
  21. pymammotion/data/model/enums.py +12 -2
  22. pymammotion/data/model/errors.py +12 -0
  23. pymammotion/data/model/events.py +14 -0
  24. pymammotion/data/model/generate_geojson.py +521 -0
  25. pymammotion/data/model/generate_route_information.py +3 -4
  26. pymammotion/data/model/hash_list.py +384 -48
  27. pymammotion/data/model/location.py +4 -4
  28. pymammotion/data/model/mowing_modes.py +24 -1
  29. pymammotion/data/model/raw_data.py +215 -0
  30. pymammotion/data/model/region_data.py +10 -11
  31. pymammotion/data/model/report_info.py +62 -6
  32. pymammotion/data/model/work.py +27 -0
  33. pymammotion/data/mower_state_manager.py +316 -0
  34. pymammotion/data/mqtt/event.py +73 -28
  35. pymammotion/data/mqtt/mammotion_properties.py +257 -0
  36. pymammotion/data/mqtt/properties.py +93 -78
  37. pymammotion/data/mqtt/status.py +18 -17
  38. pymammotion/event/event.py +32 -8
  39. pymammotion/homeassistant/__init__.py +3 -0
  40. pymammotion/homeassistant/mower_api.py +484 -0
  41. pymammotion/homeassistant/rtk_api.py +54 -0
  42. pymammotion/http/__init__.py +0 -0
  43. pymammotion/http/encryption.py +220 -0
  44. pymammotion/http/http.py +652 -44
  45. pymammotion/http/model/__init__.py +0 -0
  46. pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
  47. pymammotion/http/model/http.py +160 -9
  48. pymammotion/http/model/response_factory.py +61 -0
  49. pymammotion/http/model/rtk.py +16 -0
  50. pymammotion/mammotion/commands/abstract_message.py +7 -5
  51. pymammotion/mammotion/commands/mammotion_command.py +32 -3
  52. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  53. pymammotion/mammotion/commands/messages/driver.py +61 -29
  54. pymammotion/mammotion/commands/messages/media.py +68 -15
  55. pymammotion/mammotion/commands/messages/navigation.py +61 -25
  56. pymammotion/mammotion/commands/messages/network.py +93 -100
  57. pymammotion/mammotion/commands/messages/ota.py +18 -18
  58. pymammotion/mammotion/commands/messages/system.py +97 -72
  59. pymammotion/mammotion/commands/messages/video.py +17 -12
  60. pymammotion/mammotion/devices/__init__.py +27 -3
  61. pymammotion/mammotion/devices/base.py +50 -127
  62. pymammotion/mammotion/devices/mammotion.py +447 -212
  63. pymammotion/mammotion/devices/mammotion_bluetooth.py +105 -60
  64. pymammotion/mammotion/devices/mammotion_cloud.py +157 -105
  65. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  66. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  67. pymammotion/mammotion/devices/managers/managers.py +81 -0
  68. pymammotion/mammotion/devices/mower_device.py +124 -0
  69. pymammotion/mammotion/devices/mower_manager.py +107 -0
  70. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  71. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  72. pymammotion/mammotion/devices/rtk_device.py +50 -0
  73. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  74. pymammotion/mqtt/__init__.py +2 -1
  75. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  76. pymammotion/mqtt/linkkit/__init__.py +5 -0
  77. pymammotion/mqtt/linkkit/h2client.py +585 -0
  78. pymammotion/mqtt/linkkit/linkkit.py +3023 -0
  79. pymammotion/mqtt/mammotion_mqtt.py +176 -169
  80. pymammotion/mqtt/mqtt_models.py +66 -0
  81. pymammotion/proto/__init__.py +4839 -4
  82. pymammotion/proto/basestation.proto +8 -0
  83. pymammotion/proto/basestation_pb2.py +11 -9
  84. pymammotion/proto/basestation_pb2.pyi +16 -2
  85. pymammotion/proto/dev_net.proto +79 -55
  86. pymammotion/proto/dev_net_pb2.py +60 -56
  87. pymammotion/proto/dev_net_pb2.pyi +49 -6
  88. pymammotion/proto/luba_msg.proto +2 -1
  89. pymammotion/proto/luba_msg_pb2.py +6 -6
  90. pymammotion/proto/luba_msg_pb2.pyi +1 -0
  91. pymammotion/proto/luba_mul.proto +62 -1
  92. pymammotion/proto/luba_mul_pb2.py +38 -22
  93. pymammotion/proto/luba_mul_pb2.pyi +94 -7
  94. pymammotion/proto/mctrl_driver.proto +44 -4
  95. pymammotion/proto/mctrl_driver_pb2.py +26 -14
  96. pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
  97. pymammotion/proto/mctrl_nav.proto +97 -51
  98. pymammotion/proto/mctrl_nav_pb2.py +75 -67
  99. pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
  100. pymammotion/proto/mctrl_ota.proto +40 -2
  101. pymammotion/proto/mctrl_ota_pb2.py +23 -13
  102. pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
  103. pymammotion/proto/mctrl_pept.proto +8 -3
  104. pymammotion/proto/mctrl_pept_pb2.py +8 -6
  105. pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
  106. pymammotion/proto/mctrl_sys.proto +325 -86
  107. pymammotion/proto/mctrl_sys_pb2.py +162 -98
  108. pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
  109. pymammotion/proto/message_pool.py +3 -0
  110. pymammotion/proto/py.typed +0 -0
  111. pymammotion/utility/constant/device_constant.py +65 -21
  112. pymammotion/utility/datatype_converter.py +13 -12
  113. pymammotion/utility/device_config.py +755 -0
  114. pymammotion/utility/device_type.py +218 -21
  115. pymammotion/utility/map.py +238 -51
  116. pymammotion/utility/mur_mur_hash.py +159 -0
  117. {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/METADATA +27 -31
  118. pymammotion-0.5.51.dist-info/RECORD +152 -0
  119. {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
  120. pymammotion/aliyun/cloud_service.py +0 -65
  121. pymammotion/data/model/plan.py +0 -58
  122. pymammotion/data/state_manager.py +0 -130
  123. pymammotion/proto/basestation.py +0 -59
  124. pymammotion/proto/common.py +0 -12
  125. pymammotion/proto/dev_net.py +0 -381
  126. pymammotion/proto/luba_msg.py +0 -81
  127. pymammotion/proto/luba_mul.py +0 -76
  128. pymammotion/proto/mctrl_driver.py +0 -100
  129. pymammotion/proto/mctrl_nav.py +0 -660
  130. pymammotion/proto/mctrl_ota.py +0 -48
  131. pymammotion/proto/mctrl_pept.py +0 -41
  132. pymammotion/proto/mctrl_sys.py +0 -574
  133. pymammotion-0.2.62.dist-info/RECORD +0 -125
  134. /pymammotion/{http/_init_.py → bluetooth/model/__init__.py} +0 -0
  135. {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info/licenses}/LICENSE +0 -0
@@ -1,9 +1,11 @@
1
1
  import asyncio
2
+ from collections.abc import Awaitable, Callable
2
3
  import logging
3
- from typing import Any, cast
4
+ import time
5
+ from typing import Any
4
6
  from uuid import UUID
5
7
 
6
- import betterproto
8
+ import betterproto2
7
9
  from bleak import BleakGATTCharacteristic, BleakGATTServiceCollection, BLEDevice
8
10
  from bleak.exc import BleakDBusError
9
11
  from bleak_retry_connector import (
@@ -13,12 +15,12 @@ from bleak_retry_connector import (
13
15
  establish_connection,
14
16
  )
15
17
 
18
+ from pymammotion.aliyun.model.dev_by_account_response import Device
16
19
  from pymammotion.bluetooth import BleMessage
17
- from pymammotion.data.model.device import MowingDevice
20
+ from pymammotion.data.mower_state_manager import MowerStateManager
18
21
  from pymammotion.mammotion.commands.mammotion_command import MammotionCommand
19
22
  from pymammotion.mammotion.devices.base import MammotionBaseDevice
20
- from pymammotion.proto import has_field
21
- from pymammotion.proto.luba_msg import LubaMsg
23
+ from pymammotion.proto import LubaMsg
22
24
 
23
25
  DBUS_ERROR_BACKOFF_TIME = 0.25
24
26
 
@@ -69,33 +71,61 @@ async def _handle_retry(fut: asyncio.Future[None], func, command: bytes) -> None
69
71
  class MammotionBaseBLEDevice(MammotionBaseDevice):
70
72
  """Base class for Mammotion BLE devices."""
71
73
 
72
- def __init__(self, mowing_state: MowingDevice, device: BLEDevice, interface: int = 0, **kwargs: Any) -> None:
74
+ def __init__(
75
+ self,
76
+ state_manager: MowerStateManager,
77
+ cloud_device: Device,
78
+ device: BLEDevice,
79
+ interface: int = 0,
80
+ **kwargs: Any,
81
+ ) -> None:
73
82
  """Initialize MammotionBaseBLEDevice."""
74
- super().__init__(mowing_state)
83
+ super().__init__(state_manager, cloud_device)
84
+ self.command_sent_time = 0
75
85
  self._disconnect_strategy = True
76
86
  self._ble_sync_task = None
77
87
  self._prev_notification = None
78
88
  self._interface = f"hci{interface}"
79
- self._device = device
89
+ self.ble_device = device
80
90
  self._client: BleakClientWithServiceCache | None = None
81
91
  self._read_char: BleakGATTCharacteristic | int | str | UUID = 0
82
92
  self._write_char: BleakGATTCharacteristic | int | str | UUID = 0
83
93
  self._disconnect_timer: asyncio.TimerHandle | None = None
84
94
  self._message: BleMessage | None = None
85
- self._commands: MammotionCommand = MammotionCommand(device.name)
95
+ self._commands: MammotionCommand = MammotionCommand(device.name, 1)
96
+ self.command_queue = asyncio.Queue()
86
97
  self._expected_disconnect = False
87
98
  self._connect_lock = asyncio.Lock()
88
99
  self._operation_lock = asyncio.Lock()
89
100
  self._key: str | None = None
101
+ self._cloud_device = cloud_device
90
102
  self.set_queue_callback(self.queue_command)
103
+ loop = asyncio.get_event_loop()
104
+ loop.create_task(self.process_queue())
105
+
106
+ def __del__(self) -> None:
107
+ """Cleanup."""
108
+ if self._disconnect_timer:
109
+ self._disconnect_timer.cancel()
110
+ if self._ble_sync_task:
111
+ self._ble_sync_task.cancel()
112
+
113
+ self._state_manager.ble_queue_command_callback.remove_subscribers(self.queue_command)
114
+
115
+ def set_notification_callback(self, func: Callable[[tuple[str, Any | None]], Awaitable[None]]) -> None:
116
+ self._state_manager.ble_on_notification_callback.add_subscribers(func)
117
+
118
+ def set_queue_callback(self, func: Callable[[str, dict[str, Any]], Awaitable[None]]) -> None:
119
+ self._state_manager.ble_queue_command_callback.add_subscribers(func)
91
120
 
92
121
  def update_device(self, device: BLEDevice) -> None:
93
122
  """Update the BLE device."""
94
- self._device = device
123
+ self.ble_device = device
95
124
 
96
125
  async def _ble_sync(self) -> None:
97
- command_bytes = self._commands.send_todev_ble_sync(2)
98
- await self._message.post_custom_data_bytes(command_bytes)
126
+ if self._client is not None and self._client.is_connected:
127
+ command_bytes = self._commands.send_todev_ble_sync(2)
128
+ await self._message.post_custom_data_bytes(command_bytes)
99
129
 
100
130
  async def run_periodic_sync_task(self) -> None:
101
131
  """Send ble sync to robot."""
@@ -113,11 +143,37 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
113
143
 
114
144
  async def stop(self) -> None:
115
145
  """Stop all tasks and disconnect."""
116
- self._ble_sync_task.cancel()
117
- await self._client.disconnect()
118
-
119
- async def queue_command(self, key: str, **kwargs: Any) -> bytes | None:
120
- return await self._send_command_with_args(key, **kwargs)
146
+ if self._ble_sync_task:
147
+ self._ble_sync_task.cancel()
148
+ if self._client is not None and self._client.is_connected:
149
+ await self._client.disconnect()
150
+
151
+ async def queue_command(self, key: str, **kwargs: Any) -> None:
152
+ # Create a future to hold the result
153
+ _LOGGER.debug("Queueing command: %s", key)
154
+ future = asyncio.Future()
155
+ # Put the command in the queue as a tuple (key, command, future)
156
+ command_bytes = getattr(self._commands, key)(**kwargs)
157
+ await self.command_queue.put((key, command_bytes, future))
158
+ # Wait for the future to be resolved
159
+ await future
160
+ # return await self._send_command_with_args(key, **kwargs)
161
+
162
+ async def process_queue(self) -> None:
163
+ while True:
164
+ # Get the next item from the queue
165
+ key, command, future = await self.command_queue.get()
166
+ try:
167
+ # Process the command using _execute_command_locked
168
+ result = await self._send_command_locked(key, command)
169
+ # Set the result on the future
170
+ future.set_result(result)
171
+ except Exception as ex:
172
+ # Set the exception on the future if something goes wrong
173
+ future.set_exception(ex)
174
+ finally:
175
+ # Mark the task as done
176
+ self.command_queue.task_done()
121
177
 
122
178
  async def _send_command_with_args(self, key: str, **kwargs) -> bytes | None:
123
179
  """Send command to device and read response."""
@@ -184,15 +240,17 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
184
240
  @property
185
241
  def name(self) -> str:
186
242
  """Return device name."""
187
- return f"{self._device.name} ({self._device.address})"
243
+ return f"{self.ble_device.name} ({self.ble_device.address})"
188
244
 
189
245
  @property
190
246
  def rssi(self) -> int:
191
247
  """Return RSSI of device."""
192
- try:
193
- return cast(self.mower.sys.toapp_report_data.connect.ble_rssi, int)
194
- finally:
195
- return 0
248
+ return self.mower.report_data.connect.ble_rssi
249
+
250
+ @property
251
+ def client(self) -> BleakClientWithServiceCache | None:
252
+ """Return client."""
253
+ return self._client
196
254
 
197
255
  async def _ensure_connected(self) -> None:
198
256
  """Ensure connection to device is established."""
@@ -223,11 +281,11 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
223
281
  _LOGGER.debug("%s: Connecting; RSSI: %s", self.name, self.rssi)
224
282
  client: BleakClientWithServiceCache = await establish_connection(
225
283
  BleakClientWithServiceCache,
226
- self._device,
284
+ self.ble_device,
227
285
  self.name,
228
286
  self._disconnected,
229
287
  max_attempts=10,
230
- ble_device_callback=lambda: self._device,
288
+ ble_device_callback=lambda: self.ble_device,
231
289
  )
232
290
  _LOGGER.debug("%s: Connected; RSSI: %s", self.name, self.rssi)
233
291
  self._client = client
@@ -255,11 +313,10 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
255
313
  )
256
314
  self._reset_disconnect_timer()
257
315
  await self._start_notify()
258
- command_bytes = self._commands.send_todev_ble_sync(2)
259
- await self._message.post_custom_data_bytes(command_bytes)
316
+ await self._ble_sync()
260
317
  self.schedule_ble_sync()
261
318
 
262
- async def _send_command_locked(self, key: str, command: bytes) -> bytes:
319
+ async def _send_command_locked(self, key: str, command: bytes) -> None:
263
320
  """Send command to device and read response."""
264
321
  await self._ensure_connected()
265
322
  try:
@@ -284,73 +341,60 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
284
341
 
285
342
  async def _notification_handler(self, _sender: BleakGATTCharacteristic, data: bytearray) -> None:
286
343
  """Handle notification responses."""
344
+
287
345
  if self._message is None:
288
346
  return
289
347
  result = self._message.parseNotification(data)
290
348
  if result == 0:
291
349
  data = await self._message.parseBlufiNotifyData(True)
292
- self._update_raw_data(data)
293
- self._message.clearNotification()
350
+ self._message.clear_notification()
351
+ try:
352
+ self._update_raw_data(data)
353
+ except (KeyError, ValueError, IndexError, UnicodeDecodeError):
354
+ _LOGGER.exception("Error parsing message %s", data)
355
+ data = b""
356
+
294
357
  _LOGGER.debug("%s: Received notification: %s", self.name, data)
295
358
  else:
296
359
  return
360
+
297
361
  new_msg = LubaMsg().parse(data)
298
- if betterproto.serialized_on_wire(new_msg.net):
299
- if new_msg.net.todev_ble_sync != 0 or has_field(new_msg.net.toapp_wifi_iot_status):
300
- if has_field(new_msg.net.toapp_wifi_iot_status) and self._commands.get_device_product_key() == "":
362
+ res = betterproto2.which_one_of(new_msg, "LubaSubMsg")
363
+ if res[0] == "net":
364
+ if new_msg.net.todev_ble_sync != 0 or new_msg.net.toapp_wifi_iot_status is not None:
365
+ if new_msg.net.toapp_wifi_iot_status is not None and self._commands.get_device_product_key() == "":
301
366
  self._commands.set_device_product_key(new_msg.net.toapp_wifi_iot_status.productkey)
302
367
 
303
- return
368
+ await self._state_manager.notification(new_msg)
304
369
 
305
- # may or may not be correct, some work could be done here to correctly match responses
306
- if self._notify_future and not self._notify_future.done():
307
- self._notify_future.set_result(data)
370
+ if self._execute_timed_disconnect is None:
371
+ await self._execute_forced_disconnect()
308
372
 
309
373
  self._reset_disconnect_timer()
310
- await self._state_manager.notification(new_msg)
311
374
 
312
375
  async def _start_notify(self) -> None:
313
376
  """Start notification."""
314
377
  _LOGGER.debug("%s: Subscribe to notifications; RSSI: %s", self.name, self.rssi)
315
378
  await self._client.start_notify(self._read_char, self._notification_handler)
316
379
 
317
- async def _execute_command_locked(self, key: str, command: bytes) -> bytes:
380
+ async def _execute_command_locked(self, key: str, command: bytes) -> None:
318
381
  """Execute command and read response."""
319
382
  assert self._client is not None
320
- self._notify_future = self.loop.create_future()
321
- self._key = key
322
383
  _LOGGER.debug("%s: Sending command: %s", self.name, key)
323
384
  await self._message.post_custom_data_bytes(command)
324
-
325
- timeout = 2
326
- timeout_handle = self.loop.call_at(self.loop.time() + timeout, _handle_timeout, self._notify_future)
327
- timeout_expired = False
328
- try:
329
- notify_msg = await self._notify_future
330
- except asyncio.TimeoutError:
331
- timeout_expired = True
332
- notify_msg = b""
333
- finally:
334
- if not timeout_expired:
335
- timeout_handle.cancel()
336
- self._notify_future = None
337
-
338
- _LOGGER.debug("%s: Notification received: %s", self.name, notify_msg.hex())
339
- return notify_msg
385
+ self.command_sent_time = time.time()
340
386
 
341
387
  def get_address(self) -> str:
342
388
  """Return address of device."""
343
- return self._device.address
389
+ return self.ble_device.address
344
390
 
345
391
  def _resolve_characteristics(self, services: BleakGATTServiceCollection) -> None:
346
392
  """Resolve characteristics."""
347
393
  self._read_char = services.get_characteristic(READ_CHAR_UUID)
348
394
  if not self._read_char:
349
- self._read_char = READ_CHAR_UUID
350
395
  _LOGGER.error(CharacteristicMissingError(READ_CHAR_UUID))
351
396
  self._write_char = services.get_characteristic(WRITE_CHAR_UUID)
352
397
  if not self._write_char:
353
- self._write_char = WRITE_CHAR_UUID
354
398
  _LOGGER.error(CharacteristicMissingError(WRITE_CHAR_UUID))
355
399
 
356
400
  def _reset_disconnect_timer(self) -> None:
@@ -370,6 +414,7 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
370
414
  self.rssi,
371
415
  )
372
416
  self._cancel_disconnect_timer()
417
+ self._client = None
373
418
 
374
419
  def _disconnect_from_timer(self) -> None:
375
420
  """Disconnect from device."""
@@ -448,5 +493,5 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
448
493
  _LOGGER.debug("%s: Disconnect completed successfully", self.name)
449
494
  self._client = None
450
495
 
451
- def set_disconnect_strategy(self, disconnect: bool) -> None:
496
+ def set_disconnect_strategy(self, *, disconnect: bool) -> None:
452
497
  self._disconnect_strategy = disconnect