pymammotion 0.2.6__tar.gz → 0.2.7__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.

Potentially problematic release.


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

Files changed (117) hide show
  1. {pymammotion-0.2.6 → pymammotion-0.2.7}/PKG-INFO +2 -1
  2. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/cloud_gateway.py +2 -0
  3. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/location.py +5 -5
  4. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/devices/mammotion.py +52 -61
  5. pymammotion-0.2.7/pymammotion/mqtt/mammotion_future.py +25 -0
  6. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mqtt/mammotion_mqtt.py +19 -11
  7. {pymammotion-0.2.6 → pymammotion-0.2.7}/pyproject.toml +4 -3
  8. {pymammotion-0.2.6 → pymammotion-0.2.7}/LICENSE +0 -0
  9. {pymammotion-0.2.6 → pymammotion-0.2.7}/README.md +0 -0
  10. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/__init__.py +0 -0
  11. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/__init__.py +0 -0
  12. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/cloud_service.py +0 -0
  13. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/aep_response.py +0 -0
  14. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/connect_response.py +0 -0
  15. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/dev_by_account_response.py +0 -0
  16. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/login_by_oauth_response.py +0 -0
  17. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/regions_response.py +0 -0
  18. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/session_by_authcode_response.py +0 -0
  19. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/tmp_constant.py +0 -0
  20. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/__init__.py +0 -0
  21. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/ble.py +0 -0
  22. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/ble_message.py +0 -0
  23. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/const.py +0 -0
  24. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/data/__init__.py +0 -0
  25. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/data/convert.py +0 -0
  26. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/data/framectrldata.py +0 -0
  27. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/data/notifydata.py +0 -0
  28. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/const.py +0 -0
  29. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/__init__.py +0 -0
  30. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/__init__.py +0 -0
  31. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/account.py +0 -0
  32. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/device.py +0 -0
  33. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/device_config.py +0 -0
  34. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/enums.py +0 -0
  35. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/excute_boarder_params.py +0 -0
  36. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/execute_boarder.py +0 -0
  37. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/generate_route_information.py +0 -0
  38. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/hash_list.py +0 -0
  39. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/mowing_modes.py +0 -0
  40. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/plan.py +0 -0
  41. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/rapid_state.py +0 -0
  42. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/region_data.py +0 -0
  43. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/report_info.py +0 -0
  44. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/mqtt/__init__.py +0 -0
  45. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/mqtt/event.py +0 -0
  46. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/mqtt/properties.py +0 -0
  47. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/mqtt/status.py +0 -0
  48. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/state_manager.py +0 -0
  49. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/event/__init__.py +0 -0
  50. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/event/event.py +0 -0
  51. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/http/_init_.py +0 -0
  52. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/http/http.py +0 -0
  53. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/__init__.py +0 -0
  54. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/__init__.py +0 -0
  55. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/abstract_message.py +0 -0
  56. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/mammotion_command.py +0 -0
  57. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/__init__.py +0 -0
  58. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/driver.py +0 -0
  59. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/media.py +0 -0
  60. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/navigation.py +0 -0
  61. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/network.py +0 -0
  62. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/ota.py +0 -0
  63. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/system.py +0 -0
  64. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/video.py +0 -0
  65. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/control/__init__.py +0 -0
  66. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/control/joystick.py +0 -0
  67. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/devices/__init__.py +0 -0
  68. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mqtt/__init__.py +0 -0
  69. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/__init__.py +0 -0
  70. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/basestation.proto +0 -0
  71. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/basestation.py +0 -0
  72. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/basestation_pb2.py +0 -0
  73. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/basestation_pb2.pyi +0 -0
  74. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/common.proto +0 -0
  75. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/common.py +0 -0
  76. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/common_pb2.py +0 -0
  77. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/common_pb2.pyi +0 -0
  78. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/dev_net.proto +0 -0
  79. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/dev_net.py +0 -0
  80. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/dev_net_pb2.py +0 -0
  81. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/dev_net_pb2.pyi +0 -0
  82. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_msg.proto +0 -0
  83. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_msg.py +0 -0
  84. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_msg_pb2.py +0 -0
  85. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_msg_pb2.pyi +0 -0
  86. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_mul.proto +0 -0
  87. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_mul.py +0 -0
  88. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_mul_pb2.py +0 -0
  89. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_mul_pb2.pyi +0 -0
  90. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_driver.proto +0 -0
  91. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_driver.py +0 -0
  92. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_driver_pb2.py +0 -0
  93. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_driver_pb2.pyi +0 -0
  94. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_nav.proto +0 -0
  95. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_nav.py +0 -0
  96. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_nav_pb2.py +0 -0
  97. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_nav_pb2.pyi +0 -0
  98. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_ota.proto +0 -0
  99. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_ota.py +0 -0
  100. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_ota_pb2.py +0 -0
  101. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_ota_pb2.pyi +0 -0
  102. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_pept.proto +0 -0
  103. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_pept.py +0 -0
  104. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_pept_pb2.py +0 -0
  105. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_pept_pb2.pyi +0 -0
  106. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_sys.proto +0 -0
  107. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_sys.py +0 -0
  108. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_sys_pb2.py +0 -0
  109. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_sys_pb2.pyi +0 -0
  110. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/py.typed +0 -0
  111. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/constant/__init__.py +0 -0
  112. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/constant/device_constant.py +0 -0
  113. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/datatype_converter.py +0 -0
  114. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/device_type.py +0 -0
  115. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/map.py +0 -0
  116. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/periodic.py +0 -0
  117. {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/rocker_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pymammotion
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary:
5
5
  License: GNU-3.0
6
6
  Author: Michael Arthur
@@ -17,6 +17,7 @@ Requires-Dist: alibabacloud-iot-api-gateway (>=0.0.4,<0.0.5)
17
17
  Requires-Dist: alicloud-gateway-iot (>=1.0.0,<2.0.0)
18
18
  Requires-Dist: aliyun-iot-linkkit (>=1.2.12,<2.0.0)
19
19
  Requires-Dist: aliyun-python-sdk-iot (>=8.57.0,<9.0.0)
20
+ Requires-Dist: async-timeout (>=4.0.3,<5.0.0)
20
21
  Requires-Dist: betterproto (>=1.2.5,<2.0.0)
21
22
  Requires-Dist: bleak (>=0.21.0)
22
23
  Requires-Dist: bleak-retry-connector (>=3.5.0,<4.0.0)
@@ -530,6 +530,8 @@ class CloudIOTGateway:
530
530
  )
