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.
- {pymammotion-0.2.6 → pymammotion-0.2.8}/PKG-INFO +2 -1
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/cloud_gateway.py +85 -23
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/session_by_authcode_response.py +4 -4
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/location.py +5 -9
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/state_manager.py +6 -8
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/devices/mammotion.py +67 -60
- pymammotion-0.2.8/pymammotion/mqtt/mammotion_future.py +25 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mqtt/mammotion_mqtt.py +19 -11
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pyproject.toml +4 -3
- {pymammotion-0.2.6 → pymammotion-0.2.8}/LICENSE +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/README.md +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/cloud_service.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/aep_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/connect_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/dev_by_account_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/login_by_oauth_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/regions_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/tmp_constant.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/ble.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/ble_message.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/const.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/data/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/data/convert.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/data/framectrldata.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/bluetooth/data/notifydata.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/const.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/account.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/device.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/device_config.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/enums.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/excute_boarder_params.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/execute_boarder.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/generate_route_information.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/hash_list.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/mowing_modes.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/plan.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/rapid_state.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/region_data.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/report_info.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/mqtt/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/mqtt/event.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/mqtt/properties.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/mqtt/status.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/event/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/event/event.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/http/_init_.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/http/http.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/abstract_message.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/mammotion_command.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/driver.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/media.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/navigation.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/network.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/ota.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/system.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/video.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/control/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/control/joystick.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/devices/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mqtt/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/basestation.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/basestation.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/basestation_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/basestation_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/common.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/common.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/common_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/common_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/dev_net.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/dev_net.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/dev_net_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/dev_net_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_msg.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_msg.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_msg_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_msg_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_mul.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_mul.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_mul_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/luba_mul_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_driver.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_driver.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_driver_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_driver_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_nav.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_nav.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_nav_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_nav_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_ota.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_ota.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_ota_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_ota_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_pept.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_pept.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_pept_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_pept_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_sys.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_sys.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_sys_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/proto/mctrl_sys_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/py.typed +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/constant/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/constant/device_constant.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/datatype_converter.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/device_type.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/map.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/utility/periodic.py +0 -0
- {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.
|
|
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
|
-
|
|
64
|
-
|
|
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.
|
|
144
|
-
logger.debug("Endpoint: %s", self.
|
|
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.
|
|
153
|
-
aep_domain = self.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
#
|
|
435
|
-
# Decodifica il corpo della risposta
|
|
466
|
+
# Decode the response body
|
|
436
467
|
response_body_str = response.body.decode("utf-8")
|
|
437
468
|
|
|
438
|
-
#
|
|
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.
|
|
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.
|
|
481
|
-
return self.
|
|
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.
|
|
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.
|
|
600
|
+
return self._devices_by_account_response
|
{pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/session_by_authcode_response.py
RENAMED
|
@@ -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
|
|
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:
|
|
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
|
|
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 =
|
|
21
|
-
self.get_commondata_ack_callback =
|
|
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
|
|
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
|
|
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],
|
|
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(
|
|
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,
|
|
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
|
-
|
|
123
|
-
await loop.run_in_executor(None, func,
|
|
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
|
-
|
|
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.
|
|
229
|
-
if self.
|
|
233
|
+
if self.mqtt is not None:
|
|
234
|
+
if self.mqtt.is_connected:
|
|
230
235
|
return
|
|
231
236
|
|
|
232
|
-
|
|
233
|
-
self.
|
|
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.
|
|
245
|
+
self.mqtt._cloud_client = cloud_client
|
|
241
246
|
loop = asyncio.get_running_loop()
|
|
242
|
-
await loop.run_in_executor(None, self.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
967
|
-
|
|
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(
|
|
1009
|
-
|
|
1010
|
-
|
|
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
|
|
1039
|
-
|
|
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
|
|
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(
|
|
1071
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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[[],
|
|
40
|
-
self.on_ready: Optional[Callable[[],
|
|
41
|
-
self.on_error: Optional[Callable[[str],
|
|
42
|
-
self.on_disconnected: Optional[Callable[[],
|
|
43
|
-
self.on_message: Optional[Callable[[str, str, str],
|
|
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.
|
|
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.
|
|
3
|
+
version = "0.2.8"
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "pymammotion"
|
|
7
|
-
version = "0.2.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/dev_by_account_response.py
RENAMED
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/aliyun/dataclass/login_by_oauth_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/data/model/generate_route_information.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.8}/pymammotion/mammotion/commands/messages/navigation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|