pymammotion 0.2.6__tar.gz → 0.2.8__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.8}/PKG-INFO +2 -1
  2. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/cloud_gateway.py +85 -23
  3. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/session_by_authcode_response.py +4 -4
  4. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/location.py +5 -9
  5. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/state_manager.py +6 -8
  6. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/devices/mammotion.py +67 -60
  7. pymammotion-0.2.8/pymammotion/mqtt/mammotion_future.py +25 -0
  8. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mqtt/mammotion_mqtt.py +19 -11
  9. {pymammotion-0.2.6 → pymammotion-0.2.8}/pyproject.toml +4 -3
  10. {pymammotion-0.2.6 → pymammotion-0.2.8}/LICENSE +0 -0
  11. {pymammotion-0.2.6 → pymammotion-0.2.8}/README.md +0 -0
  12. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/__init__.py +0 -0
  13. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/__init__.py +0 -0
  14. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/cloud_service.py +0 -0
  15. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/aep_response.py +0 -0
  16. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/connect_response.py +0 -0
  17. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/dev_by_account_response.py +0 -0
  18. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/login_by_oauth_response.py +0 -0
  19. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/regions_response.py +0 -0
  20. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/tmp_constant.py +0 -0
  21. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/__init__.py +0 -0
  22. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/ble.py +0 -0
  23. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/ble_message.py +0 -0
  24. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/const.py +0 -0
  25. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/data/__init__.py +0 -0
  26. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/data/convert.py +0 -0
  27. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/data/framectrldata.py +0 -0
  28. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/data/notifydata.py +0 -0
  29. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/const.py +0 -0
  30. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/__init__.py +0 -0
  31. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/__init__.py +0 -0
  32. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/account.py +0 -0
  33. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/device.py +0 -0
  34. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/device_config.py +0 -0
  35. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/enums.py +0 -0
  36. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/excute_boarder_params.py +0 -0
  37. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/execute_boarder.py +0 -0
  38. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/generate_route_information.py +0 -0
  39. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/hash_list.py +0 -0
  40. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/mowing_modes.py +0 -0
  41. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/plan.py +0 -0
  42. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/rapid_state.py +0 -0
  43. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/region_data.py +0 -0
  44. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/report_info.py +0 -0
  45. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/mqtt/__init__.py +0 -0
  46. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/mqtt/event.py +0 -0
  47. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/mqtt/properties.py +0 -0
  48. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/mqtt/status.py +0 -0
  49. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/event/__init__.py +0 -0
  50. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/event/event.py +0 -0
  51. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/http/_init_.py +0 -0
  52. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/http/http.py +0 -0
  53. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/__init__.py +0 -0
  54. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/__init__.py +0 -0
  55. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/abstract_message.py +0 -0
  56. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/mammotion_command.py +0 -0
  57. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/__init__.py +0 -0
  58. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/driver.py +0 -0
  59. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/media.py +0 -0
  60. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/navigation.py +0 -0
  61. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/network.py +0 -0
  62. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/ota.py +0 -0
  63. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/system.py +0 -0
  64. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/video.py +0 -0
  65. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/control/__init__.py +0 -0
  66. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/control/joystick.py +0 -0
  67. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/devices/__init__.py +0 -0
  68. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mqtt/__init__.py +0 -0
  69. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/__init__.py +0 -0
  70. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/basestation.proto +0 -0
  71. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/basestation.py +0 -0
  72. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/basestation_pb2.py +0 -0
  73. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/basestation_pb2.pyi +0 -0
  74. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/common.proto +0 -0
  75. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/common.py +0 -0
  76. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/common_pb2.py +0 -0
  77. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/common_pb2.pyi +0 -0
  78. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/dev_net.proto +0 -0
  79. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/dev_net.py +0 -0
  80. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/dev_net_pb2.py +0 -0
  81. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/dev_net_pb2.pyi +0 -0
  82. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_msg.proto +0 -0
  83. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_msg.py +0 -0
  84. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_msg_pb2.py +0 -0
  85. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_msg_pb2.pyi +0 -0
  86. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_mul.proto +0 -0
  87. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_mul.py +0 -0
  88. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_mul_pb2.py +0 -0
  89. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_mul_pb2.pyi +0 -0
  90. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_driver.proto +0 -0
  91. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_driver.py +0 -0
  92. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_driver_pb2.py +0 -0
  93. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_driver_pb2.pyi +0 -0
  94. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_nav.proto +0 -0
  95. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_nav.py +0 -0
  96. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_nav_pb2.py +0 -0
  97. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_nav_pb2.pyi +0 -0
  98. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_ota.proto +0 -0
  99. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_ota.py +0 -0
  100. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_ota_pb2.py +0 -0
  101. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_ota_pb2.pyi +0 -0
  102. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_pept.proto +0 -0
  103. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_pept.py +0 -0
  104. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_pept_pb2.py +0 -0
  105. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_pept_pb2.pyi +0 -0
  106. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_sys.proto +0 -0
  107. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_sys.py +0 -0
  108. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_sys_pb2.py +0 -0
  109. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_sys_pb2.pyi +0 -0
  110. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/py.typed +0 -0
  111. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/constant/__init__.py +0 -0
  112. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/constant/device_constant.py +0 -0
  113. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/datatype_converter.py +0 -0
  114. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/device_type.py +0 -0
  115. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/map.py +0 -0
  116. {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/periodic.py +0 -0
  117. {pymammotion-0.2.6 → pymammotion-0.2.8}/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.8
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)
@@ -10,6 +10,7 @@ import string
10
10
  import time