531
531
  if response_body_dict.get("code") == 29003:
532
532
  raise SetupException(response_body_dict.get("code"))
533
+ if response_body_dict.get("code") == 6205:
534
+ """Device is offline."""
533
535
 
534
536
  return message_id
535
537
 
@@ -7,8 +7,8 @@ from dataclasses import dataclass
7
7
  class Point:
8
8
  """Returns a lat long."""
9
9
 
10
- latitude: float
11
- longitude: float
10
+ latitude: float = 0.0
11
+ longitude: float = 0.0
12
12
 
13
13
  def __init__(self, latitude=0.0, longitude=0.0):
14
14
  self.latitude = latitude
@@ -19,7 +19,7 @@ class Point:
19
19
  class Dock(Point):
20
20
  """Stores robot dock position."""
21
21
 
22
- rotation: int
22
+ rotation: int = 0
23
23
 
24
24
  def __init__(self):
25
25
  super().__init__()
@@ -33,8 +33,8 @@ class Location:
33
33
  device: Point
34
34
  RTK: Point
35
35
  dock: Dock
36
- position_type: int
37
- orientation: int # 360 degree rotation +-
36
+ position_type: int = 0
37
+ orientation: int = 0 # 360 degree rotation +-
38
38
 
39
39
  def __init__(self):
40
40
  self.device = Point()
@@ -2,12 +2,15 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import queue
6
+ import threading
5
7
  import asyncio
6
8
  import base64
7
9
  import codecs
8
10
  import json
9
11
  import logging
10
12
  from abc import abstractmethod
13
+ from collections import deque
11
14
  from enum import Enum
12
15
  from functools import cache
13
16
  from typing import Any, Callable, Optional, cast
@@ -38,6 +41,7 @@ from pymammotion.data.state_manager import StateManager
38
41
  from pymammotion.http.http import connect_http
