pymammotion 0.0.37__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymammotion might be problematic. Click here for more details.
- pymammotion/__init__.py +43 -0
- pymammotion/aliyun/cloud_gateway.py +549 -0
- pymammotion/aliyun/cloud_service.py +65 -0
- pymammotion/aliyun/dataclass/aep_response.py +18 -0
- pymammotion/aliyun/dataclass/connect_response.py +51 -0
- pymammotion/aliyun/dataclass/dev_by_account_response.py +43 -0
- pymammotion/aliyun/dataclass/login_by_oauth_response.py +65 -0
- pymammotion/aliyun/dataclass/regions_response.py +26 -0
- pymammotion/aliyun/dataclass/session_by_authcode_response.py +18 -0
- pymammotion/aliyun/tmp_constant.py +175 -0
- pymammotion/bluetooth/__init__.py +1 -0
- pymammotion/bluetooth/ble.py +74 -0
- pymammotion/bluetooth/ble_message.py +430 -0
- pymammotion/bluetooth/const.py +27 -0
- pymammotion/bluetooth/data/__init__.py +0 -0
- pymammotion/bluetooth/data/convert.py +26 -0
- pymammotion/bluetooth/data/framectrldata.py +40 -0
- pymammotion/bluetooth/data/notifydata.py +63 -0
- pymammotion/const.py +9 -0
- pymammotion/data/__init__.py +0 -0
- pymammotion/data/model/__init__.py +8 -0
- pymammotion/data/model/device.py +157 -0
- pymammotion/data/model/enums.py +67 -0
- pymammotion/data/model/excute_boarder_params.py +48 -0
- pymammotion/data/model/execute_boarder.py +36 -0
- pymammotion/data/model/generate_route_information.py +133 -0
- pymammotion/data/model/hash_list.py +17 -0
- pymammotion/data/model/mowing_modes.py +37 -0
- pymammotion/data/model/plan.py +58 -0
- pymammotion/data/model/rapid_state.py +45 -0
- pymammotion/data/model/region_data.py +99 -0
- pymammotion/data/mqtt/__init__.py +1 -0
- pymammotion/data/mqtt/event.py +90 -0
- pymammotion/data/mqtt/properties.py +140 -0
- pymammotion/data/mqtt/status.py +52 -0
- pymammotion/event/__init__.py +6 -0
- pymammotion/event/event.py +50 -0
- pymammotion/http/_init_.py +0 -0
- pymammotion/http/http.py +76 -0
- pymammotion/luba/_init_.py +0 -0
- pymammotion/luba/base.py +52 -0
- pymammotion/mammotion/__init__.py +0 -0
- pymammotion/mammotion/commands/__init__.py +0 -0
- pymammotion/mammotion/commands/abstract_message.py +7 -0
- pymammotion/mammotion/commands/mammotion_command.py +34 -0
- pymammotion/mammotion/commands/messages/__init__.py +0 -0
- pymammotion/mammotion/commands/messages/driver.py +108 -0
- pymammotion/mammotion/commands/messages/media.py +36 -0
- pymammotion/mammotion/commands/messages/navigation.py +535 -0
- pymammotion/mammotion/commands/messages/network.py +236 -0
- pymammotion/mammotion/commands/messages/ota.py +34 -0
- pymammotion/mammotion/commands/messages/system.py +266 -0
- pymammotion/mammotion/commands/messages/video.py +27 -0
- pymammotion/mammotion/control/__init__.py +0 -0
- pymammotion/mammotion/control/joystick.py +184 -0
- pymammotion/mammotion/devices/__init__.py +1 -0
- pymammotion/mammotion/devices/luba.py +564 -0
- pymammotion/mqtt/mqtt.py +230 -0
- pymammotion/proto/__init__.py +0 -0
- pymammotion/proto/common.proto +7 -0
- pymammotion/proto/common.py +12 -0
- pymammotion/proto/common_pb2.py +25 -0
- pymammotion/proto/common_pb2.pyi +13 -0
- pymammotion/proto/dev_net.proto +297 -0
- pymammotion/proto/dev_net.py +381 -0
- pymammotion/proto/dev_net_pb2.py +107 -0
- pymammotion/proto/dev_net_pb2.pyi +472 -0
- pymammotion/proto/luba_msg.proto +73 -0
- pymammotion/proto/luba_msg.py +80 -0
- pymammotion/proto/luba_msg_pb2.py +40 -0
- pymammotion/proto/luba_msg_pb2.pyi +93 -0
- pymammotion/proto/luba_mul.proto +68 -0
- pymammotion/proto/luba_mul.py +76 -0
- pymammotion/proto/luba_mul_pb2.py +45 -0
- pymammotion/proto/luba_mul_pb2.pyi +91 -0
- pymammotion/proto/mctrl_driver.proto +67 -0
- pymammotion/proto/mctrl_driver.py +100 -0
- pymammotion/proto/mctrl_driver_pb2.py +45 -0
- pymammotion/proto/mctrl_driver_pb2.pyi +112 -0
- pymammotion/proto/mctrl_nav.proto +485 -0
- pymammotion/proto/mctrl_nav.py +589 -0
- pymammotion/proto/mctrl_nav_pb2.py +116 -0
- pymammotion/proto/mctrl_nav_pb2.pyi +875 -0
- pymammotion/proto/mctrl_ota.proto +42 -0
- pymammotion/proto/mctrl_ota.py +48 -0
- pymammotion/proto/mctrl_ota_pb2.py +35 -0
- pymammotion/proto/mctrl_ota_pb2.pyi +65 -0
- pymammotion/proto/mctrl_pept.proto +29 -0
- pymammotion/proto/mctrl_pept.py +41 -0
- pymammotion/proto/mctrl_pept_pb2.py +31 -0
- pymammotion/proto/mctrl_pept_pb2.pyi +50 -0
- pymammotion/proto/mctrl_sys.proto +487 -0
- pymammotion/proto/mctrl_sys.py +574 -0
- pymammotion/proto/mctrl_sys_pb2.py +142 -0
- pymammotion/proto/mctrl_sys_pb2.pyi +787 -0
- pymammotion/py.typed +0 -0
- pymammotion/utility/constant/__init__.py +1 -0
- pymammotion/utility/constant/device_constant.py +238 -0
- pymammotion/utility/datatype_converter.py +80 -0
- pymammotion/utility/device_type.py +152 -0
- pymammotion/utility/periodic.py +41 -0
- pymammotion/utility/rocker_util.py +135 -0
- pymammotion-0.0.37.dist-info/LICENSE +674 -0
- pymammotion-0.0.37.dist-info/METADATA +92 -0
- pymammotion-0.0.37.dist-info/RECORD +106 -0
- pymammotion-0.0.37.dist-info/WHEEL +4 -0
pymammotion/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
|
|
3
|
+
# version of Luba API
|
|
4
|
+
# TODO export the three interface types
|
|
5
|
+
__version__ = "0.0.5"
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
# works outside HA on its own
|
|
12
|
+
from pyluba.bluetooth.ble import LubaBLE
|
|
13
|
+
from pyluba.http.http import LubaHTTP, connect_http
|
|
14
|
+
|
|
15
|
+
# TODO make a working device that will work outside HA too.
|
|
16
|
+
from pyluba.mammotion.devices import MammotionBaseBLEDevice
|
|
17
|
+
from pyluba.mqtt.mqtt import LubaMQTT, logger
|
|
18
|
+
|
|
19
|
+
# TODO provide interface to pick between mqtt/cloud/bluetooth
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
"""Values are generated from calls to aliyun APIs, can find what order is required in the login_test.py."""
|
|
23
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
24
|
+
logger.getChild("paho").setLevel(logging.WARNING)
|
|
25
|
+
PRODUCT_KEY = os.environ.get("PRODUCT_KEY")
|
|
26
|
+
DEVICE_NAME = os.environ.get("DEVICE_NAME")
|
|
27
|
+
DEVICE_SECRET = os.environ.get("DEVICE_SECRET")
|
|
28
|
+
CLIENT_ID = os.environ.get("CLIENT_ID")
|
|
29
|
+
IOT_TOKEN = os.environ.get("IOT_TOKEN")
|
|
30
|
+
REGION = os.environ.get("REGION")
|
|
31
|
+
luba = LubaMQTT(
|
|
32
|
+
iot_token=IOT_TOKEN,
|
|
33
|
+
region_id=REGION,
|
|
34
|
+
product_key=PRODUCT_KEY,
|
|
35
|
+
device_name=DEVICE_NAME,
|
|
36
|
+
device_secret=DEVICE_SECRET,
|
|
37
|
+
client_id=CLIENT_ID,
|
|
38
|
+
)
|
|
39
|
+
luba.connect_async()
|
|
40
|
+
|
|
41
|
+
event_loop = asyncio.new_event_loop()
|
|
42
|
+
asyncio.set_event_loop(event_loop)
|
|
43
|
+
event_loop.run_forever()
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import hmac
|
|
4
|
+
import itertools
|
|
5
|
+
import json
|
|
6
|
+
import random
|
|
7
|
+
import string
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from logging import getLogger
|
|
11
|
+
|
|
12
|
+
from aiohttp import ClientSession
|
|
13
|
+
from alibabacloud_iot_api_gateway.client import Client
|
|
14
|
+
from alibabacloud_iot_api_gateway.models import CommonParams, Config, IoTApiRequest
|
|
15
|
+
from alibabacloud_tea_util.client import Client as UtilClient
|
|
16
|
+
from alibabacloud_tea_util.models import RuntimeOptions
|
|
17
|
+
|
|
18
|
+
from pyluba.aliyun.dataclass.aep_response import AepResponse
|
|
19
|
+
from pyluba.aliyun.dataclass.connect_response import ConnectResponse
|
|
20
|
+
from pyluba.aliyun.dataclass.dev_by_account_response import ListingDevByAccountResponse
|
|
21
|
+
from pyluba.aliyun.dataclass.login_by_oauth_response import LoginByOAuthResponse
|
|
22
|
+
from pyluba.aliyun.dataclass.regions_response import RegionResponse
|
|
23
|
+
from pyluba.aliyun.dataclass.session_by_authcode_response import (
|
|
24
|
+
SessionByAuthCodeResponse,
|
|
25
|
+
)
|
|
26
|
+
from pyluba.const import ALIYUN_DOMAIN, APP_KEY, APP_SECRET, APP_VERSION
|
|
27
|
+
from pyluba.utility.datatype_converter import DatatypeConverter
|
|
28
|
+
|
|
29
|
+
logger = getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
# init client
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
MOVE_HEADERS = (
|
|
35
|
+
"x-ca-signature",
|
|
36
|
+
"x-ca-signature-headers",
|
|
37
|
+
"accept",
|
|
38
|
+
"content-md5",
|
|
39
|
+
"content-type",
|
|
40
|
+
"date",
|
|
41
|
+
"host",
|
|
42
|
+
"token",
|
|
43
|
+
"user-agent",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CloudIOTGateway:
|
|
48
|
+
_client_id = ""
|
|
49
|
+
_device_sn = ""
|
|
50
|
+
_utdid = ""
|
|
51
|
+
|
|
52
|
+
_connect_response = None
|
|
53
|
+
_login_by_oauth_response = None
|
|
54
|
+
_aep_response = None
|
|
55
|
+
_session_by_authcode_response = None
|
|
56
|
+
_listing_dev_by_account_response = None
|
|
57
|
+
_region = None
|
|
58
|
+
|
|
59
|
+
converter = DatatypeConverter()
|
|
60
|
+
|
|
61
|
+
def __init__(self):
|
|
62
|
+
self._app_key = APP_KEY
|
|
63
|
+
self._app_secret = APP_SECRET
|
|
64
|
+
self.domain = ALIYUN_DOMAIN
|
|
65
|
+
|
|
66
|
+
self._client_id = self.generate_hardware_string(8) # 8 charatters
|
|
67
|
+
self._device_sn = self.generate_hardware_string(32) # 32 charatters
|
|
68
|
+
self._utdid = self.generate_hardware_string(32) # 32 charatters
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def generate_random_string(length):
|
|
72
|
+
characters = string.ascii_letters + string.digits
|
|
73
|
+
random_string = "".join(random.choice(characters) for _ in range(length))
|
|
74
|
+
return random_string
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def generate_hardware_string(length) -> str:
|
|
78
|
+
"""Generate hardware string that is consistent per device."""
|
|
79
|
+
hashed_uuid = hashlib.sha1(f"{uuid.getnode()}".encode()).hexdigest()
|
|
80
|
+
return "".join(itertools.islice(itertools.cycle(hashed_uuid), length))
|
|
81
|
+
|
|
82
|
+
def sign(self, data):
|
|
83
|
+
keys = ["appKey", "clientId", "deviceSn", "timestamp"]
|
|
84
|
+
concatenated_str = ""
|
|
85
|
+
for key in keys:
|
|
86
|
+
concatenated_str += f"{key}{data.get(key, '')}"
|
|
87
|
+
|
|
88
|
+
logger.debug(f"sign(), toSignStr = {concatenated_str}")
|
|
89
|
+
|
|
90
|
+
sign = hmac.new(
|
|
91
|
+
self._app_secret.encode("utf-8"),
|
|
92
|
+
concatenated_str.encode("utf-8"),
|
|
93
|
+
hashlib.sha1,
|
|
94
|
+
).hexdigest()
|
|
95
|
+
|
|
96
|
+
return sign
|
|
97
|
+
|
|
98
|
+
def get_region(self, country_code: str, auth_code: str):
|
|
99
|
+
# shim out the regions?
|
|
100
|
+
# https://api.link.aliyun.com/living/account/region/get?x-ca-request-id=59abc767-fbbc-4333-9127-e65d792133a8
|
|
101
|
+
# x-ca-request-id is a random UUID on each request
|
|
102
|
+
|
|
103
|
+
config = Config(
|
|
104
|
+
app_key=self._app_key, # correct
|
|
105
|
+
app_secret=self._app_secret,
|
|
106
|
+
domain=self.domain,
|
|
107
|
+
)
|
|
108
|
+
client = Client(config)
|
|
109
|
+
|
|
110
|
+
# build request
|
|
111
|
+
request = CommonParams(api_ver="1.0.2", language="en-US")
|
|
112
|
+
body = IoTApiRequest(
|
|
113
|
+
id=str(uuid.uuid4()),
|
|
114
|
+
params={
|
|
115
|
+
"authCode": auth_code,
|
|
116
|
+
"type": "THIRD_AUTHCODE",
|
|
117
|
+
"countryCode": country_code,
|
|
118
|
+
},
|
|
119
|
+
request=request,
|
|
120
|
+
version="1.0",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# send request
|
|
124
|
+
# possibly need to do this ourselves
|
|
125
|
+
response = client.do_request(
|
|
126
|
+
"/living/account/region/get", "https", "POST", None, body, RuntimeOptions()
|
|
127
|
+
)
|
|
128
|
+
logger.debug(response.status_message)
|
|
129
|
+
logger.debug(response.headers)
|
|
130
|
+
logger.debug(response.status_code)
|
|
131
|
+
logger.debug(response.body)
|
|
132
|
+
|
|
133
|
+
# Decodifica il corpo della risposta
|
|
134
|
+
response_body_str = response.body.decode("utf-8")
|
|
135
|
+
|
|
136
|
+
# Carica la stringa JSON in un dizionario
|
|
137
|
+
response_body_dict = json.loads(response_body_str)
|
|
138
|
+
|
|
139
|
+
if int(response_body_dict.get("code")) != 200:
|
|
140
|
+
raise Exception("Error in getting regions: " + response_body_dict["msg"])
|
|
141
|
+
else:
|
|
142
|
+
self._region = RegionResponse.from_dict(response_body_dict)
|
|
143
|
+
logger.debug("Endpoint : " + self._region.data.mqttEndpoint)
|
|
144
|
+
|
|
145
|
+
return response.body
|
|
146
|
+
|
|
147
|
+
def aep_handle(self):
|
|
148
|
+
# https://api.link.aliyun.com/app/aepauth/handle
|
|
149
|
+
aep_domain = self.domain
|
|
150
|
+
|
|
151
|
+
if self._region.data.apiGatewayEndpoint is not None:
|
|
152
|
+
aep_domain = self._region.data.apiGatewayEndpoint
|
|
153
|
+
|
|
154
|
+
config = Config(
|
|
155
|
+
app_key=self._app_key, # correct
|
|
156
|
+
app_secret=self._app_secret,
|
|
157
|
+
domain=aep_domain,
|
|
158
|
+
)
|
|
159
|
+
client = Client(config)
|
|
160
|
+
|
|
161
|
+
request = CommonParams(api_ver="1.0.0", language="en-US")
|
|
162
|
+
logger.debug("client id ", self._client_id)
|
|
163
|
+
time_now = time.time()
|
|
164
|
+
data_to_sign = {
|
|
165
|
+
"appKey": self._app_key,
|
|
166
|
+
"clientId": self._client_id, # needs to be unique to device
|
|
167
|
+
"deviceSn": self._device_sn, # same here
|
|
168
|
+
"timestamp": str(time_now),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
body = IoTApiRequest(
|
|
172
|
+
id=str(uuid.uuid4()),
|
|
173
|
+
params={
|
|
174
|
+
"authInfo": {
|
|
175
|
+
"clientId": self._client_id,
|
|
176
|
+
"sign": self.sign(data_to_sign),
|
|
177
|
+
"deviceSn": self._device_sn,
|
|
178
|
+
"timestamp": str(time_now),
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
request=request,
|
|
182
|
+
version="1.0",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# send request
|
|
186
|
+
# possibly need to do this ourselves
|
|
187
|
+
response = client.do_request(
|
|
188
|
+
"/app/aepauth/handle", "https", "POST", None, body, RuntimeOptions()
|
|
189
|
+
)
|
|
190
|
+
logger.debug(response.status_message)
|
|
191
|
+
logger.debug(response.headers)
|
|
192
|
+
logger.debug(response.status_code)
|
|
193
|
+
logger.debug(response.body)
|
|
194
|
+
|
|
195
|
+
response_body_str = response.body.decode("utf-8")
|
|
196
|
+
|
|
197
|
+
response_body_dict = json.loads(response_body_str)
|
|
198
|
+
|
|
199
|
+
if int(response_body_dict.get("code")) != 200:
|
|
200
|
+
raise Exception(
|
|
201
|
+
"Error in getting mqtt credentials: " + response_body_dict["msg"]
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
self._aep_response = AepResponse.from_dict(response_body_dict)
|
|
205
|
+
|
|
206
|
+
logger.debug(response_body_dict)
|
|
207
|
+
|
|
208
|
+
return response.body
|
|
209
|
+
|
|
210
|
+
# returns vid
|
|
211
|
+
|
|
212
|
+
async def connect(self):
|
|
213
|
+
region_url = "sdk.openaccount.aliyun.com"
|
|
214
|
+
async with ClientSession() as session:
|
|
215
|
+
headers = {
|
|
216
|
+
"host": region_url,
|
|
217
|
+
"date": UtilClient.get_date_utcstring(),
|
|
218
|
+
"x-ca-nonce": UtilClient.get_nonce(),
|
|
219
|
+
"x-ca-key": self._app_key,
|
|
220
|
+
"x-ca-signaturemethod": "HmacSHA256",
|
|
221
|
+
"accept": "application/json",
|
|
222
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
223
|
+
"user-agent": UtilClient.get_user_agent(None),
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
_bodyParam = {
|
|
227
|
+
"context": {
|
|
228
|
+
"sdkVersion": "3.4.2",
|
|
229
|
+
"platformName": "android",
|
|
230
|
+
"netType": "wifi",
|
|
231
|
+
"appKey": self._app_key,
|
|
232
|
+
"yunOSId": "",
|
|
233
|
+
"appVersion": APP_VERSION,
|
|
234
|
+
"utDid": self._utdid,
|
|
235
|
+
"appAuthToken": self._utdid, # ???
|
|
236
|
+
"securityToken": self._utdid, # ???
|
|
237
|
+
},
|
|
238
|
+
"config": {"version": 0, "lastModify": 0},
|
|
239
|
+
"device": {
|
|
240
|
+
"model": "sdk_gphone_x86_arm",
|
|
241
|
+
"brand": "goldfish_x86",
|
|
242
|
+
"platformVersion": "30",
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Get sign header
|
|
247
|
+
dic = headers.copy()
|
|
248
|
+
for key in MOVE_HEADERS:
|
|
249
|
+
dic.pop(key, None)
|
|
250
|
+
|
|
251
|
+
keys = sorted(dic.keys())
|
|
252
|
+
sign_headers = ",".join(keys)
|
|
253
|
+
header = "".join(f"{k}:{dic[k]}\n" for k in keys).strip()
|
|
254
|
+
|
|
255
|
+
headers["x-ca-signature-headers"] = sign_headers
|
|
256
|
+
string_to_sign = (
|
|
257
|
+
"POST\n{}\n\n{}\n{}\n{}\n/api/prd/connect.json?request={}".format(
|
|
258
|
+
headers["accept"],
|
|
259
|
+
headers["content-type"],
|
|
260
|
+
headers["date"],
|
|
261
|
+
header,
|
|
262
|
+
json.dumps(_bodyParam, separators=(",", ":")),
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
hash_val = hmac.new(
|
|
267
|
+
self._app_secret.encode("utf-8"),
|
|
268
|
+
string_to_sign.encode("utf-8"),
|
|
269
|
+
hashlib.sha256,
|
|
270
|
+
).digest()
|
|
271
|
+
signature = base64.b64encode(hash_val).decode("utf-8")
|
|
272
|
+
headers["x-ca-signature"] = signature
|
|
273
|
+
|
|
274
|
+
async with session.post(
|
|
275
|
+
f"https://{region_url}/api/prd/connect.json",
|
|
276
|
+
headers=headers,
|
|
277
|
+
params=dict(request=json.dumps(_bodyParam, separators=(",", ":"))),
|
|
278
|
+
) as resp:
|
|
279
|
+
data = await resp.json()
|
|
280
|
+
self._connect_response = ConnectResponse.from_dict(data)
|
|
281
|
+
logger.debug(data)
|
|
282
|
+
|
|
283
|
+
async def login_by_oauth(self, country_code: str, auth_code: str):
|
|
284
|
+
"""loginbyoauth.json."""
|
|
285
|
+
|
|
286
|
+
region_url = self._region.data.oaApiGatewayEndpoint
|
|
287
|
+
|
|
288
|
+
async with ClientSession() as session:
|
|
289
|
+
headers = {
|
|
290
|
+
"host": region_url,
|
|
291
|
+
"date": UtilClient.get_date_utcstring(),
|
|
292
|
+
"x-ca-nonce": UtilClient.get_nonce(),
|
|
293
|
+
"x-ca-key": self._app_key,
|
|
294
|
+
"x-ca-signaturemethod": "HmacSHA256",
|
|
295
|
+
"accept": "application/json",
|
|
296
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
297
|
+
"user-agent": UtilClient.get_user_agent(None),
|
|
298
|
+
"vid": self._connect_response.data.vid,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
_bodyParam = {
|
|
302
|
+
"country": country_code,
|
|
303
|
+
"authCode": auth_code,
|
|
304
|
+
"oauthPlateform": "23",
|
|
305
|
+
"oauthAppKey": self._app_key,
|
|
306
|
+
"appAuthToken": self._device_sn,
|
|
307
|
+
"riskControlInfo": {
|
|
308
|
+
"appID": "com.agilexrobotics",
|
|
309
|
+
"signType": "RSA",
|
|
310
|
+
"utdid": self._utdid,
|
|
311
|
+
"umidToken": self._utdid,
|
|
312
|
+
"USE_OA_PWD_ENCRYPT": "true",
|
|
313
|
+
"USE_H5_NC": "true",
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# Get sign header
|
|
318
|
+
dic = headers.copy()
|
|
319
|
+
for key in MOVE_HEADERS:
|
|
320
|
+
dic.pop(key, None)
|
|
321
|
+
|
|
322
|
+
keys = sorted(dic.keys())
|
|
323
|
+
sign_headers = ",".join(keys)
|
|
324
|
+
header = "".join(f"{k}:{dic[k]}\n" for k in keys).strip()
|
|
325
|
+
|
|
326
|
+
headers["x-ca-signature-headers"] = sign_headers
|
|
327
|
+
string_to_sign = "POST\n{}\n\n{}\n{}\n{}\n/api/prd/loginbyoauth.json?loginByOauthRequest={}".format(
|
|
328
|
+
headers["accept"],
|
|
329
|
+
headers["content-type"],
|
|
330
|
+
headers["date"],
|
|
331
|
+
header,
|
|
332
|
+
json.dumps(_bodyParam, separators=(",", ":")),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
hash_val = hmac.new(
|
|
336
|
+
self._app_secret.encode("utf-8"),
|
|
337
|
+
string_to_sign.encode("utf-8"),
|
|
338
|
+
hashlib.sha256,
|
|
339
|
+
).digest()
|
|
340
|
+
signature = base64.b64encode(hash_val).decode("utf-8")
|
|
341
|
+
headers["x-ca-signature"] = signature
|
|
342
|
+
|
|
343
|
+
async with session.post(
|
|
344
|
+
f"https://{region_url}/api/prd/loginbyoauth.json",
|
|
345
|
+
headers=headers,
|
|
346
|
+
params=dict(
|
|
347
|
+
loginByOauthRequest=json.dumps(_bodyParam, separators=(",", ":"))
|
|
348
|
+
),
|
|
349
|
+
) as resp:
|
|
350
|
+
data = await resp.json()
|
|
351
|
+
logger.debug(data)
|
|
352
|
+
|
|
353
|
+
self._login_by_oauth_response = LoginByOAuthResponse.from_dict(data)
|
|
354
|
+
|
|
355
|
+
# self._region = response.body.data
|
|
356
|
+
|
|
357
|
+
# return response.body
|
|
358
|
+
|
|
359
|
+
# headers require sid vid or at a minimuim vid which comes from prd/connect.json
|
|
360
|
+
|
|
361
|
+
def session_by_auth_code(self):
|
|
362
|
+
config = Config(
|
|
363
|
+
app_key=self._app_key, # correct
|
|
364
|
+
app_secret=self._app_secret,
|
|
365
|
+
domain=self._region.data.apiGatewayEndpoint,
|
|
366
|
+
)
|
|
367
|
+
client = Client(config)
|
|
368
|
+
|
|
369
|
+
# build request
|
|
370
|
+
request = CommonParams(api_ver="1.0.4", language="en-US")
|
|
371
|
+
body = IoTApiRequest(
|
|
372
|
+
id=str(uuid.uuid4()),
|
|
373
|
+
params={
|
|
374
|
+
"request": {
|
|
375
|
+
"authCode": self._login_by_oauth_response.data.data.loginSuccessResult.sid,
|
|
376
|
+
"accountType": "OA_SESSION",
|
|
377
|
+
"appKey": self._app_key,
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
request=request,
|
|
381
|
+
version="1.0",
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# send request
|
|
385
|
+
# possibly need to do this ourselves
|
|
386
|
+
response = client.do_request(
|
|
387
|
+
"/account/createSessionByAuthCode",
|
|
388
|
+
"https",
|
|
389
|
+
"POST",
|
|
390
|
+
None,
|
|
391
|
+
body,
|
|
392
|
+
RuntimeOptions(),
|
|
393
|
+
)
|
|
394
|
+
logger.debug(response.status_message)
|
|
395
|
+
logger.debug(response.headers)
|
|
396
|
+
logger.debug(response.status_code)
|
|
397
|
+
logger.debug(response.body)
|
|
398
|
+
|
|
399
|
+
# Decodifica il corpo della risposta
|
|
400
|
+
response_body_str = response.body.decode("utf-8")
|
|
401
|
+
|
|
402
|
+
# Carica la stringa JSON in un dizionario
|
|
403
|
+
response_body_dict = json.loads(response_body_str)
|
|
404
|
+
|
|
405
|
+
if int(response_body_dict.get("code")) != 200:
|
|
406
|
+
raise Exception("Error in creating session: " + response_body_dict["msg"])
|
|
407
|
+
else:
|
|
408
|
+
self._session_by_authcode_response = SessionByAuthCodeResponse.from_dict(
|
|
409
|
+
response_body_dict
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
return response.body
|
|
413
|
+
|
|
414
|
+
def check_or_refresh_session(self):
|
|
415
|
+
if self.load_saved_params() == False:
|
|
416
|
+
return False
|
|
417
|
+
config = Config(
|
|
418
|
+
app_key=self._app_key, # correct
|
|
419
|
+
app_secret=self._app_secret,
|
|
420
|
+
domain=self._region.data.apiGatewayEndpoint,
|
|
421
|
+
)
|
|
422
|
+
client = Client(config)
|
|
423
|
+
|
|
424
|
+
# build request
|
|
425
|
+
request = CommonParams(api_ver="1.0.4", language="en-US")
|
|
426
|
+
body = IoTApiRequest(
|
|
427
|
+
id=str(uuid.uuid4()),
|
|
428
|
+
params={
|
|
429
|
+
"request": {
|
|
430
|
+
"refreshToken": self._session_by_authcode_response.data.refreshToken,
|
|
431
|
+
"identityId": self._session_by_authcode_response.data.identityId,
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
request=request,
|
|
435
|
+
version="1.0",
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# send request
|
|
439
|
+
# possibly need to do this ourselves
|
|
440
|
+
response = client.do_request(
|
|
441
|
+
"/account/checkOrRefreshSession",
|
|
442
|
+
"https",
|
|
443
|
+
"POST",
|
|
444
|
+
None,
|
|
445
|
+
body,
|
|
446
|
+
RuntimeOptions(),
|
|
447
|
+
)
|
|
448
|
+
logger.debug(response.status_message)
|
|
449
|
+
logger.debug(response.headers)
|
|
450
|
+
logger.debug(response.status_code)
|
|
451
|
+
logger.debug(response.body)
|
|
452
|
+
|
|
453
|
+
# self._region = response.body.data
|
|
454
|
+
# Decodifica il corpo della risposta
|
|
455
|
+
response_body_str = response.body.decode("utf-8")
|
|
456
|
+
|
|
457
|
+
# Carica la stringa JSON in un dizionario
|
|
458
|
+
response_body_dict = json.loads(response_body_str)
|
|
459
|
+
|
|
460
|
+
def list_binding_by_account(self):
|
|
461
|
+
config = Config(
|
|
462
|
+
app_key=self._app_key, # correct
|
|
463
|
+
app_secret=self._app_secret,
|
|
464
|
+
domain=self._region.data.apiGatewayEndpoint,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
client = Client(config)
|
|
468
|
+
|
|
469
|
+
# build request
|
|
470
|
+
request = CommonParams(
|
|
471
|
+
api_ver="1.0.8",
|
|
472
|
+
language="en-US",
|
|
473
|
+
iot_token=self._session_by_authcode_response.data.iotToken,
|
|
474
|
+
)
|
|
475
|
+
body = IoTApiRequest(
|
|
476
|
+
id=str(uuid.uuid4()),
|
|
477
|
+
params={"pageSize": 100, "pageNo": 1},
|
|
478
|
+
request=request,
|
|
479
|
+
version="1.0",
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# send request
|
|
483
|
+
# possibly need to do this ourselves
|
|
484
|
+
response = client.do_request(
|
|
485
|
+
"/uc/listBindingByAccount", "https", "POST", None, body, RuntimeOptions()
|
|
486
|
+
)
|
|
487
|
+
logger.debug(response.status_message)
|
|
488
|
+
logger.debug(response.headers)
|
|
489
|
+
logger.debug(response.status_code)
|
|
490
|
+
logger.debug(response.body)
|
|
491
|
+
|
|
492
|
+
# self._region = response.body.data
|
|
493
|
+
# Decodifica il corpo della risposta
|
|
494
|
+
response_body_str = response.body.decode("utf-8")
|
|
495
|
+
|
|
496
|
+
# Carica la stringa JSON in un dizionario
|
|
497
|
+
response_body_dict = json.loads(response_body_str)
|
|
498
|
+
|
|
499
|
+
if int(response_body_dict.get("code")) != 200:
|
|
500
|
+
raise Exception("Error in creating session: " + response_body_dict["msg"])
|
|
501
|
+
else:
|
|
502
|
+
self._listing_dev_by_account_response = (
|
|
503
|
+
ListingDevByAccountResponse.from_dict(response_body_dict)
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
def send_cloud_command(self, command: bytes):
|
|
507
|
+
config = Config(
|
|
508
|
+
app_key=self._app_key, # correct
|
|
509
|
+
app_secret=self._app_secret,
|
|
510
|
+
domain=self._region.data.apiGatewayEndpoint,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
client = Client(config)
|
|
514
|
+
|
|
515
|
+
# build request
|
|
516
|
+
request = CommonParams(
|
|
517
|
+
api_ver="1.0.5",
|
|
518
|
+
language="en-US",
|
|
519
|
+
iot_token=self._session_by_authcode_response.data.iotToken,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
# TODO move to using InvokeThingServiceRequest()
|
|
523
|
+
body = IoTApiRequest(
|
|
524
|
+
id=str(uuid.uuid4()),
|
|
525
|
+
params={
|
|
526
|
+
"args": {"content": self.converter.printBase64Binary(command)},
|
|
527
|
+
"identifier": "device_protobuf_sync_service",
|
|
528
|
+
"iotId": "MbXcDE2X63CENA0lPGIo000000", # TODO get iotId from listbybinding request
|
|
529
|
+
},
|
|
530
|
+
request=request,
|
|
531
|
+
version="1.0",
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# send request
|
|
535
|
+
# possibly need to do this ourselves
|
|
536
|
+
response = client.do_request(
|
|
537
|
+
"/thing/service/invoke", "https", "POST", None, body, RuntimeOptions()
|
|
538
|
+
)
|
|
539
|
+
logger.debug(response.status_message)
|
|
540
|
+
logger.debug(response.headers)
|
|
541
|
+
logger.debug(response.status_code)
|
|
542
|
+
logger.debug(response.body)
|
|
543
|
+
|
|
544
|
+
# self._region = response.body.data
|
|
545
|
+
# Decodifica il corpo della risposta
|
|
546
|
+
response_body_str = response.body.decode("utf-8")
|
|
547
|
+
|
|
548
|
+
# Carica la stringa JSON in un dizionario
|
|
549
|
+
response_body_dict = json.loads(response_body_str)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
|
|
3
|
+
from aliyunsdkcore import client
|
|
4
|
+
from aliyunsdkiot.request.v20180120.GetDeviceStatusRequest import GetDeviceStatusRequest
|
|
5
|
+
from aliyunsdkiot.request.v20180120.InvokeThingServiceRequest import (
|
|
6
|
+
InvokeThingServiceRequest,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CloudService:
|
|
11
|
+
# com.aliyun.iot.aep.sdk
|
|
12
|
+
# https://domestic.mammotion.com/privacy/ - lists all aliyun packages
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.selectDeviceIOTID = ""
|
|
15
|
+
accessKeyId = "<your accessKey>"
|
|
16
|
+
accessKeySecret = "<your accessSecret>"
|
|
17
|
+
self.clt = client.AcsClient(accessKeyId, accessKeySecret, "ap-southeast")
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
String printBase64Binary = DatatypeConverter.printBase64Binary(byteArray);
|
|
21
|
+
JSONObject jSONObject = new JSONObject();
|
|
22
|
+
JSONObject jSONObject2 = new JSONObject();
|
|
23
|
+
try {
|
|
24
|
+
jSONObject2.put("content", printBase64Binary);
|
|
25
|
+
jSONObject.put("args", jSONObject2);
|
|
26
|
+
jSONObject.put("iotId", this.selectDeviceIOTID);
|
|
27
|
+
jSONObject.put("identifier", "device_protobuf_sync_service");
|
|
28
|
+
} catch (JSONException e) {
|
|
29
|
+
e.printStackTrace();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def invoke_thing_service(self, data: bytearray):
|
|
35
|
+
base64_encoded = base64.b64encode(data).decode("utf-8")
|
|
36
|
+
|
|
37
|
+
# Create a dictionary structure
|
|
38
|
+
data = {
|
|
39
|
+
"args": {"content": base64_encoded},
|
|
40
|
+
"DEVICE_IOTID": self.selectDeviceIOTID,
|
|
41
|
+
"identifier": "device_protobuf_sync_service",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
request = InvokeThingServiceRequest()
|
|
45
|
+
request.set_accept_format("json")
|
|
46
|
+
|
|
47
|
+
request.set_Args("Args")
|
|
48
|
+
request.set_Identifier("Identifier")
|
|
49
|
+
request.set_IotId("IotId")
|
|
50
|
+
request.set_ProductKey("ProductKey")
|
|
51
|
+
|
|
52
|
+
response = self.clt.do_action_with_exception(request)
|
|
53
|
+
# python2: print(response)
|
|
54
|
+
print(response)
|
|
55
|
+
|
|
56
|
+
def get_device_status(self):
|
|
57
|
+
request = GetDeviceStatusRequest()
|
|
58
|
+
request.set_accept_format("json")
|
|
59
|
+
|
|
60
|
+
request.set_IotId("IotId")
|
|
61
|
+
request.set_ProductKey("ProductKey")
|
|
62
|
+
request.set_DeviceName("DeviceName")
|
|
63
|
+
|
|
64
|
+
response = self.clt.do_action_with_exception(request)
|
|
65
|
+
print(response)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class DeviceData(DataClassORJSONMixin):
|
|
9
|
+
deviceSecret: str
|
|
10
|
+
productKey: str
|
|
11
|
+
deviceName: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AepResponse(DataClassORJSONMixin):
|
|
16
|
+
code: int
|
|
17
|
+
data: DeviceData
|
|
18
|
+
id: Optional[str] = None
|