11
11
  import uuid
12
12
  from logging import getLogger, exception
13
+ from datetime import datetime
13
14
 
14
15
  from aiohttp import ClientSession
15
16
  from alibabacloud_iot_api_gateway.client import Client
@@ -48,6 +49,9 @@ MOVE_HEADERS = (
48
49
  class SetupException(Exception):
49
50
  pass
50
51
 
52
+ class AuthRefreshException(Exception):
53
+ """Raise exception when library cannot refresh token."""
54
+
51
55
 
52
56
  class CloudIOTGateway:
53
57
  """Class for interacting with Aliyun Cloud IoT Gateway."""
@@ -56,16 +60,18 @@ class CloudIOTGateway:
56
60
  _device_sn = ""
57
61
  _utdid = ""
58
62
 
59
- _connect_response = None
60
- _login_by_oauth_response = None
61
- _aep_response = None
62
- _session_by_authcode_response = None
63
- _listing_dev_by_account_response = None
64
- _region = None
63
+ _connect_response: ConnectResponse | None = None
64
+ _login_by_oauth_response: LoginByOAuthResponse | None = None
65
+ _aep_response: AepResponse | None = None
66
+ _session_by_authcode_response: SessionByAuthCodeResponse | None = None
67
+ _devices_by_account_response: ListingDevByAccountResponse | None = None
68
+ _region_response = None
69
+
70
+ _iot_token_issued_at : int = None
65
71
 
66
72
  converter = DatatypeConverter()
67
73
 
68
- def __init__(self):
74
+ def __init__(self, connect_response: ConnectResponse | None, login_by_oauth_response: LoginByOAuthResponse | None, aep_response: AepResponse | None, session_by_authcode_response: SessionByAuthCodeResponse | None, region_response: RegionResponse | None, dev_by_account: ListingDevByAccountResponse | None):
69
75
  """Initialize the CloudIOTGateway."""
70
76
  self._app_key = APP_KEY
71
77
  self._app_secret = APP_SECRET
@@ -74,6 +80,12 @@ class CloudIOTGateway:
74
80
  self._client_id = self.generate_hardware_string(8) # 8 characters
75
81
  self._device_sn = self.generate_hardware_string(32) # 32 characters
76
82
  self._utdid = self.generate_hardware_string(32) # 32 characters
83
+ self._connect_response = connect_response
84
+ self._login_by_oauth_response = login_by_oauth_response
85
+ self._aep_response = aep_response
86
+ self._session_by_authcode_response = session_by_authcode_response
87
+ self._region_response = region_response
88
+ self._devices_by_account_response = dev_by_account
77
89
 
78
90
  @staticmethod
79
91
  def generate_random_string(length):
@@ -102,6 +114,24 @@ class CloudIOTGateway:
102
114
  hashlib.sha1,
103
115
  ).hexdigest()