39
42
  from pymammotion.mammotion.commands.mammotion_command import MammotionCommand
40
43
  from pymammotion.mqtt import MammotionMQTT
44
+ from pymammotion.mqtt.mammotion_future import MammotionFuture
41
45
  from pymammotion.proto.luba_msg import LubaMsg
42
46
  from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck
43
47
  from pymammotion.utility.rocker_util import RockerControlUtil
@@ -88,10 +92,10 @@ def slashescape(err):
88
92
  codecs.register_error("slashescape", slashescape)
89
93
 
90
94
 
91
- def find_next_integer(lst: list[int], current_int: int) -> int | None:
95
+ def find_next_integer(lst: list[int], current_hash: float) -> int | None:
92
96
  try:
93
97
  # Find the index of the current integer
94
- current_index = lst.index(current_int)
98
+ current_index = lst.index(current_hash)
95
99
 
96
100
  # Check if there is a next integer in the list
97
101
  if current_index + 1 < len(lst):
@@ -115,12 +119,12 @@ async def _handle_retry(fut: asyncio.Future[None], func, command: bytes) -> None
115
119
  await func(command)
116
120
 
117
121
 
118
- async def _handle_retry_cloud(fut: asyncio.Future[None], func, iotId: str, command: bytes) -> None:
122
+ async def _handle_retry_cloud(self, fut: asyncio.Future[None], func, iot_id: str, command: bytes) -> None:
119
123
  """Handle a retry."""
120
124
 
121
125
  if not fut.done():
122
- loop = asyncio.get_running_loop()
123
- await loop.run_in_executor(None, func, iotId, command)
126
+ self._operation_lock.release()
127
+ await self.loop.run_in_executor(None, func, iot_id, command)
124
128
 
125
129
 
126
130
  class ConnectionPreference(Enum):
@@ -334,7 +338,6 @@ class MammotionBaseDevice:
334
338
  self._raw_data = LubaMsg().to_dict(casing=betterproto.Casing.SNAKE)
335
339
  self._mower = device
336
340
  self._state_manager = StateManager(self._mower)
337
-
338
341
  self._state_manager.gethash_ack_callback.add_subscribers(self.datahash_response)
339
342
  self._state_manager.get_commondata_ack_callback.add_subscribers(self.commdata_response)
340
343
  self._notify_future: asyncio.Future[bytes] | None = None
@@ -945,7 +948,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
945
948
  self._commands: MammotionCommand = MammotionCommand(cloud_device.deviceName)
946
949
  self.currentID = ""
947
950
  self.on_ready_callback: Optional[Callable[[], None]] = None
948
- self._operation_lock = asyncio.Lock()
951
+ self._waiting_queue = deque()
949
952
 
950
953
  self._mqtt_client.on_connected = self.on_connected
951
954
  self._mqtt_client.on_disconnected = self.on_disconnected
@@ -958,19 +961,19 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
958
961
  # temporary for testing only
959
962
  # self._start_sync_task = self.loop.call_later(30, lambda: asyncio.ensure_future(self.start_sync(0)))
960
963
 
961
- def on_ready(self):
964
+ async def on_ready(self):
962
965
  """Callback for when MQTT is subscribed to events."""
963
966
  if self.on_ready_callback:
964
967
  self.on_ready_callback()
965
968
 
966
- asyncio.run(self._ble_sync())
967
- asyncio.run(self.run_periodic_sync_task())
969
+ await self._ble_sync()
970
+ await self.run_periodic_sync_task()
968
971
 
969
- def on_connected(self):
972
+ async def on_connected(self):
970
973
  """Callback for when MQTT connects."""
971
974
 
972
975
 
973
- def on_disconnected(self):
976
+ async def on_disconnected(self):
974
977
  """Callback for when MQTT disconnects."""
975
978
 
976
979
  async def _ble_sync(self):
@@ -993,27 +996,24 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
993
996
  160, lambda: asyncio.ensure_future(self.run_periodic_sync_task())
994
997
  )
995
998
 
996
- def _on_mqtt_message(self, topic: str, payload: str, iot_id: str) -> None:
999
+ async def _on_mqtt_message(self, topic: str, payload: str, iot_id: str) -> None:
997
1000
  """Handle incoming MQTT messages."""
998
- _LOGGER.debug("MQTT message received on topic %s: %s", topic, payload)
1001
+ _LOGGER.debug("MQTT message received on topic %s: %s", topic, payload, iot_id)
999
1002
 
1000
1003
  json_str = json.dumps(payload)
1001
1004
  payload = json.loads(json_str)
1002
1005
 
1003
- self._handle_mqtt_message(topic, payload)
1006
+ await self._handle_mqtt_message(topic, payload)
1004
1007
 
1005
1008
  async def _send_command(self, key: str, retry: int | None = None) -> bytes | None:
1006
1009
  """Send command to device via MQTT and read response."""
1007
- if self._operation_lock.locked():
1008
- _LOGGER.debug("%s: Operation already in progress, waiting for it to complete;", self.device.nickName)
1009
1010
 
1010
- async with self._operation_lock:
1011
- try:
1012
- command_bytes = getattr(self._commands, key)()
1013
- return await self._send_command_locked(key, command_bytes)
1014
- except Exception as ex:
1015
- _LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
1016
- raise
1011
+ try:
1012
+ command_bytes = getattr(self._commands, key)()
1013
+ return await self._send_command_locked(key, command_bytes)
1014
+ except Exception as ex:
1015
+ _LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
1016
+ raise
1017
1017
 
1018
1018
  async def _send_command_locked(self, key: str, command: bytes) -> bytes:
1019
1019
  """Send command to device and read response."""
@@ -1032,33 +1032,16 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1032
1032
  async def _execute_command_locked(self, key: str, command: bytes) -> bytes:
1033
1033
  """Execute command and read response."""
1034
1034
  assert self._mqtt_client is not None
1035
- self._notify_future = self.loop.create_future()
1036
1035
  self._key = key
1037
1036
  _LOGGER.debug("%s: Sending command: %s", self.device.nickName, key)
1038
- loop = asyncio.get_running_loop()
1039
- await loop.run_in_executor(None, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command)
1040
-
1041
- retry_handle = self.loop.call_at(
1042
- self.loop.time() + 10,
1043
- lambda: asyncio.ensure_future(
1044
- _handle_retry_cloud(
1045
- self._notify_future, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command
1046
- )
1047
- ),
1048
- )
1037
+ await self.loop.run_in_executor(None, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command)
1038
+ future = MammotionFuture()
1039
+ self._waiting_queue.append(future)
1049
1040
  timeout = 20
1050
- timeout_handle = self.loop.call_at(self.loop.time() + timeout, _handle_timeout, self._notify_future)
1051
- timeout_expired = False
1052
1041
  try:
1053
- notify_msg = await self._notify_future
1042
+ notify_msg = await future.async_get(timeout)
1054
1043
  except asyncio.TimeoutError:
1055
- timeout_expired = True
1056
1044
  raise
1057
- finally:
1058
- if not timeout_expired:
1059
- timeout_handle.cancel()
1060
- retry_handle.cancel()
1061
- self._notify_future = None
1062
1045
 
1063
1046
  _LOGGER.debug("%s: Message received", self.device.nickName)
1064
1047
 
@@ -1066,15 +1049,12 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1066
1049
 
1067
1050
  async def _send_command_with_args(self, key: str, **kwargs: any) -> bytes | None:
1068
1051
  """Send command with arguments to device via MQTT and read response."""
1069
- if self._operation_lock.locked():
1070
- _LOGGER.debug("%s: Operation already in progress, waiting for it to complete;", self.device.nickName)
1071
- async with self._operation_lock:
1072
- try:
1073
- command_bytes = getattr(self._commands, key)(**kwargs)
1074
- return await self._send_command_locked(key, command_bytes)
1075
- except Exception as ex:
1076
- _LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
1077
- raise
1052
+ try:
1053
+ command_bytes = getattr(self._commands, key)(**kwargs)
1054
+ return await self._send_command_locked(key, command_bytes)
1055
+ except Exception as ex:
1056
+ _LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
1057
+ raise
1078
1058
 
1079
1059
  def _extract_message_id(self, payload: dict) -> str:
1080
1060
  """Extract the message ID from the payload."""
@@ -1089,7 +1069,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1089
1069
  _LOGGER.error("Error extracting encoded message. Payload: %s", payload)