104
116
 
117
+ def get_connect_response(self):
118
+ return self._connect_response
119
+
120
+ def get_login_by_oauth_response(self):
121
+ return self._login_by_oauth_response
122
+
123
+ def get_aep_response(self):
124
+ return self._aep_response
125
+
126
+ def get_session_by_authcode_response(self):
127
+ return self._session_by_authcode_response
128
+
129
+ def get_devices_by_account_response(self):
130
+ return self._devices_by_account_response
131
+
132
+ def get_region_response(self):
133
+ return self._region_response
134
+
105
135
  def get_region(self, country_code: str, auth_code: str):
106
136
  """Get the region based on country code and auth code."""
107
137
  config = Config(
@@ -140,8 +170,8 @@ class CloudIOTGateway:
140
170
  if int(response_body_dict.get("code")) != 200:
141
171
  raise Exception("Error in getting regions: " + response_body_dict["msg"])
142
172
 
143
- self._region = RegionResponse.from_dict(response_body_dict)
144
- logger.debug("Endpoint: %s", self._region.data.mqttEndpoint)
173
+ self._region_response = RegionResponse.from_dict(response_body_dict)
174
+ logger.debug("Endpoint: %s", self._region_response.data.mqttEndpoint)
145
175
 
146
176
  return response.body
147
177
 
@@ -149,8 +179,8 @@ class CloudIOTGateway:
149
179
  """Handle AEP authentication."""
150
180
  aep_domain = self.domain
151
181
 
152
- if self._region.data.apiGatewayEndpoint is not None:
153
- aep_domain = self._region.data.apiGatewayEndpoint
182
+ if self._region_response.data.apiGatewayEndpoint is not None:
183
+ aep_domain = self._region_response.data.apiGatewayEndpoint
154
184
 
155
185
  config = Config(
156
186
  app_key=self._app_key,
@@ -275,7 +305,7 @@ class CloudIOTGateway:
275
305
 
276
306
  async def login_by_oauth(self, country_code: str, auth_code: str):
277
307
  """Login by OAuth."""
278
- region_url = self._region.data.oaApiGatewayEndpoint
308
+ region_url = self._region_response.data.oaApiGatewayEndpoint
279
309
 
280
310
  async with ClientSession() as session:
281
311
  headers = {
@@ -347,7 +377,7 @@ class CloudIOTGateway:
347
377
  config = Config(
348
378
  app_key=self._app_key,
349
379
  app_secret=self._app_secret,
350
- domain=self._region.data.apiGatewayEndpoint,
380
+ domain=self._region_response.data.apiGatewayEndpoint,
351
381
  )
352
382
  client = Client(config)
353
383
 
@@ -390,15 +420,17 @@ class CloudIOTGateway:
390
420
  raise Exception("Error in creating session: " + response_body_dict["msg"])
391
421
 
392
422
  self._session_by_authcode_response = SessionByAuthCodeResponse.from_dict(response_body_dict)
423
+ self._iot_token_issued_at = int(time.time())
393
424
 
394
425
  return response.body
395
426
 
396
427
  def check_or_refresh_session(self):
397
428
  """Check or refresh the session."""
429
+ logger.debug("Try to refresh token")
398
430
  config = Config(
399
431
  app_key=self._app_key,
400
432
  app_secret=self._app_secret,
401
- domain=self._region.data.apiGatewayEndpoint,
433
+ domain=self._region_response.data.apiGatewayEndpoint,
402
434
  )
403
435
  client = Client(config)
404
436
 
@@ -431,19 +463,36 @@ class CloudIOTGateway:
431
463
  logger.debug(response.status_code)
432
464
  logger.debug(response.body)
433
465
 
434
- # self._region = response.body.data
435
- # Decodifica il corpo della risposta
466
+ # Decode the response body
436
467
  response_body_str = response.body.decode("utf-8")
437
468
 
438
- # Carica la stringa JSON in un dizionario
439
- json.loads(response_body_str)
469
+ # Load the JSON string into a dictionary
470
+ response_body_dict = json.loads(response_body_str)
471
+
472
+ if int(response_body_dict.get("code")) != 200:
473
+ raise Exception("Error check or refresh token: " + response_body_dict.get('msg', ''))
474
+
475
+ identityId = response_body_dict.get('data', {}).get('identityId', None)
476
+ refreshTokenExpire = response_body_dict.get('data', {}).get('refreshTokenExpire', None)
477
+ iotToken = response_body_dict.get('data', {}).get('iotToken', None)
478
+ iotTokenExpire = response_body_dict.get('data', {}).get('iotTokenExpire', None)
479
+ refreshToken = response_body_dict.get('data', {}).get('refreshToken', None)
480
+
481
+
482
+ if (identityId is None or refreshTokenExpire is None or iotToken is None or iotTokenExpire is None or refreshToken is None):
483
+ raise Exception("Error check or refresh token: Parameters not correct")
484
+
485
+ self._session_by_authcode_response = SessionByAuthCodeResponse.from_dict(response_body_dict)
486
+ self._iot_token_issued_at = int(time.time())
487
+
488
+
440
489
 
441
490
  def list_binding_by_account(self) -> ListingDevByAccountResponse:
442
491
  """List bindings by account."""
443
492
  config = Config(
444
493
  app_key=self._app_key,
445
494
  app_secret=self._app_secret,
446
- domain=self._region.data.apiGatewayEndpoint,
495
+ domain=self._region_response.data.apiGatewayEndpoint,
447
496
  )
448
497
 
449
498
  client = Client(config)
@@ -477,15 +526,26 @@ class CloudIOTGateway:
477
526
  if int(response_body_dict.get("code")) != 200:
478
527
  raise Exception("Error in creating session: " + response_body_dict["msg"])
479
528
 
480
- self._listing_dev_by_account_response = ListingDevByAccountResponse.from_dict(response_body_dict)
481
- return self._listing_dev_by_account_response
529
+ self._devices_by_account_response = ListingDevByAccountResponse.from_dict(response_body_dict)
530
+ return self._devices_by_account_response
482
531
 
483
532
  def send_cloud_command(self, iot_id: str, command: bytes) -> str:
484
533
  """Send a cloud command to the specified IoT device."""
534
+
535
+ """Check if iotToken is expired"""
536
+ if self._iot_token_issued_at + self._session_by_authcode_response.data.iotTokenExpire <= (int(time.time()) + (5 * 3600)):
537
+ """Token expired - Try to refresh - Check if refreshToken is not expired"""
538
+ if self._iot_token_issued_at + self._session_by_authcode_response.data.refreshTokenExpire > (int(time.time())):
539
+ self.check_or_refresh_session()
540
+ else:
541
+ raise AuthRefreshException("Refresh token expired. Please re-login")
542
+
543
+
544
+
485
545
  config = Config(
486
546
  app_key=self._app_key,
487
547
  app_secret=self._app_secret,
488
- domain=self._region.data.apiGatewayEndpoint,
548
+ domain=self._region_response.data.apiGatewayEndpoint,
489
549
  )
490
550
 
491
551
  client = Client(config)
@@ -530,9 +590,11 @@ class CloudIOTGateway:
530
590
  )
531
591
  if response_body_dict.get("code") == 29003:
532
592
  raise SetupException(response_body_dict.get("code"))
593
+ if response_body_dict.get("code") == 6205:
594
+ """Device is offline."""
533
595
 
534
596
  return message_id
535
597
 
536
598
  @property
537
599
  def listing_dev_by_account_response(self):
538
- return self._listing_dev_by_account_response
600
+ return self._devices_by_account_response
@@ -1,18 +1,18 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
+ from datetime import time
2
3
 
3
4
  from mashumaro.mixins.orjson import DataClassORJSONMixin
4
5
 
5
6
 
6
7
  @dataclass
7
- class TokenData(DataClassORJSONMixin):
8
+ class SessionOauthToken(DataClassORJSONMixin):
8
9
  identityId: str
9
10
  refreshTokenExpire: int
10
11
  iotToken: str
11
12
  iotTokenExpire: int
12
13
  refreshToken: str
13
14
 
14
-
15
15
  @dataclass
16
16
  class SessionByAuthCodeResponse(DataClassORJSONMixin):
17
17
  code: int
18
- data: TokenData
18
+ data: SessionOauthToken
@@ -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,11 +19,7 @@ class Point:
19
19
  class Dock(Point):
20
20
  """Stores robot dock position."""
21
21
 
22
- rotation: int
23
-
24
- def __init__(self):
25
- super().__init__()
26
- self.rotation = 0
22
+ rotation: int = 0
27
23
 
28
24
 
29
25
  @dataclass
@@ -33,8 +29,8 @@ class Location:
33
29
  device: Point
34
30
  RTK: Point
35
31
  dock: Dock
36
- position_type: int
37
- orientation: int # 360 degree rotation +-
32
+ position_type: int = 0
33
+ orientation: int = 0 # 360 degree rotation +-
38
34
 
39
35
  def __init__(self):
40
36
  self.device = Point()
@@ -1,24 +1,22 @@
1
1
  """Manage state from notifications into MowingDevice."""
2
+ from typing import Optional, Callable, Awaitable
2
3
 
3
4
  import betterproto
4
5
 
5
6
  from pymammotion.data.model.device import MowingDevice
6
- from pymammotion.event.event import DataEvent
7
7
  from pymammotion.proto.luba_msg import LubaMsg
8
- from pymammotion.proto.mctrl_nav import NavGetCommDataAck
8
+ from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck
9
9
 
10
10
 
11
11
  class StateManager:
12
12
  """Manage state."""
13
13
 
14
14
  _device: MowingDevice
15
- gethash_ack_callback: DataEvent
16
- get_commondata_ack_callback: DataEvent
17
15
 
18
16
  def __init__(self, device: MowingDevice):
19
17
  self._device = device
20
- self.gethash_ack_callback = DataEvent()
21
- self.get_commondata_ack_callback = DataEvent()
18
+ self.gethash_ack_callback: Optional[Callable[[NavGetHashListAck],Awaitable[None]]] = None
19
+ self.get_commondata_ack_callback: Optional[Callable[[NavGetCommDataAck],Awaitable[None]]] = None
22
20
 
23
21
  def get_device(self) -> MowingDevice:
24
22
  """Get device."""
@@ -54,12 +52,12 @@ class StateManager:
54
52
  self._device.map.obstacle = dict()
55
53
  self._device.map.area = dict()
56
54
  self._device.map.path = dict()
57
- await self.gethash_ack_callback.data_event(nav_msg[1])
55
+ await self.gethash_ack_callback(nav_msg[1])
58
56
  case "toapp_get_commondata_ack":
59
57
  common_data: NavGetCommDataAck = nav_msg[1]
60
58
  updated = self._device.map.update(common_data)
61
59
  if updated:
62
- await self.get_commondata_ack_callback.data_event(common_data)
60
+ await self.get_commondata_ack_callback(common_data)
63
61
 
64
62
  def _update_sys_data(self, message):
65
63
  """Update system."""
@@ -2,15 +2,18 @@
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
- from typing import Any, Callable, Optional, cast
16
+ from typing import Any, Callable, Optional, cast, Awaitable
14
17
  from uuid import UUID
15
18
 
16
19
  import betterproto
@@ -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):
@@ -208,7 +212,8 @@ class Mammotion(object):
208
212
  """Represents a Mammotion device."""
209
213
 
210
214
  devices = MammotionDevices()
211
- _mammotion_mqtt: MammotionMQTT | None = None
215
+ cloud_client: CloudIOTGateway | None = None
216
+ mqtt: MammotionMQTT | None = None
212
217
 
213
218
 
214
219
 
@@ -225,25 +230,25 @@ class Mammotion(object):
225
230
  self._preference = preference
226
231
 
227
232
  async def initiate_cloud_connection(self, cloud_client: CloudIOTGateway) -> None:
228
- if self._mammotion_mqtt is not None:
229
- if self._mammotion_mqtt.is_connected:
233
+ if self.mqtt is not None:
234
+ if self.mqtt.is_connected:
230
235
  return
231
236
 
232
-
233
- self._mammotion_mqtt = MammotionMQTT(region_id=cloud_client._region.data.regionId,
237
+ self.cloud_client = cloud_client
238
+ self.mqtt = MammotionMQTT(region_id=cloud_client._region.data.regionId,
234
239
  product_key=cloud_client._aep_response.data.productKey,
235
240
  device_name=cloud_client._aep_response.data.deviceName,
236
241
  device_secret=cloud_client._aep_response.data.deviceSecret,
237
242
  iot_token=cloud_client._session_by_authcode_response.data.iotToken,
238
243
  client_id=cloud_client._client_id)
239
244
 
240
- self._mammotion_mqtt._cloud_client = cloud_client
245
+ self.mqtt._cloud_client = cloud_client
241
246
  loop = asyncio.get_running_loop()
242
- await loop.run_in_executor(None, self._mammotion_mqtt.connect_async)
247
+ await loop.run_in_executor(None, self.mqtt.connect_async)
243
248
 
244
249
  for device in cloud_client.listing_dev_by_account_response.data.data:
245
250
  if device.deviceName.startswith(("Luba-", "Yuka-")):
246
- self.devices.add_device(MammotionMixedDeviceManager(name=device.deviceName, cloud_device=device, mqtt=self._mammotion_mqtt))
251
+ self.devices.add_device(MammotionMixedDeviceManager(name=device.deviceName, cloud_device=device, mqtt=self.mqtt))
247
252
 
248
253
  def set_disconnect_strategy(self, disconnect: bool):
249
254
  for device_name, device in self.devices.devices:
@@ -334,9 +339,8 @@ class MammotionBaseDevice:
334
339
  self._raw_data = LubaMsg().to_dict(casing=betterproto.Casing.SNAKE)
335
340
  self._mower = device
336
341
  self._state_manager = StateManager(self._mower)
337
-
338
- self._state_manager.gethash_ack_callback.add_subscribers(self.datahash_response)
339
- self._state_manager.get_commondata_ack_callback.add_subscribers(self.commdata_response)
342
+ self._state_manager.gethash_ack_callback = self.datahash_response
343
+ self._state_manager.get_commondata_ack_callback = self.commdata_response
340
344
  self._notify_future: asyncio.Future[bytes] | None = None
341
345
 
342
346
  async def datahash_response(self, hash_ack: NavGetHashListAck):
@@ -944,8 +948,9 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
944
948
  self._command_futures = {}
945
949
  self._commands: MammotionCommand = MammotionCommand(cloud_device.deviceName)
946
950
  self.currentID = ""
947
- self.on_ready_callback: Optional[Callable[[], None]] = None
948
- self._operation_lock = asyncio.Lock()
951
+ self.on_ready_callback: Optional[Callable[[], Awaitable[None]]] = None
952
+ self._waiting_queue = deque()
953
+ self._operation_lock = threading.Lock()
949
954
 
950
955
  self._mqtt_client.on_connected = self.on_connected
951
956
  self._mqtt_client.on_disconnected = self.on_disconnected
@@ -958,19 +963,19 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
958
963
  # temporary for testing only
959
964
  # self._start_sync_task = self.loop.call_later(30, lambda: asyncio.ensure_future(self.start_sync(0)))
960
965
 
961
- def on_ready(self):
966
+ async def on_ready(self):
962
967
  """Callback for when MQTT is subscribed to events."""
963
968
  if self.on_ready_callback:
964
969
  self.on_ready_callback()
965
970
 
966
- asyncio.run(self._ble_sync())
967
- asyncio.run(self.run_periodic_sync_task())
971
+ await self._ble_sync()
972
+ await self.run_periodic_sync_task()
968
973
 
969
- def on_connected(self):
974
+ async def on_connected(self):
970
975
  """Callback for when MQTT connects."""
971
976
 
972
977
 
973
- def on_disconnected(self):
978
+ async def on_disconnected(self):
974
979
  """Callback for when MQTT disconnects."""
975
980
 
976
981
  async def _ble_sync(self):
@@ -993,21 +998,23 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
993
998
  160, lambda: asyncio.ensure_future(self.run_periodic_sync_task())
994
999
  )
995
1000
 
996
- def _on_mqtt_message(self, topic: str, payload: str, iot_id: str) -> None:
1001
+ async def _on_mqtt_message(self, topic: str, payload: str, iot_id: str) -> None:
997
1002
  """Handle incoming MQTT messages."""
998
- _LOGGER.debug("MQTT message received on topic %s: %s", topic, payload)
1003
+ _LOGGER.debug("MQTT message received on topic %s: %s, iot_id: %s", topic, payload, iot_id)
999
1004
 
1000
1005
  json_str = json.dumps(payload)
1001
1006
  payload = json.loads(json_str)
1002
1007
 
1003
- self._handle_mqtt_message(topic, payload)
1008
+ await self._handle_mqtt_message(topic, payload)
1004
1009
 
1005
1010
  async def _send_command(self, key: str, retry: int | None = None) -> bytes | None:
1006
1011
  """Send command to device via MQTT and read response."""
1007
1012
  if self._operation_lock.locked():
1008
- _LOGGER.debug("%s: Operation already in progress, waiting for it to complete;", self.device.nickName)
1009
-
1010
- async with self._operation_lock:
1013
+ _LOGGER.debug(
1014
+ "%s: Operation already in progress, waiting for it to complete;",
1015
+ self.device.nickName
1016
+ )
1017
+ with self._operation_lock:
1011
1018
  try:
1012
1019
  command_bytes = getattr(self._commands, key)()
1013
1020
  return await self._send_command_locked(key, command_bytes)
@@ -1032,33 +1039,16 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1032
1039
  async def _execute_command_locked(self, key: str, command: bytes) -> bytes:
1033
1040
  """Execute command and read response."""
1034
1041
  assert self._mqtt_client is not None
1035
- self._notify_future = self.loop.create_future()
1036
1042
  self._key = key
1037
1043
  _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
- )
1044
+ await self.loop.run_in_executor(None, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command)
1045
+ future = MammotionFuture()
1046
+ self._waiting_queue.append(future)
1049
1047
  timeout = 20