1090
1070
  return ""
1091
1071
 
1092
- def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
1072
+ async def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
1093
1073
  """Parse the MQTT response."""
1094
1074
  if topic.endswith("/app/down/thing/events"):
1095
1075
  _LOGGER.debug("Thing event received")
@@ -1100,14 +1080,25 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1100
1080
  binary_data = base64.b64decode(params.value.get("content", ""))
1101
1081
  self._update_raw_data(cast(bytes, binary_data))
1102
1082
  new_msg = LubaMsg().parse(cast(bytes, binary_data))
1103
- if self._notify_future and not self._notify_future.done():
1104
- self._notify_future.set_result(new_msg)
1105
- asyncio.run(self._state_manager.notification(new_msg))
1106
1083
 
1107
- def _handle_mqtt_message(self, topic: str, payload: dict) -> None:
1084
+ if self._commands.get_device_product_key() == "" and self._commands.get_device_name() == event.params.deviceName:
1085
+ self._commands.set_device_product_key(event.params.productKey)
1086
+
1087
+ if betterproto.serialized_on_wire(new_msg.net):
1088
+ if new_msg.net.todev_ble_sync != 0 or has_field(new_msg.net.toapp_wifi_iot_status):
1089
+ return
1090
+
1091
+
1092
+ if len(self._waiting_queue) > 0:
1093
+ fut: MammotionFuture = self._waiting_queue.popleft()
1094
+ fut.resolve(cast(bytes, binary_data))
1095
+ await self._state_manager.notification(new_msg)
1096
+
1097
+ async def _handle_mqtt_message(self, topic: str, payload: dict) -> None:
1108
1098
  """Async handler for incoming MQTT messages."""
1109
- self._parse_mqtt_response(topic=topic, payload=payload)
1099
+ await self._parse_mqtt_response(topic=topic, payload=payload)
1110
1100
 
1111
- async def _disconnect(self):
1101
+ def _disconnect(self):
1112
1102
  """Disconnect the MQTT client."""
1113
1103
  self._mqtt_client.disconnect()
1104
+
@@ -0,0 +1,25 @@
1
+ from asyncio import Future
2
+ from typing import Any
3
+
4
+ import async_timeout
5
+
6
+
7
+ class MammotionFuture:
8
+ """Create futures for each MQTT Message."""
9
+ def __init__(self):
10
+ self.fut: Future = Future()
11
+ self.loop = self.fut.get_loop()
12
+
13
+ def _resolve(self, item: bytes) -> None:
14
+ if not self.fut.cancelled():
15
+ self.fut.set_result(item)
16
+
17
+ def resolve(self, item: bytes) -> None:
18
+ self.loop.call_soon_threadsafe(self._resolve, item)
19
+
20
+ async def async_get(self, timeout: float | int) -> bytes:
21
+ try:
22
+ async with async_timeout.timeout(timeout):
23
+ return await self.fut
24
+ finally:
25
+ self.fut.cancel()
@@ -5,7 +5,7 @@ import hmac
5
5
  import json
6
6
  import logging
7
7
  from logging import getLogger
8
- from typing import Callable, Optional, cast
8
+ from typing import Callable, Optional, cast, Awaitable
9
9
 
10
10
  from linkkit.linkkit import LinkKit
11
11
  from paho.mqtt.client import MQTTMessage
@@ -36,11 +36,11 @@ class MammotionMQTT:
36
36
  self._cloud_client = None
37
37
  self.is_connected = False
38
38
  self.is_ready = False
39
- self.on_connected: Optional[Callable[[], None]] = None
40
- self.on_ready: Optional[Callable[[], None]] = None
41
- self.on_error: Optional[Callable[[str], None]] = None
42
- self.on_disconnected: Optional[Callable[[], None]] = None
43
- self.on_message: Optional[Callable[[str, str, str], None]] = None
39
+ self.on_connected: Optional[Callable[[],Awaitable[None]]] = None
40
+ self.on_ready: Optional[Callable[[],Awaitable[None]]] = None
41
+ self.on_error: Optional[Callable[[str],Awaitable[None]]] = None
42
+ self.on_disconnected: Optional[Callable[[],Awaitable[None]]] = None
43
+ self.on_message: Optional[Callable[[str, str, str],Awaitable[None]]] = None
44
44
 