1050
- timeout_handle = self.loop.call_at(self.loop.time() + timeout, _handle_timeout, self._notify_future)
1051
- timeout_expired = False
1052
1048
  try:
1053
- notify_msg = await self._notify_future
1049
+ notify_msg = await future.async_get(timeout)
1054
1050
  except asyncio.TimeoutError:
1055
- timeout_expired = True
1056
1051
  raise
1057
- finally:
1058
- if not timeout_expired:
1059
- timeout_handle.cancel()
1060
- retry_handle.cancel()
1061
- self._notify_future = None
1062
1052
 
1063
1053
  _LOGGER.debug("%s: Message received", self.device.nickName)
1064
1054
 
@@ -1067,8 +1057,11 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1067
1057
  async def _send_command_with_args(self, key: str, **kwargs: any) -> bytes | None:
1068
1058
  """Send command with arguments to device via MQTT and read response."""
1069
1059
  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:
1060
+ _LOGGER.debug(
1061
+ "%s: Operation already in progress, waiting for it to complete;",
1062
+ self.device.nickName
1063
+ )
1064
+ with self._operation_lock:
1072
1065
  try:
1073
1066
  command_bytes = getattr(self._commands, key)(**kwargs)
1074
1067
  return await self._send_command_locked(key, command_bytes)
@@ -1089,7 +1082,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1089
1082
  _LOGGER.error("Error extracting encoded message. Payload: %s", payload)
1090
1083
  return ""
1091
1084
 
1092
- def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
1085
+ async def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
1093
1086
  """Parse the MQTT response."""
1094
1087
  if topic.endswith("/app/down/thing/events"):
1095
1088
  _LOGGER.debug("Thing event received")
@@ -1100,14 +1093,28 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
1100
1093
  binary_data = base64.b64decode(params.value.get("content", ""))
1101
1094
  self._update_raw_data(cast(bytes, binary_data))
1102
1095
  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
1096
 
1107
- def _handle_mqtt_message(self, topic: str, payload: dict) -> None:
1097
+ if self._commands.get_device_product_key() == "" and self._commands.get_device_name() == event.params.deviceName:
1098
+ self._commands.set_device_product_key(event.params.productKey)
1099
+
1100
+ if betterproto.serialized_on_wire(new_msg.net):
1101
+ if new_msg.net.todev_ble_sync != 0 or has_field(new_msg.net.toapp_wifi_iot_status):
1102
+ return
1103
+
1104
+
1105
+ if len(self._waiting_queue) > 0:
1106
+ fut: MammotionFuture = self._waiting_queue.popleft()
1107
+ while fut.fut.cancelled():
1108
+ fut: MammotionFuture = self._waiting_queue.popleft()
1109
+ fut.resolve(cast(bytes, binary_data))
1110
+ await self._state_manager.notification(new_msg)
1111
+
1112
+ async def _handle_mqtt_message(self, topic: str, payload: dict) -> None:
1108
1113
  """Async handler for incoming MQTT messages."""