45
45
  self._product_key = product_key
46
46
  self._device_name = device_name
@@ -57,6 +57,7 @@ class MammotionMQTT:
57
57
  ).hexdigest()
58
58
 
59
59
  self._client_id = client_id
60
+ self.loop = asyncio.get_event_loop()
60
61
 
61
62
  self._linkkit_client = LinkKit(
62
63
  region_id,
@@ -79,7 +80,6 @@ class MammotionMQTT:
79
80
  def connect_async(self):
80
81
  """Connect async to MQTT Server."""
81
82
  logger.info("Connecting...")
82
- self._linkkit_client.thing_setup()
83
83
  self._linkkit_client.connect_async()
84
84
 
85
85
 
@@ -121,7 +121,8 @@ class MammotionMQTT:
121
121
 
122
122
  if self.on_ready:
123
123
  self.is_ready = True
124
- self.on_ready()
124
+ future = asyncio.run_coroutine_threadsafe(self.on_ready(), self.loop)
125
+ asyncio.wrap_future(future, loop=self.loop)
125
126
  # self._linkkit_client.query_ota_firmware()
126
127
  # command = MammotionCommand(device_name="Luba")
127
128
  # self._cloud_client.send_cloud_command(command.get_report_cfg())
@@ -137,13 +138,17 @@ class MammotionMQTT:
137
138
  payload = json.loads(payload)
138
139
  iot_id = payload.get("params", {}).get("iotId", "")
139
140
  if iot_id != "" and self.on_message:
140
- self.on_message(topic, payload, iot_id)
141
+ future = asyncio.run_coroutine_threadsafe(self.on_message(topic, payload, iot_id), self.loop)
142
+ return asyncio.wrap_future(future, loop=self.loop)
143
+
141
144
 
142
145
  def _thing_on_connect(self, session_flag, rc, user_data):
143
146
  """Is called on thing connect."""
144
147
  self.is_connected = True
145
148
  if self.on_connected is not None:
146
- self.on_connected()
149
+ future = asyncio.run_coroutine_threadsafe(self.on_connected(), self.loop)
150
+ asyncio.wrap_future(future, loop=self.loop)
151
+
147
152
  logger.debug("on_connect, session_flag:%d, rc:%d", session_flag, rc)
148
153
 
149
154
  # self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/#")
@@ -154,7 +159,9 @@ class MammotionMQTT:
154
159
  self.is_connected = False
155
160
  self.is_ready = False
156
161
  if self.on_disconnected:
157
- self.on_disconnected()
162
+ future = asyncio.run_coroutine_threadsafe(self.on_disconnected(), self.loop)
163
+ asyncio.wrap_future(future, loop=self.loop)
164
+
158
165
 
159
166
  def _on_message(self, _client, _userdata, message: MQTTMessage):
160
167
  """Is called when message is received."""
@@ -185,3 +192,4 @@ class MammotionMQTT:
185
192
  def get_cloud_client(self) -> Optional[CloudIOTGateway]:
186
193
  """Return internal cloud client."""
187
194
  return self._cloud_client
195
+
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "pymammotion"
3
- version = "0.2.6"
3
+ version = "0.2.7"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "pymammotion"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  license = "GNU-3.0"
9
9
  description = ""
10
10
  readme = "README.md"
@@ -36,6 +36,7 @@ betterproto = "^1.2.5"
36
36
  pyjoystick = "^1.2.4"
37
37
  nest-asyncio = "^1.6.0"
38
38
  numpy = "^1.26.0"
39
+ async-timeout = "^4.0.3"
39
40
 
40
41
 
41
42
  [tool.poetry.group.dev.dependencies]
@@ -52,7 +53,7 @@ mypy = "^1.10.0"
52
53
  pre-commit = "^3.7.1"
53
54
 
54
55
  [tool.bumpver]
55
- current_version = "0.2.6"
56
+ current_version = "0.2.7"
56
57
  version_pattern = "MAJOR.MINOR.PATCH"
57
58
  commit_message = "Bump version {old_version} -> {new_version}"
58
59
  commit = true
File without changes
File without changes