1109
- self._parse_mqtt_response(topic=topic, payload=payload)
1114
+ await self._parse_mqtt_response(topic=topic, payload=payload)
1110
1115
 
1111
- async def _disconnect(self):
1116
+ def _disconnect(self):
1112
1117
  """Disconnect the MQTT client."""
1113
1118
  self._mqtt_client.disconnect()
1119
+
1120
+
@@ -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,8 @@ 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
+ if self._linkkit_client.check_state() is LinkKit.LinkKitState.INITIALIZED:
84
+ self._linkkit_client.thing_setup()
83
85
  self._linkkit_client.connect_async()
84
86
 
85
87
 
@@ -121,7 +123,7 @@ class MammotionMQTT:
121
123
 
122
124
  if self.on_ready:
123
125
  self.is_ready = True
124
- self.on_ready()
126
+ asyncio.run_coroutine_threadsafe(self.on_ready(), self.loop).result()
125
127
  # self._linkkit_client.query_ota_firmware()
126
128
  # command = MammotionCommand(device_name="Luba")
127
129
  # self._cloud_client.send_cloud_command(command.get_report_cfg())
@@ -137,13 +139,16 @@ class MammotionMQTT:
137
139
  payload = json.loads(payload)
138
140
  iot_id = payload.get("params", {}).get("iotId", "")
139
141
  if iot_id != "" and self.on_message:
140
- self.on_message(topic, payload, iot_id)
142
+ asyncio.run_coroutine_threadsafe(self.on_message(topic, payload, iot_id), self.loop).result()
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.8"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "pymammotion"
7
- version = "0.2.6"
7
+ version = "0.2.8"
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.8"
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