pymammotion 0.5.69__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.
Files changed (154) hide show
  1. pymammotion/__init__.py +53 -0
  2. pymammotion/agora/__init__.py +0 -0
  3. pymammotion/agora/agora_api.py +755 -0
  4. pymammotion/agora/agora_rtc_capabilities.py +748 -0
  5. pymammotion/agora/agora_websockets.py +1175 -0
  6. pymammotion/aliyun/__init__.py +1 -0
  7. pymammotion/aliyun/client.py +235 -0
  8. pymammotion/aliyun/cloud_gateway.py +982 -0
  9. pymammotion/aliyun/model/aep_response.py +21 -0
  10. pymammotion/aliyun/model/connect_response.py +51 -0
  11. pymammotion/aliyun/model/dev_by_account_response.py +195 -0
  12. pymammotion/aliyun/model/login_by_oauth_response.py +64 -0
  13. pymammotion/aliyun/model/regions_response.py +29 -0
  14. pymammotion/aliyun/model/session_by_authcode_response.py +19 -0
  15. pymammotion/aliyun/model/thing_response.py +12 -0
  16. pymammotion/aliyun/regions.py +62 -0
  17. pymammotion/aliyun/tea/core.py +297 -0
  18. pymammotion/aliyun/tmp_constant.py +171 -0
  19. pymammotion/bluetooth/__init__.py +1 -0
  20. pymammotion/bluetooth/ble.py +62 -0
  21. pymammotion/bluetooth/ble_message.py +676 -0
  22. pymammotion/bluetooth/const.py +27 -0
  23. pymammotion/bluetooth/data/__init__.py +0 -0
  24. pymammotion/bluetooth/data/convert.py +25 -0
  25. pymammotion/bluetooth/data/framectrldata.py +40 -0
  26. pymammotion/bluetooth/data/notifydata.py +62 -0
  27. pymammotion/bluetooth/model/__init__.py +0 -0
  28. pymammotion/bluetooth/model/atomic_integer.py +54 -0
  29. pymammotion/const.py +13 -0
  30. pymammotion/data/__init__.py +0 -0
  31. pymammotion/data/model/__init__.py +8 -0
  32. pymammotion/data/model/account.py +8 -0
  33. pymammotion/data/model/device.py +192 -0
  34. pymammotion/data/model/device_config.py +72 -0
  35. pymammotion/data/model/device_info.py +60 -0
  36. pymammotion/data/model/device_limits.py +49 -0
  37. pymammotion/data/model/enums.py +77 -0
  38. pymammotion/data/model/errors.py +12 -0
  39. pymammotion/data/model/events.py +14 -0
  40. pymammotion/data/model/generate_geojson.py +565 -0
  41. pymammotion/data/model/generate_route_information.py +26 -0
  42. pymammotion/data/model/hash_list.py +475 -0
  43. pymammotion/data/model/location.py +36 -0
  44. pymammotion/data/model/mowing_modes.py +77 -0
  45. pymammotion/data/model/rapid_state.py +45 -0
  46. pymammotion/data/model/raw_data.py +215 -0
  47. pymammotion/data/model/region_data.py +102 -0
  48. pymammotion/data/model/report_info.py +182 -0
  49. pymammotion/data/model/work.py +27 -0
  50. pymammotion/data/mower_state_manager.py +369 -0
  51. pymammotion/data/mqtt/__init__.py +1 -0
  52. pymammotion/data/mqtt/event.py +227 -0
  53. pymammotion/data/mqtt/mammotion_properties.py +276 -0
  54. pymammotion/data/mqtt/properties.py +203 -0
  55. pymammotion/data/mqtt/status.py +57 -0
  56. pymammotion/event/__init__.py +6 -0
  57. pymammotion/event/event.py +96 -0
  58. pymammotion/homeassistant/__init__.py +3 -0
  59. pymammotion/homeassistant/mower_api.py +514 -0
  60. pymammotion/homeassistant/rtk_api.py +54 -0
  61. pymammotion/http/__init__.py +0 -0
  62. pymammotion/http/encryption.py +220 -0
  63. pymammotion/http/http.py +673 -0
  64. pymammotion/http/model/__init__.py +0 -0
  65. pymammotion/http/model/camera_stream.py +31 -0
  66. pymammotion/http/model/http.py +249 -0
  67. pymammotion/http/model/response_factory.py +61 -0
  68. pymammotion/http/model/rtk.py +16 -0
  69. pymammotion/mammotion/__init__.py +0 -0
  70. pymammotion/mammotion/commands/__init__.py +0 -0
  71. pymammotion/mammotion/commands/abstract_message.py +24 -0
  72. pymammotion/mammotion/commands/mammotion_command.py +81 -0
  73. pymammotion/mammotion/commands/messages/__init__.py +0 -0
  74. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  75. pymammotion/mammotion/commands/messages/driver.py +122 -0
  76. pymammotion/mammotion/commands/messages/media.py +87 -0
  77. pymammotion/mammotion/commands/messages/navigation.py +564 -0
  78. pymammotion/mammotion/commands/messages/network.py +205 -0
  79. pymammotion/mammotion/commands/messages/ota.py +38 -0
  80. pymammotion/mammotion/commands/messages/system.py +330 -0
  81. pymammotion/mammotion/commands/messages/video.py +33 -0
  82. pymammotion/mammotion/control/__init__.py +0 -0
  83. pymammotion/mammotion/control/joystick.py +145 -0
  84. pymammotion/mammotion/devices/__init__.py +29 -0
  85. pymammotion/mammotion/devices/base.py +163 -0
  86. pymammotion/mammotion/devices/mammotion.py +571 -0
  87. pymammotion/mammotion/devices/mammotion_bluetooth.py +496 -0
  88. pymammotion/mammotion/devices/mammotion_cloud.py +355 -0
  89. pymammotion/mammotion/devices/mammotion_mower_ble.py +48 -0
  90. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  91. pymammotion/mammotion/devices/managers/managers.py +81 -0
  92. pymammotion/mammotion/devices/mower_device.py +120 -0
  93. pymammotion/mammotion/devices/mower_manager.py +107 -0
  94. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  95. pymammotion/mammotion/devices/rtk_cloud.py +115 -0
  96. pymammotion/mammotion/devices/rtk_device.py +50 -0
  97. pymammotion/mammotion/devices/rtk_manager.py +125 -0
  98. pymammotion/mqtt/__init__.py +6 -0
  99. pymammotion/mqtt/aliyun_mqtt.py +237 -0
  100. pymammotion/mqtt/linkkit/__init__.py +5 -0
  101. pymammotion/mqtt/linkkit/h2client.py +585 -0
  102. pymammotion/mqtt/linkkit/linkkit.py +3025 -0
  103. pymammotion/mqtt/mammotion_future.py +26 -0
  104. pymammotion/mqtt/mammotion_mqtt.py +214 -0
  105. pymammotion/mqtt/mqtt_models.py +66 -0
  106. pymammotion/proto/__init__.py +4841 -0
  107. pymammotion/proto/basestation.proto +51 -0
  108. pymammotion/proto/basestation_pb2.py +35 -0
  109. pymammotion/proto/basestation_pb2.pyi +89 -0
  110. pymammotion/proto/common.proto +7 -0
  111. pymammotion/proto/common_pb2.py +25 -0
  112. pymammotion/proto/common_pb2.pyi +13 -0
  113. pymammotion/proto/dev_net.proto +321 -0
  114. pymammotion/proto/dev_net_pb2.py +111 -0
  115. pymammotion/proto/dev_net_pb2.pyi +515 -0
  116. pymammotion/proto/luba_msg.proto +76 -0
  117. pymammotion/proto/luba_msg_pb2.py +41 -0
  118. pymammotion/proto/luba_msg_pb2.pyi +97 -0
  119. pymammotion/proto/luba_mul.proto +129 -0
  120. pymammotion/proto/luba_mul_pb2.py +61 -0
  121. pymammotion/proto/luba_mul_pb2.pyi +178 -0
  122. pymammotion/proto/mctrl_driver.proto +107 -0
  123. pymammotion/proto/mctrl_driver_pb2.py +57 -0
  124. pymammotion/proto/mctrl_driver_pb2.pyi +167 -0
  125. pymammotion/proto/mctrl_nav.proto +591 -0
  126. pymammotion/proto/mctrl_nav_pb2.py +136 -0
  127. pymammotion/proto/mctrl_nav_pb2.pyi +1067 -0
  128. pymammotion/proto/mctrl_ota.proto +80 -0
  129. pymammotion/proto/mctrl_ota_pb2.py +45 -0
  130. pymammotion/proto/mctrl_ota_pb2.pyi +128 -0
  131. pymammotion/proto/mctrl_pept.proto +34 -0
  132. pymammotion/proto/mctrl_pept_pb2.py +33 -0
  133. pymammotion/proto/mctrl_pept_pb2.pyi +58 -0
  134. pymammotion/proto/mctrl_sys.proto +741 -0
  135. pymammotion/proto/mctrl_sys_pb2.py +206 -0
  136. pymammotion/proto/mctrl_sys_pb2.pyi +1213 -0
  137. pymammotion/proto/message_pool.py +3 -0
  138. pymammotion/proto/py.typed +0 -0
  139. pymammotion/py.typed +0 -0
  140. pymammotion/utility/constant/__init__.py +3 -0
  141. pymammotion/utility/constant/device_constant.py +315 -0
  142. pymammotion/utility/conversions.py +5 -0
  143. pymammotion/utility/datatype_converter.py +124 -0
  144. pymammotion/utility/device_config.py +755 -0
  145. pymammotion/utility/device_type.py +489 -0
  146. pymammotion/utility/map.py +259 -0
  147. pymammotion/utility/movement.py +18 -0
  148. pymammotion/utility/mur_mur_hash.py +159 -0
  149. pymammotion/utility/periodic.py +106 -0
  150. pymammotion/utility/rocker_util.py +194 -0
  151. pymammotion-0.5.69.dist-info/METADATA +93 -0
  152. pymammotion-0.5.69.dist-info/RECORD +154 -0
  153. pymammotion-0.5.69.dist-info/WHEEL +4 -0
  154. pymammotion-0.5.69.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,3025 @@
1
+ #
2
+ # Copyright (c) 2014-2018 Alibaba Group. All rights reserved.
3
+ # License-Identifier: Apache-2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ #
18
+
19
+ from enum import Enum
20
+ import hashlib
21
+ import hmac
22
+ import json
23
+ import logging
24
+ import os
25
+ import queue
26
+ import random
27
+ import re
28
+ import ssl
29
+ import string
30
+ import sys
31
+ import threading
32
+ import time
33
+ from typing import Any
34
+ import urllib.parse
35
+ import urllib.request
36
+
37
+ import paho.mqtt.client as mqtt
38
+ from paho.mqtt.enums import CallbackAPIVersion
39
+
40
+ REQUIRED_MAJOR_VERSION = 3
41
+ REQUIRED_MINOR_VERSION = 5
42
+
43
+
44
+ # check Python version
45
+ def lk_check_python_version(major_version, minor_version) -> None:
46
+ version = sys.version_info
47
+ if version[0] < major_version or (version[0] == major_version and version[1] < minor_version):
48
+ print(
49
+ "WARNING: linkit requires Python %d.%d or higher, and the current version is %s"
50
+ % (major_version, minor_version, sys.version)
51
+ )
52
+
53
+
54
+ lk_check_python_version(REQUIRED_MAJOR_VERSION, REQUIRED_MINOR_VERSION)
55
+
56
+
57
+ class LinkKit:
58
+ TAG_KEY = "attrKey"
59
+ TAG_VALUE = "attrValue"
60
+
61
+ class LinkKitState(Enum):
62
+ INITIALIZED = 1
63
+ CONNECTING = 2
64
+ CONNECTED = 3
65
+ DISCONNECTING = 4
66
+ DISCONNECTED = 5
67
+ DESTRUCTING = 6
68
+ DESTRUCTED = 7
69
+
70
+ class ErrorCode(Enum):
71
+ SUCCESS = 0
72
+ # 错误码一共三位数,第一位为0表示为mqtt相关的错误码
73
+ NETWORK_DISCONNECTED = -0x001 # 当前mqtt连接断开
74
+ INVALID_TOPIC = -0x002 # 输入的topic为空
75
+ INVALID_QOS = -0x003 # 输入的qos非法
76
+ PAHO_MQTT_ERROR = -0x004 # 底层的mqtt报错
77
+ # 0x100开头的表示为网关相关的错误码
78
+ NULL_SUBDEV_ERR = -0x101 # 输入的子设备信息为空错误
79
+ SUBDEV_NOT_ARRAY_ERR = -0x102 # 输入数据并非数组错误
80
+ ARRAY_LENGTH_ERR = -0x103 # 数组长度错误
81
+ # 0x300开头的表示为动态注册相关的错误码
82
+ DYNREG_AUTH_FAILED = -0x301 # 预注册动态注册认证失败
83
+ DYNREG_AUTH_NWL_FAILED = -0x302 # 免白动态注册认证失败
84
+ # 0x400开头表示为OTA有关的错误
85
+ OTA_INVALID_PARAM = -0x401 # 参数非法
86
+ OTA_DIGEST_MISMATCH = -0x402 # 校验和不通过
87
+ OTA_PUB_FAILED = -0x403 # 上报失败
88
+ OTA_INVALID_SIGN_METHOD = -0x404 # 非法校验方法
89
+ OTA_DOWNLOAD_FAIL = -0x405 # 下载失败
90
+ OTA_INVALID_URL = -0x406 # 无法访问HTTP链接
91
+ OTA_INVALID_PATH = -0x407 # 存储路径打开失败
92
+
93
+ class StateError(Exception):
94
+ def __init__(self, err) -> None:
95
+ Exception.__init__(self, err)
96
+
97
+ class Shadow:
98
+ def __init__(self) -> None:
99
+ self.__version = None
100
+ self.__timestamp = None
101
+ self.__state = None
102
+ self.__metadata = None
103
+ self.__latest_shadow_lock = threading.Lock()
104
+ self.__latest_received_time = None
105
+ self.__lastest_received_payload = None
106
+
107
+ def get_version(self):
108
+ with self.__latest_shadow_lock:
109
+ return self.__version
110
+
111
+ def get_metadata(self):
112
+ with self.__latest_shadow_lock:
113
+ return self.__metadata
114
+
115
+ def get_state(self):
116
+ with self.__latest_shadow_lock:
117
+ return self.__state
118
+
119
+ def set_state(self, state) -> None:
120
+ with self.__latest_shadow_lock:
121
+ self.__state = state
122
+
123
+ def set_metadata(self, metadata) -> None:
124
+ with self.__latest_shadow_lock:
125
+ self.__metadata = metadata
126
+
127
+ def set_version(self, version) -> None:
128
+ with self.__latest_shadow_lock:
129
+ self.__version = version
130
+
131
+ def set_timestamp(self, timestamp) -> None:
132
+ with self.__latest_shadow_lock:
133
+ self.__timestamp = timestamp
134
+
135
+ def set_latest_recevied_time(self, timestamp) -> None:
136
+ with self.__latest_shadow_lock:
137
+ self.__latest_received_time = timestamp
138
+
139
+ def get_latest_recevied_time(self):
140
+ with self.__latest_shadow_lock:
141
+ return self.__latest_received_time
142
+
143
+ def set_latest_recevied_payload(self, payload) -> None:
144
+ with self.__latest_shadow_lock:
145
+ self.__latest_received_payload = payload
146
+
147
+ def get_latest_recevied_payload(self):
148
+ with self.__latest_shadow_lock:
149
+ return self.__latest_received_payload
150
+
151
+ def to_dict(self):
152
+ return {
153
+ "state": self.__state,
154
+ "metadata": self.__metadata,
155
+ "version": self.__version,
156
+ "timestamp": self.__timestamp,
157
+ }
158
+
159
+ def to_json_string(self):
160
+ return json.dumps(self.to_dict())
161
+
162
+ class __HandlerTask:
163
+ def __init__(self, logger=None) -> None:
164
+ self.__logger = logger
165
+ if self.__logger is not None:
166
+ self.__logger.info("HandlerTask init enter")
167
+ self.__message_queue = queue.Queue(2000)
168
+ self.__cmd_callback = {}
169
+ self.__started = False
170
+ self.__exited = False
171
+ self.__thread = None
172
+
173
+ def register_cmd_callback(self, cmd, callback) -> int:
174
+ if self.__started is False:
175
+ if cmd != "req_exit":
176
+ self.__cmd_callback[cmd] = callback
177
+ return 0
178
+ else:
179
+ return 1
180
+ else:
181
+ return 2
182
+
183
+ def post_message(self, cmd, value) -> bool:
184
+ self.__logger.debug("post_message :%r " % cmd)
185
+ if self.__started and self.__exited is False:
186
+ try:
187
+ self.__message_queue.put((cmd, value), timeout=5)
188
+ except queue.Full as e:
189
+ self.__logger.error("queue full: %r" % e)
190
+ return False
191
+ self.__logger.debug("post_message success")
192
+ return True
193
+ self.__logger.debug("post_message fail started:%r,exited:%r" % (self.__started, self.__exited))
194
+ return False
195
+
196
+ def start(self) -> int:
197
+ if self.__logger is not None:
198
+ self.__logger.info("HandlerTask start")
199
+ if self.__started is False:
200
+ if self.__logger is not None:
201
+ self.__logger.info("HandlerTask try start")
202
+ self.__exited = False
203
+ self.__started = True
204
+ self.__message_queue = queue.Queue(2000)
205
+ self.__thread = threading.Thread(target=self.__thread_runnable)
206
+ self.__thread.daemon = True
207
+ self.__thread.start()
208
+ return 0
209
+ return 1
210
+
211
+ def stop(self) -> None:
212
+ if self.__started and self.__exited is False:
213
+ self.__exited = True
214
+ self.__message_queue.put(("req_exit", None))
215
+
216
+ def wait_stop(self) -> None:
217
+ if self.__started is True:
218
+ self.__thread.join()
219
+
220
+ def __thread_runnable(self) -> None:
221
+ if self.__logger is not None:
222
+ self.__logger.debug("thread runnable enter")
223
+ while True:
224
+ cmd, value = self.__message_queue.get()
225
+ self.__logger.debug("thread runnable pop cmd:%r" % cmd)
226
+ if cmd == "req_exit":
227
+ break
228
+ if self.__cmd_callback[cmd] is not None:
229
+ try:
230
+ self.__cmd_callback[cmd](value)
231
+ except Exception as e:
232
+ if self.__logger is not None:
233
+ self.__logger.error("thread runnable raise exception:%s" % e)
234
+ self.__started = False
235
+ if self.__logger is not None:
236
+ self.__logger.debug("thread runnable exit")
237
+
238
+ class LoopThread:
239
+ def __init__(self, logger=None) -> None:
240
+ self.__logger = logger
241
+ if logger is not None:
242
+ self.__logger.info("LoopThread init enter")
243
+ self.__callback = None
244
+ self.__thread = None
245
+ self.__started = False
246
+ self.__req_wait = threading.Event()
247
+ if logger is not None:
248
+ self.__logger.info("LoopThread init exit")
249
+
250
+ def start(self, callback) -> int:
251
+ if self.__started is True:
252
+ self.__logger.info("LoopThread already ")
253
+ return 1
254
+ else:
255
+ self.__callback = callback
256
+ self.__thread = threading.Thread(target=self.__thread_main)
257
+ self.__thread.daemon = True
258
+ self.__thread.start()
259
+ return 0
260
+
261
+ def stop(self) -> None:
262
+ self.__req_wait.wait()
263
+ self.__req_wait.clear()
264
+
265
+ def __thread_main(self) -> None:
266
+ self.__started = True
267
+ try:
268
+ if self.__logger is not None:
269
+ self.__logger.debug("LoopThread thread enter")
270
+ if self.__callback is not None:
271
+ self.__callback()
272
+ if self.__logger is not None:
273
+ self.__logger.debug("LoopThread thread exit")
274
+ except Exception as e:
275
+ self.__logger.error("LoopThread thread Exception:" + str(e))
276
+ self.__started = False
277
+ self.__req_wait.set()
278
+
279
+ def _on_file_upload_start(self, id, upload_file_info, user_data) -> None:
280
+ if self.__on_file_upload_begin != None:
281
+ self.__on_file_upload_begin(id, upload_file_info, self.__user_data)
282
+
283
+ def _on_file_upload_end(self, id, upload_file_info, upload_file_result, user_data) -> None:
284
+ if self.__on_file_upload_end != None:
285
+ self.__on_file_upload_end(id, upload_file_info, upload_file_result, self.__user_data)
286
+
287
+ def _on_file_upload_progress(self, id, upload_file_result, upload_file_info, user_data) -> None:
288
+ pass
289
+
290
+ class __LinkKitLog:
291
+ def __init__(self) -> None:
292
+ self.__logger = logging.getLogger("linkkit")
293
+ self.__enabled = False
294
+
295
+ def enable_logger(self) -> None:
296
+ self.__enabled = True
297
+
298
+ def disable_logger(self) -> None:
299
+ self.__enabled = False
300
+
301
+ def is_enabled(self):
302
+ return self.__enabled
303
+
304
+ def config_logger(self, level) -> None:
305
+ self.__logger.setLevel(level)
306
+
307
+ def debug(self, fmt, *args) -> None:
308
+ if self.__enabled:
309
+ self.__logger.debug(fmt, *args)
310
+
311
+ def warring(self, fmt, *args) -> None:
312
+ if self.__enabled:
313
+ self.__logger.warning(fmt, *args)
314
+
315
+ def info(self, fmt, *args) -> None:
316
+ if self.__enabled:
317
+ self.__logger.info(fmt, *args)
318
+
319
+ def error(self, fmt, *args) -> None:
320
+ if self.__enabled:
321
+ self.__logger.error(fmt, *args)
322
+
323
+ def critical(self, fmt, *args) -> None:
324
+ if self.__enabled:
325
+ self.__logger.critical(fmt, *args)
326
+
327
+ __USER_TOPIC_PREFIX = "/%s/%s/%s"
328
+ __ALIYUN_BROKER_CA_DATA = """
329
+ -----BEGIN CERTIFICATE-----
330
+ MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
331
+ A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
332
+ b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
333
+ MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
334
+ YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
335
+ aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
336
+ jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
337
+ xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
338
+ 1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
339
+ snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
340
+ U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
341
+ 9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
342
+ BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
343
+ AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
344
+ yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
345
+ 38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
346
+ AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
347
+ DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
348
+ HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
349
+ -----END CERTIFICATE-----
350
+ -----BEGIN CERTIFICATE-----
351
+ MIID3zCCAsegAwIBAgISfiX6mTa5RMUTGSC3rQhnestIMA0GCSqGSIb3DQEBCwUA
352
+ MHcxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFu
353
+ Z3pob3UxEzARBgNVBAoMCkFsaXl1biBJb1QxEDAOBgNVBAsMB1Jvb3QgQ0ExGzAZ
354
+ BgNVBAMMEkFsaXl1biBJb1QgUm9vdCBDQTAgFw0yMzA3MDQwNjM2NThaGA8yMDUz
355
+ MDcwNDA2MzY1OFowdzELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREw
356
+ DwYDVQQHDAhIYW5nemhvdTETMBEGA1UECgwKQWxpeXVuIElvVDEQMA4GA1UECwwH
357
+ Um9vdCBDQTEbMBkGA1UEAwwSQWxpeXVuIElvVCBSb290IENBMIIBIjANBgkqhkiG
358
+ 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoK//6vc2oXhnvJD7BVhj6grj7PMlN2N4iNH4
359
+ GBmLmMdkF1z9eQLjksYc4Zid/FX67ypWFtdycOei5ec0X00m53Gvy4zLGBo2uKgi
360
+ T9IxMudmt95bORZbaph4VK82gPNU4ewbiI1q2loRZEHRdyPORTPpvNLHu8DrYBnY
361
+ Vg5feEYLLyhxg5M1UTrT/30RggHpaa0BYIPxwsKyylQ1OskOsyZQeOyPe8t8r2D4
362
+ RBpUGc5ix4j537HYTKSyK3Hv57R7w1NzKtXoOioDOm+YySsz9sTLFajZkUcQci4X
363
+ aedyEeguDLAIUKiYicJhRCZWljVlZActorTgjCY4zRajodThrQIDAQABo2MwYTAO
364
+ BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUkWHoKi2h
365
+ DlS1/rYpcT/Ue+aKhP8wHwYDVR0jBBgwFoAUkWHoKi2hDlS1/rYpcT/Ue+aKhP8w
366
+ DQYJKoZIhvcNAQELBQADggEBADrrLcBY7gDXN8/0KHvPbGwMrEAJcnF9z4MBxRvt
367
+ rEoRxhlvRZzPi7w/868xbipwwnksZsn0QNIiAZ6XzbwvIFG01ONJET+OzDy6ZqUb
368
+ YmJI09EOe9/Hst8Fac2D14Oyw0+6KTqZW7WWrP2TAgv8/Uox2S05pCWNfJpRZxOv
369
+ Lr4DZmnXBJCMNMY/X7xpcjylq+uCj118PBobfH9Oo+iAJ4YyjOLmX3bflKIn1Oat
370
+ vdJBtXCj3phpfuf56VwKxoxEVR818GqPAHnz9oVvye4sQqBp/2ynrKFxZKUaJtk0
371
+ 7UeVbtecwnQTrlcpWM7ACQC0OO0M9+uNjpKIbksv1s11xu0=
372
+ -----END CERTIFICATE-----
373
+ -----BEGIN CERTIFICATE-----
374
+ MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDE
375
+ gMB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2
376
+ JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNM
377
+ zQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBS
378
+ NjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiI
379
+ wDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQ
380
+ ssgrRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuT
381
+ ToVBu1kZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSK
382
+ vGRMIRxDaNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n
383
+ 16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9
384
+ CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJ
385
+ Da38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiW
386
+ m05OWgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4
387
+ UoQSwC+n+7o/hbguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQ
388
+ Ce24DWJfncBZ4nWUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFl
389
+ WQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZ
390
+ cIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjA
391
+ PBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToD
392
+ AfBgNVHSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFA
393
+ AOCAgEAgyXt6NH9lVLNnsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcW
394
+ c+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKP
395
+ rmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waN
396
+ rlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944
397
+ Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl
398
+ +68KnyBr3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU
399
+ 3/gKbaKxCXcPu9czc8FB10jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTO
400
+ wY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsVi
401
+ VO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9
402
+ x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDf
403
+ LRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
404
+ -----END CERTIFICATE-----
405
+ -----BEGIN CERTIFICATE-----
406
+ MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDME
407
+ YxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD
408
+ VQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MD
409
+ MyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24g
410
+ bnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQ
411
+ IBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omw
412
+ lwxwEwkBjtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4
413
+ Tb+0cUB+hflGddyXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E
414
+ BTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQ
415
+ QDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cV
416
+ i4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/alt
417
+ P+CAezNIm8BZ/3Hobui3A=
418
+ -----END CERTIFICATE-----
419
+ -----BEGIN CERTIFICATE-----
420
+ MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAU
421
+ AMEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGg
422
+ YDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2M
423
+ DMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24g
424
+ bnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb
425
+ 3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08
426
+ EsCVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUl
427
+ ghYruQGvGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTq
428
+ a1VbkNud316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/O
429
+ rffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPT
430
+ Xhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTy
431
+ G/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0N
432
+ XfeD412lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JM
433
+ WKmIJ5jqSngiCNI/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+F
434
+ fy7dXxd7Pj2Fxzsx2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7
435
+ /mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKg
436
+ Gwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQ
437
+ H/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIh
438
+ vcNAQEMBQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048
439
+ p9gkUbJUHJNOxO97k4VgJuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63b
440
+ EIaZHU1VNaL8FpO7XJqti2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6
441
+ Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MV
442
+ enQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdk
443
+ LG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSy
444
+ BQ7N0H3qqJZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7f
445
+ XwgNNgyYMqIgXQBztSvwyeqiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJ
446
+ Mbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvd
447
+ kzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE
448
+ 9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QE
449
+ UxeCp6
450
+ -----END CERTIFICATE-----
451
+ """
452
+
453
+ def __init__(
454
+ self,
455
+ host_name,
456
+ product_key,
457
+ device_name,
458
+ device_secret,
459
+ auth_type=None,
460
+ username=None,
461
+ client_id=None,
462
+ password=None,
463
+ instance_id=None,
464
+ product_secret=None,
465
+ user_data=None,
466
+ ) -> None:
467
+ # logging configs
468
+ self.__just_for_pycharm_autocomplete = False
469
+
470
+ def __str_is_empty(value) -> bool:
471
+ if value is None or value == "":
472
+ return True
473
+ else:
474
+ return False
475
+
476
+ # param check
477
+ if __str_is_empty(host_name):
478
+ raise ValueError("host_name wrong")
479
+ if __str_is_empty(product_key):
480
+ raise ValueError("product key wrong")
481
+ if __str_is_empty(device_name):
482
+ raise ValueError("device name wrong")
483
+ if __str_is_empty(device_secret) and __str_is_empty(product_secret):
484
+ lack_other_auth_info = __str_is_empty(username) or __str_is_empty(client_id) or __str_is_empty(password)
485
+ if lack_other_auth_info:
486
+ raise ValueError("device secret & product secret are both empty")
487
+
488
+ self.__link_log = LinkKit.__LinkKitLog()
489
+ self.__PahoLog = logging.getLogger("Paho")
490
+ self.__PahoLog.setLevel(logging.DEBUG)
491
+
492
+ # config internal property
493
+ self.__host_name = host_name
494
+ self.__product_key = product_key
495
+ self.__device_name = device_name
496
+ self.__device_secret = device_secret
497
+ self.__product_secret = product_secret
498
+ self.__user_data = user_data
499
+ self.__device_interface_info = ""
500
+ self.__device_mac = None
501
+ self.__cellular_IMEI = None
502
+ self.__cellular_ICCID = None
503
+ self.__cellular_IMSI = None
504
+ self.__cellular_MSISDN = None
505
+ self.__mqtt_client = None
506
+ self.__sdk_version = "1.2.13"
507
+ self.__sdk_program_language = "Python"
508
+ self.__endpoint = None
509
+ self.__check_hostname = True
510
+ self.__instance_id = instance_id
511
+
512
+ self.__mqtt_port = 8883
513
+ self.__mqtt_protocol = "MQTTv311"
514
+ self.__mqtt_transport = "TCP"
515
+ self.__mqtt_secure = "TLS"
516
+ self.__mqtt_keep_alive = 60
517
+ self.__mqtt_clean_session = True
518
+ self.__mqtt_max_inflight_message = 20
519
+ self.__mqtt_max_queued_message = 40
520
+ self.__mqtt_auto_reconnect_min_sec = 1
521
+ self.__mqtt_auto_reconnect_max_sec = 60
522
+ self.__mqtt_auto_reconnect_sec = 0
523
+ self.__mqtt_request_timeout = 10
524
+ # 动态注册(免预注册)的flag
525
+ self.__dynamic_register_nwl_flag = 0
526
+ # 动态注册(预注册)的flag
527
+ self.__dynamic_register_flag = 0
528
+ self.__linkkit_state = LinkKit.LinkKitState.INITIALIZED
529
+ self.__aliyun_broker_ca_data = self.__ALIYUN_BROKER_CA_DATA
530
+ self.__force_reconnect = False
531
+
532
+ self.__latest_shadow = LinkKit.Shadow()
533
+ self.__auth_type = auth_type
534
+ self.__username = username
535
+ self.__client_id = client_id
536
+ self.__password = password
537
+
538
+ # aliyun IoT callbacks
539
+ self.__on_device_dynamic_register = None
540
+
541
+ # mqtt callbacks
542
+ self.__on_connect = None
543
+ self.__on_disconnect = None
544
+ self.__on_publish_topic = None
545
+ self.__on_subscribe_topic = None
546
+ self.__on_unsubscribe_topic = None
547
+ self.__on_topic_message = None
548
+
549
+ self.__on_topic_rrpc_message = None
550
+ self.__on_subscribe_rrpc_topic = None
551
+ self.__on_unsubscribe_rrpc_topic = None
552
+
553
+ # thing model callbacks
554
+ self.__on_thing_create = None
555
+ self.__on_thing_enable = None
556
+ self.__on_thing_disable = None
557
+ self.__on_thing_raw_data_arrived = None
558
+ self.__on_thing_raw_data_post = None
559
+ self.__on_thing_call_service = None
560
+ self.__on_thing_prop_changed = None
561
+ self.__on_thing_event_post = None
562
+ self.__on_thing_prop_post = None
563
+ self.__on_thing_shadow_get = None
564
+ self.__on_thing_device_info_update = None
565
+ self.__on_thing_device_info_delete = None
566
+
567
+ self.__on_file_upload_begin = None
568
+ self.__on_file_upload_end = None
569
+
570
+ self.__user_topics = {}
571
+ self.__user_topics_subscribe_request = {}
572
+ self.__user_topics_unsubscribe_request = {}
573
+ self.__user_topics_request_lock = threading.Lock()
574
+ self.__user_topics_unsubscribe_request_lock = threading.Lock()
575
+
576
+ self.__user_rrpc_topics = {}
577
+ self.__user_rrpc_topics_lock = threading.RLock()
578
+ self.__user_rrpc_topics_subscribe_request = {}
579
+ self.__user_rrpc_topics_unsubscribe_request = {}
580
+ self.__user_rrpc_topics_subscribe_request_lock = threading.RLock()
581
+ self.__user_rrpc_topics_unsubscribe_request_lock = threading.RLock()
582
+ self.__user_rrpc_request_ids = []
583
+ self.__user_rrpc_request_id_index_map = {}
584
+ self.__user_rrpc_request_ids_lock = threading.RLock()
585
+ self.__user_rrpc_request_max_len = 100
586
+
587
+ # things topic - Alink
588
+ self.__thing_topic_prop_post = "/sys/%s/%s/thing/event/property/post" % (self.__product_key, self.__device_name)
589
+ self.__thing_topic_prop_post_reply = self.__thing_topic_prop_post + "_reply"
590
+ self.__thing_topic_prop_set = "/sys/%s/%s/thing/service/property/set" % (self.__product_key, self.__device_name)
591
+ self.__thing_topic_prop_set_reply = self.__thing_topic_prop_set + "_reply"
592
+ self.__thing_topic_prop_get = "/sys/%s/%s/thing/service/property/get" % (self.__product_key, self.__device_name)
593
+ self.__thing_topic_event_post_pattern = "/sys/%s/%s/thing/event/%s/post"
594
+ self.__gateway_topic_add_subdev_topo = "/sys/%s/%s/thing/topo/add" % (self.__product_key, self.__device_name)
595
+ self.__gateway_topic_add_subdev_topo_reply = self.__gateway_topic_add_subdev_topo + "_reply"
596
+
597
+ self.__gateway_topic_delete_subdev_topo = "/sys/%s/%s/thing/topo/delete" % (
598
+ self.__product_key,
599
+ self.__device_name,
600
+ )
601
+ self.__gateway_topic_delete_subdev_topo_reply = self.__gateway_topic_delete_subdev_topo + "_reply"
602
+
603
+ self.__gateway_topic_login_subdev = "/ext/session/%s/%s/combine/batch_login" % (
604
+ self.__product_key,
605
+ self.__device_name,
606
+ )
607
+ self.__gateway_topic_login_subdev_reply = self.__gateway_topic_login_subdev + "_reply"
608
+
609
+ self.__gateway_topic_logout_subdev = "/ext/session/%s/%s/combine/batch_logout" % (
610
+ self.__product_key,
611
+ self.__device_name,
612
+ )
613
+ self.__gateway_topic_logout_subdev_reply = self.__gateway_topic_logout_subdev + "_reply"
614
+
615
+ self.__gateway_topic_register_subdev = "/sys/%s/%s/thing/sub/register" % (
616
+ self.__product_key,
617
+ self.__device_name,
618
+ )
619
+ self.__gateway_topic_register_subdev_reply = self.__gateway_topic_register_subdev + "_reply"
620
+
621
+ self.__gateway_topic_product_register_subdev = "/sys/%s/%s/thing/proxy/provisioning/product_register" % (
622
+ self.__product_key,
623
+ self.__device_name,
624
+ )
625
+ self.__gateway_topic_product_register_subdev_reply = self.__gateway_topic_product_register_subdev + "_reply"
626
+
627
+ self.__gateway_topic_topo_change = "/sys/%s/%s/thing/topo/change" % (self.__product_key, self.__device_name)
628
+ self.__dynamic_register_nwl_topic = "/ext/regnwl"
629
+ self.__dynamic_register_topic = "/ext/register"
630
+
631
+ self.__ota_report_version_topic = "/ota/device/inform/%s/%s" % (self.__product_key, self.__device_name)
632
+ self.__ota_push_topic = "/ota/device/upgrade/%s/%s" % (self.__product_key, self.__device_name)
633
+ self.__ota_pull_topic = "/sys/%s/%s/thing/ota/firmware/get" % (self.__product_key, self.__device_name)
634
+ self.__ota_pull_reply_topic = "/sys/%s/%s/thing/ota/firmware/get_reply" % (
635
+ self.__product_key,
636
+ self.__device_name,
637
+ )
638
+
639
+ self.__thing_prop_post_mid = {}
640
+ self.__thing_prop_post_mid_lock = threading.Lock()
641
+ self.__thing_prop_set_reply_mid = {}
642
+ self.__thing_prop_set_reply_mid_lock = threading.Lock()
643
+ self.__gateway_add_subdev_topo_mid = {}
644
+ self.__gateway_add_subdev_topo_mid_lock = threading.Lock()
645
+ self.__gateway_delete_subdev_topo_mid = {}
646
+ self.__gateway_delete_subdev_topo_mid_lock = threading.Lock()
647
+ # event:post topic
648
+ self.__thing_topic_event_post = {}
649
+ self.__thing_topic_event_post_reply = set()
650
+ self.__thing_events = set()
651
+ self.__thing_request_id_max = 1000000
652
+ self.__thing_request_value = 0
653
+ self.__thing_request_id = {}
654
+ self.__thing_request_id_lock = threading.Lock()
655
+ self.__thing_event_post_mid = {}
656
+ self.__thing_event_post_mid_lock = threading.Lock()
657
+
658
+ self.__thing_topic_shadow_get = "/shadow/get/%s/%s" % (self.__product_key, self.__device_name)
659
+ self.__thing_topic_shadow_update = "/shadow/update/%s/%s" % (self.__product_key, self.__device_name)
660
+ self.__thing_shadow_mid = {}
661
+ self.__thing_shadow_mid_lock = threading.Lock()
662
+
663
+ # service topic
664
+ self.__thing_topic_service_pattern = "/sys/%s/%s/thing/service/%s"
665
+ self.__thing_topic_services = set()
666
+ self.__thing_topic_services_reply = set()
667
+ self.__thing_services = set()
668
+ self.__thing_answer_service_mid = {}
669
+ self.__thing_answer_service_mid_lock = threading.Lock()
670
+
671
+ # thing topic - raw
672
+ self.__thing_topic_raw_up = "/sys/%s/%s/thing/model/up_raw" % (self.__product_key, self.__device_name)
673
+ self.__thing_topic_raw_up_reply = self.__thing_topic_raw_up + "_reply"
674
+ self.__thing_topic_raw_down = "/sys/%s/%s/thing/model/down_raw" % (self.__product_key, self.__device_name)
675
+ self.__thing_topic_raw_down_reply = self.__thing_topic_raw_down + "_reply"
676
+ self.__thing_raw_up_mid = {}
677
+ self.__thing_raw_up_mid_lock = threading.Lock()
678
+ self.__thing_raw_down_reply_mid = {}
679
+ self.__thing_raw_down_reply_mid_lock = threading.Lock()
680
+
681
+ # thing topic - device_info
682
+ self.__thing_topic_update_device_info_up = "/sys/%s/%s/thing/deviceinfo/update" % (
683
+ self.__product_key,
684
+ self.__device_name,
685
+ )
686
+ self.__thing_topic_update_device_info_reply = self.__thing_topic_update_device_info_up + "_reply"
687
+ self.__thing_topic_delete_device_info_up = "/sys/%s/%s/thing/deviceinfo/delete" % (
688
+ self.__product_key,
689
+ self.__device_name,
690
+ )
691
+ self.__thing_topic_delete_device_info_reply = self.__thing_topic_delete_device_info_up + "_reply"
692
+ self.__thing_update_device_info_up_mid = {}
693
+ self.__thing_update_device_info_up_mid_lock = threading.Lock()
694
+ self.__thing_delete_device_info_up_mid = {}
695
+ self.__thing_delete_device_info_up_mid_lock = threading.Lock()
696
+
697
+ # properties
698
+ self.__thing_properties_set = set()
699
+ self.__thing_properties_get = set()
700
+ self.__thing_properties_post = set()
701
+
702
+ # thing setup state
703
+ self.__thing_setup_state = False
704
+ self.__thing_raw_only = False
705
+ self.__thing_enable_state = False
706
+ self.__on_gateway_add_subdev_topo_reply = None
707
+ self.__on_gateway_delete_subdev_topo_reply = None
708
+ self.__on_gateway_login_subdev_reply = None
709
+ self.__on_gateway_logout_subdev_reply = None
710
+ self.__on_gateway_register_subdev_reply = None
711
+ self.__on_gateway_product_register_subdev_reply = None
712
+ self.__on_gateway_topo_change = None
713
+ self.__on_device_dynamic_register_nwl_reply = None
714
+ self.__on_ota_message_arrived = None
715
+
716
+ if self.__just_for_pycharm_autocomplete:
717
+ self.__mqtt_client = mqtt.Client(CallbackAPIVersion.VERSION1)
718
+
719
+ # device interface info
720
+ self.__device_info_topic = "/sys/%s/%s/thing/deviceinfo/update" % (self.__product_key, self.__device_name)
721
+ self.__device_info_topic_reply = self.__device_info_topic + "_reply"
722
+ self.__device_info_mid_lock = threading.Lock()
723
+ self.__device_info_mid = {}
724
+
725
+ # connect_async
726
+ self.__connect_async_req = False
727
+ self.__worker_loop_exit_req = False
728
+ self.__worker_loop_runing_state = False
729
+ self.__worker_loop_exit_req_lock = threading.Lock()
730
+
731
+ # loop thread
732
+ self.__loop_thread = LinkKit.LoopThread(self.__link_log)
733
+
734
+ # HandlerTask
735
+ self.__handler_task = LinkKit.__HandlerTask(self.__link_log)
736
+ self.__handler_task_cmd_on_connect = "on_connect"
737
+ self.__handler_task_cmd_on_disconnect = "on_disconnect"
738
+ self.__handler_task_cmd_on_message = "on_message"
739
+ self.__handler_task_cmd_on_publish = "on_publish"
740
+ self.__handler_task_cmd_on_subscribe = "on_subscribe"
741
+ self.__handler_task_cmd_on_unsubscribe = "on_unsubscribe"
742
+ self.__handler_task.register_cmd_callback(
743
+ self.__handler_task_cmd_on_connect, self.__handler_task_on_connect_callback
744
+ )
745
+ self.__handler_task.register_cmd_callback(
746
+ self.__handler_task_cmd_on_disconnect, self.__handler_task_on_disconnect_callback
747
+ )
748
+ self.__handler_task.register_cmd_callback(
749
+ self.__handler_task_cmd_on_message, self.__handler_task_on_message_callback
750
+ )
751
+ self.__handler_task.register_cmd_callback(
752
+ self.__handler_task_cmd_on_publish, self.__handler_task_on_publish_callback
753
+ )
754
+ self.__handler_task.register_cmd_callback(
755
+ self.__handler_task_cmd_on_subscribe, self.__handler_task_on_subscribe_callback
756
+ )
757
+ self.__handler_task.register_cmd_callback(
758
+ self.__handler_task_cmd_on_unsubscribe, self.__handler_task_on_unsubscribe_callback
759
+ )
760
+ self.__handler_task.start()
761
+
762
+ @property
763
+ def on_device_dynamic_register(self):
764
+ return None
765
+
766
+ @on_device_dynamic_register.setter
767
+ def on_device_dynamic_register(self, value) -> None:
768
+ self.__on_device_dynamic_register = value
769
+
770
+ @property
771
+ def on_connect(self):
772
+ return self.__on_connect
773
+
774
+ @on_connect.setter
775
+ def on_connect(self, value) -> None:
776
+ self.__on_connect = value
777
+
778
+ @property
779
+ def on_disconnect(self):
780
+ return self.__on_disconnect
781
+
782
+ @on_disconnect.setter
783
+ def on_disconnect(self, value) -> None:
784
+ self.__on_disconnect = value
785
+
786
+ @property
787
+ def on_publish_topic(self):
788
+ return None
789
+
790
+ @on_publish_topic.setter
791
+ def on_publish_topic(self, value) -> None:
792
+ self.__on_publish_topic = value
793
+
794
+ @property
795
+ def on_subscribe_topic(self):
796
+ return None
797
+
798
+ @on_subscribe_topic.setter
799
+ def on_subscribe_topic(self, value) -> None:
800
+ self.__on_subscribe_topic = value
801
+
802
+ @property
803
+ def on_unsubscribe_topic(self):
804
+ return None
805
+
806
+ @on_unsubscribe_topic.setter
807
+ def on_unsubscribe_topic(self, value) -> None:
808
+ self.__on_unsubscribe_topic = value
809
+
810
+ @property
811
+ def on_topic_message(self):
812
+ return None
813
+
814
+ @on_topic_message.setter
815
+ def on_topic_message(self, value) -> None:
816
+ self.__on_topic_message = value
817
+
818
+ @property
819
+ def on_topic_rrpc_message(self):
820
+ return None
821
+
822
+ @on_topic_rrpc_message.setter
823
+ def on_topic_rrpc_message(self, value) -> None:
824
+ self.__on_topic_rrpc_message = value
825
+
826
+ @property
827
+ def on_thing_create(self):
828
+ return None
829
+
830
+ @on_thing_create.setter
831
+ def on_thing_create(self, value) -> None:
832
+ self.__on_thing_create = value
833
+
834
+ @property
835
+ def on_thing_enable(self):
836
+ return None
837
+
838
+ @on_thing_enable.setter
839
+ def on_thing_enable(self, value) -> None:
840
+ self.__on_thing_enable = value
841
+
842
+ @property
843
+ def on_thing_disable(self):
844
+ return None
845
+
846
+ @on_thing_disable.setter
847
+ def on_thing_disable(self, value) -> None:
848
+ self.__on_thing_disable = value
849
+
850
+ @property
851
+ def on_thing_raw_data_arrived(self):
852
+ return None
853
+
854
+ @on_thing_raw_data_arrived.setter
855
+ def on_thing_raw_data_arrived(self, value) -> None:
856
+ self.__on_thing_raw_data_arrived = value
857
+
858
+ @property
859
+ def on_thing_raw_data_post(self):
860
+ return self.__on_thing_raw_data_post
861
+
862
+ @property
863
+ def on_thing_device_info_update(self):
864
+ return self.__on_thing_device_info_update
865
+
866
+ @on_thing_device_info_update.setter
867
+ def on_thing_device_info_update(self, value) -> None:
868
+ self.__on_thing_device_info_update = value
869
+
870
+ @property
871
+ def on_thing_device_info_delete(self):
872
+ return self.__on_thing_device_info_delete
873
+
874
+ @on_thing_device_info_delete.setter
875
+ def on_thing_device_info_delete(self, value) -> None:
876
+ self.__on_thing_device_info_delete = value
877
+
878
+ @on_thing_raw_data_post.setter
879
+ def on_thing_raw_data_post(self, value) -> None:
880
+ self.__on_thing_raw_data_post = value
881
+
882
+ @property
883
+ def on_thing_call_service(self):
884
+ return None
885
+
886
+ @on_thing_call_service.setter
887
+ def on_thing_call_service(self, value) -> None:
888
+ self.__on_thing_call_service = value
889
+
890
+ @property
891
+ def on_thing_prop_changed(self):
892
+ return None
893
+
894
+ @on_thing_prop_changed.setter
895
+ def on_thing_prop_changed(self, value) -> None:
896
+ self.__on_thing_prop_changed = value
897
+
898
+ @property
899
+ def on_thing_event_post(self):
900
+ return self.__on_thing_event_post
901
+
902
+ @on_thing_event_post.setter
903
+ def on_thing_event_post(self, value) -> None:
904
+ self.__on_thing_event_post = value
905
+
906
+ @property
907
+ def on_thing_prop_post(self):
908
+ return self.__on_thing_prop_post
909
+
910
+ @on_thing_prop_post.setter
911
+ def on_thing_prop_post(self, value) -> None:
912
+ self.__on_thing_prop_post = value
913
+
914
+ @property
915
+ def on_thing_shadow_get(self):
916
+ return self.__on_thing_shadow_get
917
+
918
+ @on_thing_shadow_get.setter
919
+ def on_thing_shadow_get(self, value) -> None:
920
+ self.__on_thing_shadow_get = value
921
+
922
+ @property
923
+ def on_file_upload_begin(self):
924
+ return self.__on_file_upload_begin
925
+
926
+ @on_file_upload_begin.setter
927
+ def on_file_upload_begin(self, value) -> None:
928
+ self.__on_file_upload_begin = value
929
+
930
+ @property
931
+ def on_file_upload_end(self):
932
+ return self.__on_file_upload_end
933
+
934
+ @on_file_upload_end.setter
935
+ def on_file_upload_end(self, value) -> None:
936
+ self.__on_file_upload_end = value
937
+
938
+ @property
939
+ def on_gateway_add_subdev_topo_reply(self):
940
+ return None
941
+
942
+ @on_gateway_add_subdev_topo_reply.setter
943
+ def on_gateway_add_subdev_topo_reply(self, value) -> None:
944
+ self.__on_gateway_add_subdev_topo_reply = value
945
+
946
+ @property
947
+ def on_gateway_delete_subdev_topo_reply(self):
948
+ return None
949
+
950
+ @on_gateway_delete_subdev_topo_reply.setter
951
+ def on_gateway_delete_subdev_topo_reply(self, value) -> None:
952
+ self.__on_gateway_delete_subdev_topo_reply = value
953
+
954
+ @property
955
+ def on_gateway_login_subdev_reply(self):
956
+ return None
957
+
958
+ @on_gateway_login_subdev_reply.setter
959
+ def on_gateway_login_subdev_reply(self, value) -> None:
960
+ self.__on_gateway_login_subdev_reply = value
961
+
962
+ @property
963
+ def on_gateway_logout_subdev_reply(self):
964
+ return None
965
+
966
+ @on_gateway_logout_subdev_reply.setter
967
+ def on_gateway_logout_subdev_reply(self, value) -> None:
968
+ self.__on_gateway_logout_subdev_reply = value
969
+
970
+ @property
971
+ def on_gateway_register_subdev_reply(self):
972
+ return None
973
+
974
+ @on_gateway_register_subdev_reply.setter
975
+ def on_gateway_register_subdev_reply(self, value) -> None:
976
+ self.__on_gateway_register_subdev_reply = value
977
+
978
+ @property
979
+ def on_gateway_product_register_subdev_reply(self):
980
+ return None
981
+
982
+ @on_gateway_product_register_subdev_reply.setter
983
+ def on_gateway_product_register_subdev_reply(self, value) -> None:
984
+ self.__on_gateway_product_register_subdev_reply = value
985
+
986
+ @property
987
+ def on_device_dynamic_register_nwl_reply(self):
988
+ return None
989
+
990
+ @on_device_dynamic_register_nwl_reply.setter
991
+ def on_device_dynamic_register_nwl_reply(self, value) -> None:
992
+ self.__on_device_dynamic_register_nwl_reply = value
993
+
994
+ @property
995
+ def on_ota_message_arrived(self):
996
+ return None
997
+
998
+ @on_ota_message_arrived.setter
999
+ def on_ota_message_arrived(self, value) -> None:
1000
+ self.__on_ota_message_arrived = value
1001
+
1002
+ @property
1003
+ def on_gateway_topo_change(self):
1004
+ return None
1005
+
1006
+ @on_gateway_topo_change.setter
1007
+ def on_gateway_topo_change(self, value) -> None:
1008
+ self.__on_gateway_topo_change = value
1009
+
1010
+ def enable_logger(self, level) -> None:
1011
+ self.__link_log.config_logger(level)
1012
+ self.__link_log.enable_logger()
1013
+ if self.__mqtt_client is not None:
1014
+ self.__mqtt_client.enable_logger(self.__PahoLog)
1015
+ self.__PahoLog.setLevel(level)
1016
+
1017
+ def disable_logger(self) -> None:
1018
+ self.__link_log.disable_logger()
1019
+ if self.__mqtt_client is not None:
1020
+ self.__mqtt_client.disable_logger()
1021
+
1022
+ def config_logger(self, level) -> None:
1023
+ self.__link_log.config_logger(level)
1024
+ if self.__mqtt_client is not None:
1025
+ self.__PahoLog.setLevel(level)
1026
+
1027
+ def config_http2(self, endpoint=None):
1028
+ raise LinkKit.StateError("not supported any more")
1029
+
1030
+ def config_mqtt(
1031
+ self,
1032
+ port=8883,
1033
+ protocol="MQTTv311",
1034
+ transport="TCP",
1035
+ secure="TLS",
1036
+ keep_alive=60,
1037
+ clean_session=True,
1038
+ max_inflight_message=20,
1039
+ max_queued_message=40,
1040
+ auto_reconnect_min_sec=1,
1041
+ auto_reconnect_max_sec=60,
1042
+ cadata=None,
1043
+ endpoint=None,
1044
+ check_hostname=True,
1045
+ ):
1046
+ if self.__linkkit_state is not LinkKit.LinkKitState.INITIALIZED:
1047
+ raise LinkKit.StateError("not in INITIALIZED state")
1048
+ if port < 1 or port > 65535:
1049
+ raise ValueError("port wrong")
1050
+ if protocol not in ("MQTTv311", "MQTTv31"):
1051
+ raise ValueError("protocol wrong")
1052
+ if transport != "TCP":
1053
+ raise ValueError("transport wrong")
1054
+ if secure != "TLS" and secure != "":
1055
+ raise ValueError("secure wrong")
1056
+ if keep_alive < 30 or keep_alive > 1200:
1057
+ raise ValueError("keep_alive range wrong")
1058
+ if clean_session is not True and clean_session is not False:
1059
+ raise ValueError("clean session wrong")
1060
+ if max_queued_message < 0:
1061
+ raise ValueError("max_queued_message wrong")
1062
+ if max_inflight_message < 0:
1063
+ raise ValueError("max_inflight_message wrong")
1064
+ if auto_reconnect_min_sec < 1 or auto_reconnect_min_sec > 120 * 60:
1065
+ raise ValueError("auto_reconnect_min_sec wrong")
1066
+ if auto_reconnect_max_sec < 1 or auto_reconnect_max_sec > 120 * 60:
1067
+ raise ValueError("auto_reconnect_max_sec wrong")
1068
+ if auto_reconnect_min_sec > auto_reconnect_max_sec:
1069
+ raise ValueError("auto_reconnect_max_sec less than auto_reconnect_min_sec")
1070
+
1071
+ self.__link_log.info("config_mqtt enter")
1072
+ if self.__linkkit_state == LinkKit.LinkKitState.INITIALIZED:
1073
+ if port is not None:
1074
+ self.__mqtt_port = port
1075
+ if protocol is not None:
1076
+ self.__mqtt_protocol = protocol
1077
+ if transport is not None:
1078
+ self.__mqtt_transport = transport
1079
+ if secure is not None:
1080
+ self.__mqtt_secure = secure
1081
+ if keep_alive is not None:
1082
+ self.__mqtt_keep_alive = keep_alive
1083
+ if clean_session is not None:
1084
+ self.__mqtt_clean_session = clean_session
1085
+ if max_inflight_message is not None:
1086
+ self.__mqtt_max_inflight_message = max_inflight_message
1087
+ if max_queued_message is not None:
1088
+ self.__mqtt_max_queued_message = max_queued_message
1089
+ if auto_reconnect_min_sec is not None:
1090
+ self.__mqtt_auto_reconnect_min_sec = auto_reconnect_min_sec
1091
+ if auto_reconnect_max_sec is not None:
1092
+ self.__mqtt_auto_reconnect_max_sec = auto_reconnect_max_sec
1093
+ if cadata is not None:
1094
+ self.__aliyun_broker_ca_data = cadata
1095
+ if endpoint is not None:
1096
+ self.__endpoint = endpoint
1097
+ if check_hostname is not None:
1098
+ self.__check_hostname = check_hostname
1099
+ if check_hostname == False:
1100
+ self.__link_log.info("skip hostname check")
1101
+
1102
+ def config_device_info(self, interface_info) -> int:
1103
+ if self.__linkkit_state is not LinkKit.LinkKitState.INITIALIZED:
1104
+ raise LinkKit.StateError("LinkKit object not in INITIALIZED")
1105
+ if not isinstance(interface_info, str):
1106
+ raise ValueError("interface info must be string")
1107
+ if len(interface_info) > 160:
1108
+ return 1
1109
+ self.__device_interface_info = interface_info
1110
+ return 0
1111
+
1112
+ def get_product(self):
1113
+ return self.__product_key
1114
+
1115
+ def get_device_name(self):
1116
+ return self.__device_name
1117
+
1118
+ def get_endpoint(self):
1119
+ return self.__endpoint
1120
+
1121
+ def get_h2_endpoint(self):
1122
+ raise LinkKit.StateError("not supported any more")
1123
+
1124
+ def get_actual_endpoint(self):
1125
+ return self.__generate_endpoint()
1126
+
1127
+ def get_actual_h2_endpoint(self):
1128
+ raise LinkKit.StateError("not supported any more")
1129
+
1130
+ def __load_json(self, payload):
1131
+ return json.loads(self.__to_str(payload))
1132
+
1133
+ def __to_str(self, payload):
1134
+ if type(payload) is bytes:
1135
+ return str(payload, "utf-8")
1136
+ else:
1137
+ return payload
1138
+
1139
+ def upload_file_sync(self, local_filename, remote_filename=None, over_write=True, timeout=None):
1140
+ raise LinkKit.StateError("not supported any more")
1141
+
1142
+ def upload_file_async(self, local_filename, remote_filename=None, over_write=True):
1143
+ raise LinkKit.StateError("not supported any more")
1144
+
1145
+ def __upload_device_interface_info(self) -> int:
1146
+ request_id = self.__get_thing_request_id()
1147
+ payload = {
1148
+ "id": request_id,
1149
+ "version": "1.0",
1150
+ "params": [
1151
+ {"domain": "SYSTEM", "attrKey": "SYS_SDK_LANGUAGE", "attrValue": self.__sdk_program_language},
1152
+ {"domain": "SYSTEM", "attrKey": "SYS_LP_SDK_VERSION", "attrValue": self.__sdk_version},
1153
+ {"domain": "SYSTEM", "attrKey": "SYS_SDK_IF_INFO", "attrValue": self.__device_interface_info},
1154
+ ],
1155
+ "method": "thing.deviceinfo.update",
1156
+ }
1157
+ with self.__device_info_mid_lock:
1158
+ rc, mid = self.__mqtt_client.publish(self.__device_info_topic, json.dumps(payload), 0)
1159
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1160
+ self.__device_info_mid[mid] = self.__timestamp()
1161
+ return 0
1162
+ else:
1163
+ return 1
1164
+
1165
+ def destruct(self) -> None:
1166
+ if self.__linkkit_state is LinkKit.LinkKitState.DESTRUCTED:
1167
+ self.__link_log.info("LinkKit object has already destructed")
1168
+ return
1169
+ self.__link_log.debug("destruct enter")
1170
+ if (
1171
+ self.__linkkit_state == LinkKit.LinkKitState.CONNECTED
1172
+ or self.__linkkit_state == LinkKit.LinkKitState.CONNECTING
1173
+ ):
1174
+ self.__linkkit_state = LinkKit.LinkKitState.DESTRUCTING
1175
+ if self.__connect_async_req:
1176
+ with self.__worker_loop_exit_req_lock:
1177
+ self.__worker_loop_exit_req = True
1178
+ if self.__mqtt_client is not None:
1179
+ self.__mqtt_client.disconnect()
1180
+ self.__handler_task.wait_stop()
1181
+ else:
1182
+ self.__linkkit_state = LinkKit.LinkKitState.DESTRUCTING
1183
+ if self.__connect_async_req:
1184
+ with self.__worker_loop_exit_req_lock:
1185
+ self.__worker_loop_exit_req = True
1186
+ self.__handler_task.stop()
1187
+ self.__handler_task.wait_stop()
1188
+ self.__linkkit_state = LinkKit.LinkKitState.DESTRUCTED
1189
+
1190
+ def destroy(self) -> None:
1191
+ self.destruct()
1192
+
1193
+ def check_state(self) -> LinkKitState:
1194
+ return self.__linkkit_state
1195
+
1196
+ @staticmethod
1197
+ def __generate_random_str(randomlength: int = 16) -> str:
1198
+ """Generate radom string"""
1199
+ random_str = ""
1200
+ for i in range(randomlength):
1201
+ random_str += random.choice(string.digits + string.ascii_letters)
1202
+ return random_str
1203
+
1204
+ # 基于HTTPS的一型一密预注册
1205
+ def __dynamic_register_device(self) -> tuple[int, Any] | None:
1206
+ pk = self.__product_key
1207
+ ps = self.__product_secret
1208
+ dn = self.__device_name
1209
+ random_str = self.__generate_random_str(15)
1210
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cadata=self.__aliyun_broker_ca_data)
1211
+ sign_content = "deviceName%sproductKey%srandom%s" % (dn, pk, random_str)
1212
+ sign = hmac.new(ps.encode("utf-8"), sign_content.encode("utf-8"), hashlib.sha256).hexdigest()
1213
+ post_data = {"productKey": pk, "deviceName": dn, "random": random_str, "sign": sign, "signMethod": "hmacsha256"}
1214
+ data = urllib.parse.urlencode(post_data)
1215
+ data = data.encode("ascii")
1216
+ request_url = "https://iot-auth.%s.aliyuncs.com/auth/register/device" % self.__host_name
1217
+ with urllib.request.urlopen(request_url, data, context=context) as f:
1218
+ reply_data = f.read().decode("utf-8")
1219
+ reply_obj = self.__load_json(reply_data)
1220
+ if reply_obj["code"] == 200:
1221
+ reply_obj_data = reply_obj["data"]
1222
+ if reply_obj_data is not None:
1223
+ return 0, reply_obj_data["deviceSecret"]
1224
+ else:
1225
+ return 1, reply_obj["message"]
1226
+ return None
1227
+
1228
+ def __config_mqtt_client_internal(self) -> None:
1229
+ self.__link_log.info("start connect")
1230
+
1231
+ timestamp = str(int(time.time()))
1232
+ if self.__mqtt_secure == "TLS":
1233
+ securemode = 2
1234
+ else:
1235
+ securemode = 3
1236
+ if self.__device_interface_info:
1237
+ sii_option = "sii=%s," % (self.__device_interface_info)
1238
+ else:
1239
+ sii_option = ""
1240
+
1241
+ # 普通的一机一密认证方式
1242
+ if self.__is_valid_str(self.__device_secret):
1243
+ client_id = "%s&%s|securemode=%d,signmethod=hmacsha1,ext=1,_ss=1,lan=%s,_v=%s,%stimestamp=%s|" % (
1244
+ self.__product_key,
1245
+ self.__device_name,
1246
+ securemode,
1247
+ self.__sdk_program_language,
1248
+ self.__sdk_version,
1249
+ sii_option,
1250
+ timestamp,
1251
+ )
1252
+ username = self.__device_name + "&" + self.__product_key
1253
+ # calc sign
1254
+ sign_content = "clientId%sdeviceName%sproductKey%stimestamp%s" % (
1255
+ self.__product_key + "&" + self.__device_name,
1256
+ self.__device_name,
1257
+ self.__product_key,
1258
+ timestamp,
1259
+ )
1260
+ password = hmac.new(
1261
+ self.__device_secret.encode("utf-8"), sign_content.encode("utf-8"), hashlib.sha1
1262
+ ).hexdigest()
1263
+ # 通过username, passwd, cilentid直连接入的方式
1264
+ elif (
1265
+ self.__is_valid_str(self.__client_id)
1266
+ and self.__is_valid_str(self.__username)
1267
+ and self.__is_valid_str(self.__password)
1268
+ ):
1269
+ # 如果已经通过基于mqtt的免预注册一型一密协议获取到了client_id, user_name, password
1270
+ client_id = self.__client_id
1271
+ username = self.__username
1272
+ password = self.__password
1273
+ # 基于mqtt协议的一型一密,包括预注册和非预注册两种, 尝试去获取username, clientid和password
1274
+ elif self.__is_valid_str(self.__product_secret):
1275
+ if self.__auth_type == "regnwl":
1276
+ # 通过一型一密免预注册协议发起认证
1277
+ self.__dynamic_register_nwl_flag = 1
1278
+ elif self.__auth_type == "register":
1279
+ # 通过一型一密预注册协议发起认证
1280
+ self.__dynamic_register_flag = 1
1281
+ else:
1282
+ raise LinkKit.StateError("unknow dynreg param error")
1283
+
1284
+ auth_type_str = self.__auth_type
1285
+ random_str = self.__generate_random_str(15)
1286
+ if self.__is_valid_str(self.__instance_id):
1287
+ client_id = "%s.%s|random=%s,authType=%s,securemode=2,signmethod=hmacsha256,instanceId=%s|" % (
1288
+ self.__device_name,
1289
+ self.__product_key,
1290
+ random_str,
1291
+ auth_type_str,
1292
+ self.__instance_id,
1293
+ )
1294
+ else:
1295
+ client_id = "%s.%s|random=%s,authType=%s,securemode=2,signmethod=hmacsha256|" % (
1296
+ self.__device_name,
1297
+ self.__product_key,
1298
+ random_str,
1299
+ auth_type_str,
1300
+ )
1301
+ username = "%s&%s" % (self.__device_name, self.__product_key)
1302
+ password_src = "deviceName%sproductKey%srandom%s" % (self.__device_name, self.__product_key, random_str)
1303
+ password = hmac.new(
1304
+ self.__product_secret.encode("utf-8"), password_src.encode("utf-8"), hashlib.sha256
1305
+ ).hexdigest()
1306
+ else:
1307
+ raise LinkKit.StateError("unknow auth error")
1308
+
1309
+ # mqtt client start initialize
1310
+ mqtt_protocol_version = mqtt.MQTTv311
1311
+ if self.__mqtt_protocol == "MQTTv311":
1312
+ mqtt_protocol_version = mqtt.MQTTv311
1313
+ elif self.__mqtt_protocol == "MQTTv31":
1314
+ mqtt_protocol_version = mqtt.MQTTv31
1315
+ self.__mqtt_client = mqtt.Client(
1316
+ client_id=client_id, clean_session=self.__mqtt_clean_session, protocol=mqtt_protocol_version
1317
+ )
1318
+
1319
+ if self.__link_log.is_enabled():
1320
+ self.__mqtt_client.enable_logger(self.__PahoLog)
1321
+ self.__mqtt_client.username_pw_set(username, password)
1322
+ self.__mqtt_client.on_connect = self.__on_internal_connect
1323
+ self.__mqtt_client.on_disconnect = self.__on_internal_disconnect
1324
+ self.__mqtt_client.on_message = self.__on_internal_message
1325
+ self.__mqtt_client.on_publish = self.__on_internal_publish
1326
+ self.__mqtt_client.on_subscribe = self.__on_internal_subscribe
1327
+ self.__mqtt_client.on_unsubscribe = self.__on_internal_unsubscribe
1328
+
1329
+ self.__mqtt_client.reconnect_delay_set(self.__mqtt_auto_reconnect_min_sec, self.__mqtt_auto_reconnect_max_sec)
1330
+ self.__mqtt_client.max_queued_messages_set(self.__mqtt_max_queued_message)
1331
+ self.__mqtt_client.max_inflight_messages_set(self.__mqtt_max_inflight_message)
1332
+
1333
+ # mqtt set tls
1334
+ self.__link_log.debug("current working directory:" + os.getcwd())
1335
+ if self.__mqtt_secure == "TLS":
1336
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cadata=self.__aliyun_broker_ca_data)
1337
+ context.check_hostname = self.__check_hostname
1338
+ self.__mqtt_client.tls_set_context(context)
1339
+ # mqtt client start connect
1340
+ self.__host_name_internal = self.__generate_endpoint()
1341
+
1342
+ def __generate_endpoint(self):
1343
+ if self.__endpoint:
1344
+ return self.__endpoint
1345
+ elif self.__host_name == "127.0.0.1" or self.__host_name == "localhost":
1346
+ return self.__host_name
1347
+ elif self.__is_valid_str(self.__instance_id):
1348
+ return self.__instance_id + ".mqtt.iothub.aliyuncs.com"
1349
+ else:
1350
+ return "%s.iot-as-mqtt.%s.aliyuncs.com" % (self.__product_key, self.__host_name)
1351
+
1352
+ def connect(self):
1353
+ raise LinkKit.StateError("not supported")
1354
+
1355
+ def connect_async(self):
1356
+ self.__link_log.debug("connect_async")
1357
+ if self.__linkkit_state == LinkKit.LinkKitState.CONNECTED:
1358
+ self.__link_log.info("already connected, returning")
1359
+ return
1360
+ if self.__linkkit_state not in (LinkKit.LinkKitState.INITIALIZED, LinkKit.LinkKitState.DISCONNECTED):
1361
+ raise LinkKit.StateError("not in INITIALIZED or DISCONNECTED state")
1362
+ self.__connect_async_req = True
1363
+ with self.__worker_loop_exit_req_lock:
1364
+ self.__worker_loop_exit_req = False
1365
+ return self.__loop_thread.start(self.__loop_forever_internal)
1366
+
1367
+ def disconnect(self) -> None:
1368
+ self.__link_log.debug("disconnect")
1369
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1370
+ self.__link_log.info("already disconnected, return")
1371
+ return
1372
+ self.__linkkit_state = LinkKit.LinkKitState.DISCONNECTING
1373
+ if self.__connect_async_req:
1374
+ with self.__worker_loop_exit_req_lock:
1375
+ self.__worker_loop_exit_req = True
1376
+ self.__mqtt_client.disconnect()
1377
+ self.__loop_thread.stop()
1378
+
1379
+ @staticmethod
1380
+ def __check_topic_string(topic):
1381
+ if len(topic) > 128 or len(topic) == 0:
1382
+ raise ValueError("topic string length too long,need decrease %d bytes" % (128 - len(topic)))
1383
+
1384
+ def publish_topic(self, topic, payload=None, qos=1):
1385
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1386
+ self.__link_log.error("disconnected, pub fail")
1387
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1388
+ if topic is None or len(topic) == 0:
1389
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1390
+ if qos != 0 and qos != 1:
1391
+ return LinkKit.ErrorCode.INVALID_QOS.value, None
1392
+ self.__check_topic_string(topic)
1393
+ rc, mid = self.__mqtt_client.publish(topic, payload, qos)
1394
+ if rc == 0:
1395
+ return LinkKit.ErrorCode.SUCCESS.value, mid
1396
+ else:
1397
+ return LinkKit.ErrorCode.PAHO_MQTT_ERROR.value, None
1398
+
1399
+ def subscribe_topic(self, topic, qos=1):
1400
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1401
+ self.__link_log.error("disconnected, sub fail")
1402
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1403
+ if isinstance(topic, tuple):
1404
+ topic, qos = topic
1405
+
1406
+ if isinstance(topic, str):
1407
+ if qos < 0 or qos > 1:
1408
+ return LinkKit.ErrorCode.INVALID_QOS.value, None
1409
+ if topic is None or len(topic) == 0:
1410
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1411
+ self.__check_topic_string(topic)
1412
+ if topic not in self.__user_topics:
1413
+ self.__user_topics_request_lock.acquire()
1414
+ ret = self.__mqtt_client.subscribe(topic, qos)
1415
+ rc, mid = ret
1416
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1417
+ self.__user_topics_subscribe_request[mid] = [(topic, qos)]
1418
+ self.__user_topics_request_lock.release()
1419
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1420
+ return 0, mid
1421
+ if rc == mqtt.MQTT_ERR_NO_CONN:
1422
+ return 2, None
1423
+ return 3, None
1424
+ else:
1425
+ return 1, None
1426
+ elif isinstance(topic, list):
1427
+ topic_qos_list = []
1428
+ user_topic_dict = {}
1429
+ for t, q in topic:
1430
+ if q < 0 or q > 1:
1431
+ return LinkKit.ErrorCode.INVALID_QOS.value, None
1432
+ if t is None or len(t) == 0 or not isinstance(t, str):
1433
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1434
+ self.__check_topic_string(t)
1435
+ user_topic_dict[t] = q
1436
+ topic_qos_list.append((t, q))
1437
+ self.__user_topics_request_lock.acquire()
1438
+ ret = self.__mqtt_client.subscribe(topic_qos_list)
1439
+ rc, mid = ret
1440
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1441
+ self.__user_topics_subscribe_request[mid] = topic_qos_list
1442
+ self.__link_log.debug("__user_topics_subscribe_request add mid:%d" % mid)
1443
+ self.__user_topics_request_lock.release()
1444
+ return rc, mid
1445
+ else:
1446
+ self.__link_log.error("Parameter type wrong")
1447
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1448
+
1449
+ def unsubscribe_topic(self, topic):
1450
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1451
+ self.__link_log.error("disconnected, unsub fail")
1452
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1453
+ unsubscribe_topics = []
1454
+ if topic is None or topic == "":
1455
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1456
+ if isinstance(topic, str):
1457
+ self.__check_topic_string(topic)
1458
+ if topic not in self.__user_topics:
1459
+ return 1, None
1460
+ unsubscribe_topics.append(topic)
1461
+ elif isinstance(topic, list):
1462
+ for one_topic in topic:
1463
+ self.__check_topic_string(one_topic)
1464
+ if one_topic in self.__user_topics:
1465
+ unsubscribe_topics.append(one_topic)
1466
+ else:
1467
+ pass
1468
+ with self.__user_topics_unsubscribe_request_lock:
1469
+ if len(unsubscribe_topics) == 0:
1470
+ return 2, None
1471
+ ret = self.__mqtt_client.unsubscribe(unsubscribe_topics)
1472
+ rc, mid = ret
1473
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1474
+ self.__user_topics_unsubscribe_request[mid] = unsubscribe_topics
1475
+ return ret
1476
+ else:
1477
+ return 1, None
1478
+
1479
+ def __make_rrpc_topic(self, topic) -> str:
1480
+ return "/ext/rrpc/+%s" % (topic)
1481
+
1482
+ def subscribe_rrpc_topic(self, topic):
1483
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1484
+ self.__link_log.error("disconnected, sub rrpc fail")
1485
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1486
+ qos = 0
1487
+ if isinstance(topic, str):
1488
+ if topic is None or len(topic) == 0:
1489
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1490
+ self.__check_topic_string(topic)
1491
+ topic = self.__tidy_topic(topic)
1492
+ rrpc_topic = self.__make_rrpc_topic(topic)
1493
+ with self.__user_rrpc_topics_lock:
1494
+ not_exist = topic not in self.__user_rrpc_topics.keys()
1495
+ if not_exist:
1496
+ with self.__user_rrpc_topics_lock:
1497
+ self.__user_rrpc_topics[topic] = qos
1498
+ with self.__user_rrpc_topics_subscribe_request_lock:
1499
+ ret = self.__mqtt_client.subscribe(rrpc_topic, qos)
1500
+ rc, mid = ret
1501
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1502
+ self.__user_rrpc_topics_subscribe_request[mid] = [(rrpc_topic, qos)]
1503
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1504
+ return 0, mid
1505
+ if rc == mqtt.MQTT_ERR_NO_CONN:
1506
+ return 2, None
1507
+ return 3, None
1508
+ else:
1509
+ return 1, None
1510
+ elif isinstance(topic, list):
1511
+ topic_qos_list = []
1512
+ for t in topic:
1513
+ if t is None or len(t) == 0 or not isinstance(t, str):
1514
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1515
+ self.__check_topic_string(t)
1516
+ t = self.__tidy_topic(t)
1517
+ rrpc_t = self.__make_rrpc_topic(t)
1518
+ with self.__user_rrpc_topics_lock:
1519
+ self.__user_rrpc_topics[t] = qos
1520
+ topic_qos_list.append((rrpc_t, qos))
1521
+ with self.__user_rrpc_topics_subscribe_request_lock:
1522
+ ret = self.__mqtt_client.subscribe(topic_qos_list)
1523
+ rc, mid = ret
1524
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1525
+ self.__user_rrpc_topics_subscribe_request[mid] = topic_qos_list
1526
+ self.__link_log.debug("__user_rrpc_topics_subscribe_request add mid:%d" % mid)
1527
+ return rc, mid
1528
+ else:
1529
+ self.__link_log.debug("Parameter type wrong")
1530
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1531
+
1532
+ def unsubscribe_rrpc_topic(self, topic):
1533
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1534
+ self.__link_log.error("disconnected, unsub rrpc fail")
1535
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1536
+ unsubscribe_topics = []
1537
+ if topic is None or topic == "":
1538
+ return LinkKit.ErrorCode.INVALID_TOPIC.value, None
1539
+ if isinstance(topic, str):
1540
+ self.__check_topic_string(topic)
1541
+ topic = self.__tidy_topic(topic)
1542
+ with self.__user_rrpc_topics_lock:
1543
+ if topic not in self.__user_rrpc_topics:
1544
+ return 1, None
1545
+ rrpc_topic = self.__make_rrpc_topic(topic)
1546
+ unsubscribe_topics.append(rrpc_topic)
1547
+ with self.__user_rrpc_topics_lock:
1548
+ del self.__user_rrpc_topics[topic]
1549
+
1550
+ elif isinstance(topic, list):
1551
+ for one_topic in topic:
1552
+ self.__check_topic_string(one_topic)
1553
+ one_topic = self.__tidy_topic(one_topic)
1554
+ with self.__user_rrpc_topics_lock:
1555
+ if one_topic in self.__user_rrpc_topics:
1556
+ rrpc_topic = self.__make_rrpc_topic(one_topic)
1557
+ unsubscribe_topics.append(rrpc_topic)
1558
+ del self.__user_rrpc_topics[one_topic]
1559
+ else:
1560
+ pass
1561
+ with self.__user_rrpc_topics_unsubscribe_request_lock:
1562
+ if len(unsubscribe_topics) == 0:
1563
+ return 2, None
1564
+ ret = self.__mqtt_client.unsubscribe(unsubscribe_topics)
1565
+ rc, mid = ret
1566
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1567
+ self.__user_rrpc_topics_unsubscribe_request[mid] = unsubscribe_topics
1568
+ return ret
1569
+ else:
1570
+ return 1, None
1571
+
1572
+ def __on_internal_connect_safe(self, client, user_data, session_flag, rc) -> None:
1573
+ if rc == 0:
1574
+ self.__reset_reconnect_wait()
1575
+ self.__force_reconnect = False
1576
+ session_flag_internal = {"session present": session_flag}
1577
+ self.__handler_task.post_message(
1578
+ self.__handler_task_cmd_on_connect, (client, user_data, session_flag_internal, rc)
1579
+ )
1580
+
1581
+ def __loop_forever_internal(self):
1582
+ self.__link_log.debug("enter")
1583
+ self.__linkkit_state = LinkKit.LinkKitState.CONNECTING
1584
+
1585
+ # 为了保持存量设备兼容,保留了基于https的需要预注册的一型一密的预注册
1586
+ if not self.__is_valid_str(self.__device_secret) and self.__is_valid_str(self.__product_secret):
1587
+ lack_other_auth_info = (
1588
+ not self.__is_valid_str(self.__username)
1589
+ or not self.__is_valid_str(self.__client_id)
1590
+ or not self.__is_valid_str(self.__password)
1591
+ )
1592
+ if not self.__is_valid_str(self.__auth_type) and lack_other_auth_info:
1593
+ rc, value = self.__dynamic_register_device()
1594
+ try:
1595
+ self.__on_device_dynamic_register(rc, value, self.__user_data)
1596
+ if rc == 0:
1597
+ self.__device_secret = value
1598
+ else:
1599
+ self.__link_log.error("dynamic register device fail:" + value)
1600
+ self.__linkkit_state = LinkKit.LinkKitState.INITIALIZED
1601
+ return 1
1602
+ except Exception as e:
1603
+ self.__link_log.error(e)
1604
+ self.__linkkit_state = LinkKit.LinkKitState.INITIALIZED
1605
+ return 2
1606
+ try:
1607
+ self.__config_mqtt_client_internal()
1608
+ except ssl.SSLError as e:
1609
+ self.__link_log.error("config mqtt raise exception:" + str(e))
1610
+ self.__linkkit_state = LinkKit.LinkKitState.INITIALIZED
1611
+ self.__on_internal_connect_safe(None, None, 0, 6)
1612
+ return
1613
+
1614
+ try:
1615
+ self.__mqtt_client.connect_async(
1616
+ host=self.__host_name_internal, port=self.__mqtt_port, keepalive=self.__mqtt_keep_alive
1617
+ )
1618
+ except Exception as e:
1619
+ self.__link_log.error("__loop_forever_internal connect raise exception:" + str(e))
1620
+ self.__linkkit_state = LinkKit.LinkKitState.INITIALIZED
1621
+ self.__on_internal_connect_safe(None, None, 0, 7)
1622
+ return
1623
+ while True:
1624
+ if self.__worker_loop_exit_req:
1625
+ if self.__linkkit_state == LinkKit.LinkKitState.DESTRUCTING:
1626
+ self.__handler_task.stop()
1627
+ self.__linkkit_state = LinkKit.LinkKitState.DESTRUCTED
1628
+ break
1629
+ try:
1630
+ self.__linkkit_state = LinkKit.LinkKitState.CONNECTING
1631
+ self.__mqtt_client.reconnect()
1632
+ except OSError as e:
1633
+ self.__link_log.error(e)
1634
+ # if isinstance(e, socket.timeout):
1635
+ # self.__link_log.error("connect timeout")
1636
+ # self.__on_internal_connect_safe(None, None, 0, 8)
1637
+ # self.__reconnect_wait()
1638
+ # continue
1639
+ # if isinstance(e, ssl.SSLError):
1640
+ # self.__on_internal_connect_safe(None, None, 0, 6)
1641
+ # return
1642
+ if self.__linkkit_state == LinkKit.LinkKitState.CONNECTING:
1643
+ self.__linkkit_state = LinkKit.LinkKitState.DISCONNECTED
1644
+ self.__on_internal_connect_safe(None, None, 0, 9)
1645
+ if self.__linkkit_state == LinkKit.LinkKitState.DESTRUCTING:
1646
+ self.__handler_task.stop()
1647
+ self.__linkkit_state = LinkKit.LinkKitState.DESTRUCTED
1648
+ break
1649
+ self.__reconnect_wait()
1650
+ continue
1651
+ # 1. ca wrong 2.socket create timeout 3.connect timeout, call on_connect error
1652
+ # connect success
1653
+ rc = mqtt.MQTT_ERR_SUCCESS
1654
+ while rc == mqtt.MQTT_ERR_SUCCESS:
1655
+ try:
1656
+ rc = self.__mqtt_client.loop(self.__mqtt_request_timeout)
1657
+ except Exception as e:
1658
+ self.__link_log.info("loop error:" + str(e))
1659
+ self.__clean_timeout_message()
1660
+ self.__clean_thing_timeout_request_id()
1661
+ if self.__force_reconnect is True:
1662
+ self.__force_reconnect = False
1663
+ break
1664
+
1665
+ if self.__linkkit_state == LinkKit.LinkKitState.CONNECTED:
1666
+ self.__on_internal_disconnect(None, None, 1)
1667
+ self.__link_log.info("loop return:%r" % rc)
1668
+
1669
+ if self.__worker_loop_exit_req:
1670
+ if self.__linkkit_state == LinkKit.LinkKitState.DESTRUCTING:
1671
+ self.__handler_task.stop()
1672
+ self.__linkkit_state = LinkKit.LinkKitState.DESTRUCTED
1673
+ break
1674
+ self.__reconnect_wait()
1675
+ # 在mqtt + 一型一密免预注册的情况下,由于三元组不对,导致无法与服务器建连,因此需要退出,否则会一直重试
1676
+ if self.__dynamic_register_nwl_flag == 1:
1677
+ if self.__on_device_dynamic_register_nwl_reply is not None:
1678
+ self.__on_device_dynamic_register_nwl_reply(
1679
+ LinkKit.ErrorCode.DYNREG_AUTH_NWL_FAILED.value, None, None, None
1680
+ )
1681
+ self.__linkkit_state = LinkKit.LinkKitState.DISCONNECTED
1682
+ self.__dynamic_register_nwl_flag = 0
1683
+ break
1684
+ # 在mqtt + 一型一密预注册的情况下,由于三元组不对,导致无法与服务器建连,因此需要退出,否则会一直重试
1685
+ if self.__dynamic_register_flag == 1:
1686
+ if self.__on_device_dynamic_register is not None:
1687
+ self.__on_device_dynamic_register(LinkKit.ErrorCode.DYNREG_AUTH_FAILED.value, None, None)
1688
+ self.__linkkit_state = LinkKit.LinkKitState.DISCONNECTED
1689
+ self.__dynamic_register_flag = 0
1690
+ break
1691
+
1692
+ def __clean_timeout_message(self) -> None:
1693
+ # self.__link_log.debug("__clean_timeout_message enter")
1694
+ expire_timestamp = self.__timestamp() - self.__mqtt_request_timeout * 1000
1695
+ with self.__thing_prop_post_mid_lock:
1696
+ self.__clean_timeout_message_item(self.__thing_prop_post_mid, expire_timestamp)
1697
+ with self.__thing_event_post_mid_lock:
1698
+ self.__clean_timeout_message_item(self.__thing_event_post_mid, expire_timestamp)
1699
+ with self.__thing_answer_service_mid_lock:
1700
+ self.__clean_timeout_message_item(self.__thing_answer_service_mid, expire_timestamp)
1701
+ with self.__thing_raw_up_mid_lock:
1702
+ self.__clean_timeout_message_item(self.__thing_raw_up_mid, expire_timestamp)
1703
+ with self.__thing_raw_down_reply_mid_lock:
1704
+ self.__clean_timeout_message_item(self.__thing_raw_down_reply_mid, expire_timestamp)
1705
+ with self.__thing_prop_set_reply_mid_lock:
1706
+ self.__clean_timeout_message_item(self.__thing_prop_set_reply_mid, expire_timestamp)
1707
+ self.__clean_timeout_message_item(self.__device_info_mid, expire_timestamp)
1708
+ # self.__link_log.debug("__clean_timeout_message exit")
1709
+
1710
+ def __clean_timeout_message_item(self, mids, expire_time) -> None:
1711
+ for mid in list(mids.keys()):
1712
+ if mids[mid] < expire_time:
1713
+ timestamp = mids.pop(mid)
1714
+ self.__link_log.error("__clean_timeout_message_item pop:%r,timestamp:%r", mid, timestamp)
1715
+
1716
+ def __reconnect_wait(self) -> None:
1717
+ if self.__mqtt_auto_reconnect_sec == 0:
1718
+ self.__mqtt_auto_reconnect_sec = self.__mqtt_auto_reconnect_min_sec
1719
+ else:
1720
+ self.__mqtt_auto_reconnect_sec = min(self.__mqtt_auto_reconnect_sec * 2, self.__mqtt_auto_reconnect_max_sec)
1721
+ self.__mqtt_auto_reconnect_sec += random.randint(1, self.__mqtt_auto_reconnect_sec)
1722
+ time.sleep(self.__mqtt_auto_reconnect_sec)
1723
+
1724
+ def __reset_reconnect_wait(self) -> None:
1725
+ self.__mqtt_auto_reconnect_sec = 0
1726
+
1727
+ def start_worker_loop(self) -> None:
1728
+ pass
1729
+
1730
+ def thing_setup(self, file=None) -> int:
1731
+ if self.__linkkit_state is not LinkKit.LinkKitState.INITIALIZED:
1732
+ raise LinkKit.StateError("not in INITIALIZED state")
1733
+ if self.__thing_setup_state:
1734
+ return 1
1735
+ if file is None:
1736
+ self.__thing_raw_only = True
1737
+ self.__thing_setup_state = True
1738
+ return 0
1739
+ try:
1740
+ with open(file, encoding="utf-8") as f:
1741
+ tsl = json.load(f)
1742
+ index = 0
1743
+ while "events" in tsl and index < len(tsl["events"]):
1744
+ identifier = tsl["events"][index]["identifier"]
1745
+ if identifier == "post":
1746
+ output_data = tsl["events"][index]["outputData"]
1747
+ output_data_index = 0
1748
+ while output_data_index < len(output_data):
1749
+ output_data_item = output_data[output_data_index]["identifier"]
1750
+ self.__thing_properties_post.add(output_data_item)
1751
+ output_data_index += 1
1752
+ else:
1753
+ self.__thing_events.add(identifier)
1754
+ index += 1
1755
+ index = 0
1756
+ while "services" in tsl and index < len(tsl["services"]):
1757
+ identifier = tsl["services"][index]["identifier"]
1758
+ if identifier == "set":
1759
+ input_data = tsl["services"][index]["inputData"]
1760
+ input_data_index = 0
1761
+ while input_data_index < len(input_data):
1762
+ input_data_item = input_data[input_data_index]
1763
+ self.__thing_properties_set.add(input_data_item["identifier"])
1764
+ input_data_index += 1
1765
+ elif identifier == "get":
1766
+ output_data = tsl["services"][index]["outputData"]
1767
+ output_data_index = 0
1768
+ while output_data_index < len(output_data):
1769
+ output_data_item = output_data[output_data_index]
1770
+ self.__thing_properties_get.add(output_data_item["identifier"])
1771
+ output_data_index += 1
1772
+ else:
1773
+ self.__thing_services.add(identifier)
1774
+ service_reply_topic = self.__thing_topic_service_pattern % (
1775
+ self.__product_key,
1776
+ self.__device_name,
1777
+ identifier + "_reply",
1778
+ )
1779
+ self.__thing_topic_services_reply.add(service_reply_topic)
1780
+ index += 1
1781
+
1782
+ for event in self.__thing_events:
1783
+ post_topic = self.__thing_topic_event_post_pattern % (self.__product_key, self.__device_name, event)
1784
+ self.__thing_topic_event_post[event] = post_topic
1785
+ self.__thing_topic_event_post_reply.add(post_topic + "_reply")
1786
+ # service topic
1787
+ for service in self.__thing_services:
1788
+ self.__thing_topic_services.add(
1789
+ self.__thing_topic_service_pattern % (self.__product_key, self.__device_name, service)
1790
+ )
1791
+
1792
+ except Exception as e:
1793
+ self.__link_log.info("file open error:" + str(e))
1794
+ return 2
1795
+ self.__thing_setup_state = True
1796
+ return 0
1797
+
1798
+ def thing_raw_post_data(self, payload):
1799
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1800
+ self.__link_log.error("disconnected, post raw fail")
1801
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value
1802
+ with self.__thing_raw_up_mid_lock:
1803
+ rc, mid = self.__mqtt_client.publish(self.__thing_topic_raw_up, payload, 0)
1804
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1805
+ self.__thing_raw_up_mid[mid] = self.__timestamp()
1806
+ return 0
1807
+ return 1
1808
+
1809
+ def thing_raw_data_reply(self, payload):
1810
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1811
+ self.__link_log.error("disconnected, raw data reply fail")
1812
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value
1813
+ with self.__thing_raw_down_reply_mid_lock:
1814
+ rc, mid = self.__mqtt_client.publish(self.__thing_topic_raw_down_reply, payload, 0)
1815
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1816
+ self.__thing_raw_down_reply_mid[mid] = self.__timestamp()
1817
+ return 0
1818
+ return 1
1819
+
1820
+ def thing_update_device_info(self, payload):
1821
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1822
+ self.__link_log.error("disconnected, update device info fail")
1823
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1824
+ if not self.__thing_setup_state or not self.__thing_enable_state:
1825
+ raise LinkKit.StateError("not in SETUP & ENABLE state")
1826
+ return 1, None
1827
+ request_id = self.__get_thing_request_id()
1828
+ with self.__thing_update_device_info_up_mid_lock:
1829
+ rc, mid = self.__mqtt_client.publish(
1830
+ self.__thing_topic_update_device_info_up,
1831
+ self.__pack_alink_request(request_id, "thing.deviceinfo.update", payload),
1832
+ 0,
1833
+ )
1834
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1835
+ self.__thing_update_device_info_up_mid[mid] = self.__timestamp()
1836
+ return rc, request_id
1837
+ return 1, None
1838
+
1839
+ def thing_delete_device_info(self, payload):
1840
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1841
+ self.__link_log.error("disconnected, delete device info fail")
1842
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1843
+ if not self.__thing_setup_state or not self.__thing_enable_state:
1844
+ return 1
1845
+ request_id = self.__get_thing_request_id()
1846
+ with self.__thing_delete_device_info_up_mid_lock:
1847
+ rc, mid = self.__mqtt_client.publish(
1848
+ self.__thing_topic_delete_device_info_up,
1849
+ self.__pack_alink_request(request_id, "thing.deviceinfo.delete", payload),
1850
+ 0,
1851
+ )
1852
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1853
+ self.__thing_delete_device_info_up_mid[mid] = self.__timestamp()
1854
+ return rc, request_id
1855
+ return 1, None
1856
+
1857
+ def thing_update_tags(self, tagMap):
1858
+ if not isinstance(tagMap, dict):
1859
+ raise ValueError("tagMap must be a dictionary")
1860
+ return 1, None
1861
+
1862
+ payload = []
1863
+ for k, v in tagMap.items():
1864
+ payload.append({LinkKit.TAG_KEY: k, LinkKit.TAG_VALUE: v})
1865
+ return self.thing_update_device_info(payload)
1866
+
1867
+ def thing_remove_tags(self, tagKeys):
1868
+ if not isinstance(tagKeys, list) and not isinstance(tagKeys, tuple):
1869
+ raise ValueError("tagKeys must be a list or tuple")
1870
+ return 1, None
1871
+
1872
+ payload = []
1873
+ for tagKey in tagKeys:
1874
+ payload.append({LinkKit.TAG_KEY: tagKey})
1875
+ return self.thing_delete_device_info(payload)
1876
+
1877
+ def __pack_alink_request(self, request_id, method, params):
1878
+ request = {"id": request_id, "version": "1.0", "params": params, "method": method}
1879
+ return json.dumps(request)
1880
+
1881
+ def thing_answer_service(self, identifier, request_id, code, data=None):
1882
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1883
+ self.__link_log.error("disconnected, answer service fail")
1884
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value
1885
+ if not self.__thing_setup_state or not self.__thing_enable_state:
1886
+ return 1
1887
+ if data is None:
1888
+ data = {}
1889
+ response = {"id": request_id, "code": code, "data": data}
1890
+
1891
+ item = self.__pop_rrpc_service("alink_" + str(request_id))
1892
+ if item:
1893
+ service_reply_topic = item["topic"]
1894
+ else:
1895
+ service_reply_topic = self.__thing_topic_service_pattern % (
1896
+ self.__product_key,
1897
+ self.__device_name,
1898
+ identifier + "_reply",
1899
+ )
1900
+ with self.__thing_answer_service_mid_lock:
1901
+ rc, mid = self.__mqtt_client.publish(service_reply_topic, json.dumps(response), 0)
1902
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1903
+ self.__thing_answer_service_mid[mid] = self.__timestamp()
1904
+ return 0
1905
+ return 1
1906
+
1907
+ def __get_thing_request_id(self):
1908
+ with self.__thing_request_id_lock:
1909
+ self.__thing_request_value += 1
1910
+ if self.__thing_request_value > self.__thing_request_id_max:
1911
+ self.__thing_request_value = 0
1912
+ if len(self.__thing_request_id) > self.__mqtt_max_queued_message:
1913
+ return None
1914
+ if self.__thing_request_value not in self.__thing_request_id:
1915
+ self.__thing_request_id[self.__thing_request_value] = self.__timestamp()
1916
+ self.__link_log.debug("__get_thing_request_id pop:%r" % self.__thing_request_value)
1917
+ return str(self.__thing_request_value)
1918
+ return None
1919
+
1920
+ def __back_thing_request_id(self, post_id) -> None:
1921
+ with self.__thing_request_id_lock:
1922
+ try:
1923
+ self.__thing_request_id.pop(int(post_id))
1924
+ except Exception as e:
1925
+ self.__link_log.error("__back_thing_request_id pop:%r,%r" % (post_id, e))
1926
+
1927
+ def __reset_thing_request_id(self) -> None:
1928
+ with self.__thing_request_id_lock:
1929
+ self.__thing_request_value = 0
1930
+ self.__thing_request_id.clear()
1931
+
1932
+ def __clean_thing_timeout_request_id(self) -> None:
1933
+ with self.__thing_request_id_lock:
1934
+ expire_timestamp = self.__timestamp() - self.__mqtt_request_timeout * 1000
1935
+ for request_id in list(self.__thing_request_id.keys()):
1936
+ if self.__thing_request_id[request_id] < expire_timestamp:
1937
+ timestamp = self.__thing_request_id.pop(request_id)
1938
+ self.__link_log.error("__clean_thing_timeout_request_id pop:%r,timestamp:%r", request_id, timestamp)
1939
+
1940
+ def thing_trigger_event(self, event_tuple):
1941
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1942
+ self.__link_log.error("disconnected, trigger event fail")
1943
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1944
+ if not self.__thing_setup_state or not self.__thing_enable_state:
1945
+ return 1, None
1946
+ if isinstance(event_tuple, tuple):
1947
+ event, params = event_tuple
1948
+ else:
1949
+ return 1, None
1950
+ if event not in self.__thing_topic_event_post.keys():
1951
+ return 1, None
1952
+ request_id = self.__get_thing_request_id()
1953
+ if request_id is None:
1954
+ return 1
1955
+ request = {
1956
+ "id": request_id,
1957
+ "version": "1.0",
1958
+ "params": {
1959
+ "value": params,
1960
+ },
1961
+ "method": "thing.event.%s.post" % event,
1962
+ }
1963
+ with self.__thing_event_post_mid_lock:
1964
+ event_topic = self.__thing_topic_event_post[event]
1965
+ self.__link_log.debug("thing_trigger_event publish topic")
1966
+ rc, mid = self.__mqtt_client.publish(event_topic, json.dumps(request), 0)
1967
+ self.__link_log.debug("thing_trigger_event publish done")
1968
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1969
+ self.__thing_event_post_mid[mid] = self.__timestamp()
1970
+ return 0, request_id
1971
+ else:
1972
+ return 1, None
1973
+
1974
+ def thing_post_property(self, property_data):
1975
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
1976
+ self.__link_log.error("disconnected, post property fail")
1977
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
1978
+ if not self.__thing_setup_state or not self.__thing_enable_state:
1979
+ return 1, None
1980
+ request_params = property_data
1981
+ request_id = self.__get_thing_request_id()
1982
+ if request_id is None:
1983
+ return 1, None
1984
+ request = {"id": request_id, "version": "1.0", "params": request_params, "method": "thing.event.property.post"}
1985
+ with self.__thing_prop_post_mid_lock:
1986
+ rc, mid = self.__mqtt_client.publish(self.__thing_topic_prop_post, json.dumps(request), 1)
1987
+ if rc == mqtt.MQTT_ERR_SUCCESS:
1988
+ self.__thing_prop_post_mid[mid] = self.__timestamp()
1989
+ return 0, request_id
1990
+ else:
1991
+ return 1, None
1992
+
1993
+ def __on_internal_async_message(self, message) -> None:
1994
+ self.__link_log.debug("__on_internal_async_message topic:%r" % message.topic)
1995
+ triggered_flag = 0
1996
+
1997
+ if message.topic == self.__thing_topic_prop_set:
1998
+ payload = self.__load_json(message.payload)
1999
+ params = payload["params"]
2000
+ try:
2001
+ reply = {"id": payload["id"], "code": 200, "data": {}}
2002
+ with self.__thing_prop_set_reply_mid_lock:
2003
+ rc, mid = self.__mqtt_client.publish(self.__thing_topic_prop_set_reply, json.dumps(reply), 1)
2004
+ if rc == 0:
2005
+ self.__link_log.info("prop changed reply success,mid:%d" % mid)
2006
+ self.__thing_prop_set_reply_mid[mid] = self.__timestamp()
2007
+ self.__link_log.info("prop changed reply success")
2008
+ else:
2009
+ self.__link_log.info("prop changed reply fail")
2010
+ if self.__on_thing_prop_changed is not None:
2011
+ triggered_flag = 1
2012
+ self.__on_thing_prop_changed(params, self.__user_data)
2013
+ except Exception as e:
2014
+ self.__link_log.error("on_thing_prop_changed raise exception:%s" % e)
2015
+ elif message.topic == self.__device_info_topic_reply:
2016
+ payload = self.__load_json(message.payload)
2017
+ request_id = payload["id"]
2018
+ code = payload["code"]
2019
+ reply_message = payload["message"]
2020
+ data = payload["data"]
2021
+ self.__back_thing_request_id(request_id)
2022
+ if code != 200:
2023
+ self.__link_log.error("upload device info reply error:%s" % reply_message)
2024
+ try:
2025
+ if self.__on_thing_device_info_update is not None:
2026
+ triggered_flag = 1
2027
+ self.__on_thing_device_info_update(request_id, code, data, reply_message, self.__user_data)
2028
+ except Exception as e:
2029
+ self.__link_log.error("__on_thing_device_info_update process raise exception:%s" % e)
2030
+ elif message.topic == self.__thing_topic_prop_post_reply:
2031
+ payload = self.__load_json(message.payload)
2032
+ request_id = payload["id"]
2033
+ code = payload["code"]
2034
+ data = payload["data"]
2035
+ reply_message = payload["message"]
2036
+ try:
2037
+ if self.__on_thing_prop_post is not None:
2038
+ triggered_flag = 1
2039
+ self.__on_thing_prop_post(request_id, code, data, reply_message, self.__user_data)
2040
+ except Exception as e:
2041
+ self.__link_log.error("on_thing_prop_post raise exception:%s" % e)
2042
+ self.__back_thing_request_id(request_id)
2043
+ elif message.topic == self.__thing_topic_prop_get:
2044
+ pass
2045
+ elif message.topic in self.__thing_topic_event_post_reply:
2046
+ event = message.topic.split("/", 7)[6]
2047
+ payload = self.__load_json(message.payload)
2048
+ request_id = payload["id"]
2049
+ code = payload["code"]
2050
+ data = payload["data"]
2051
+ reply_message = payload["message"]
2052
+ self.__link_log.info("on_thing_event_post message:%s" % reply_message)
2053
+ try:
2054
+ if self.on_thing_event_post is not None:
2055
+ triggered_flag = 1
2056
+ self.on_thing_event_post(event, request_id, code, data, reply_message, self.__user_data)
2057
+ except Exception as e:
2058
+ self.__link_log.error("on_thing_event_post raise exception:%s" % e)
2059
+ self.__back_thing_request_id(request_id)
2060
+ elif message.topic in self.__thing_topic_services:
2061
+ identifier = message.topic.split("/", 6)[6]
2062
+ payload = self.__load_json(message.payload)
2063
+ try:
2064
+ request_id = payload["id"]
2065
+ params = payload["params"]
2066
+ if self.__on_thing_call_service is not None:
2067
+ triggered_flag = 1
2068
+ self.__on_thing_call_service(identifier, request_id, params, self.__user_data)
2069
+ except Exception as e:
2070
+ self.__link_log.error("on_thing_call_service raise exception: %s" % e)
2071
+ elif message.topic == self.__thing_topic_raw_down:
2072
+ try:
2073
+ if self.__on_thing_raw_data_arrived is not None:
2074
+ triggered_flag = 1
2075
+ self.__on_thing_raw_data_arrived(message.payload, self.__user_data)
2076
+ except Exception as e:
2077
+ self.__link_log.error("on_thing_raw_data_arrived process raise exception:%s" % e)
2078
+ elif message.topic == self.__thing_topic_raw_up_reply:
2079
+ try:
2080
+ if self.__on_thing_raw_data_post is not None:
2081
+ triggered_flag = 1
2082
+ self.__on_thing_raw_data_post(message.payload, self.__user_data)
2083
+ except Exception as e:
2084
+ self.__link_log.error("on_thing_raw_post_data process raise exception:%s" % e)
2085
+ elif message.topic == self.__thing_topic_update_device_info_reply:
2086
+ try:
2087
+ if self.__on_thing_device_info_update is not None:
2088
+ triggered_flag = 1
2089
+ payload = self.__load_json(message.payload)
2090
+ request_id = payload["id"]
2091
+ code = payload["code"]
2092
+ data = payload["data"]
2093
+ msg = payload["message"]
2094
+ self.__on_thing_device_info_update(request_id, code, data, msg, self.__user_data)
2095
+ except Exception as e:
2096
+ self.__link_log.error("__on_thing_device_info_update process raise exception:%s" % e)
2097
+ elif message.topic == self.__thing_topic_delete_device_info_reply:
2098
+ try:
2099
+ if self.__on_thing_device_info_delete is not None:
2100
+ triggered_flag = 1
2101
+ payload = self.__load_json(message.payload)
2102
+ request_id = payload["id"]
2103
+ code = payload["code"]
2104
+ data = payload["data"]
2105
+ msg = payload["message"]
2106
+ self.__on_thing_device_info_delete(request_id, code, data, msg, self.__user_data)
2107
+ except Exception as e:
2108
+ self.__link_log.error("__on_thing_device_info_update process raise exception:%s" % e)
2109
+ elif message.topic == self.__thing_topic_shadow_get:
2110
+ self.__try_parse_try_shadow(message.payload)
2111
+ try:
2112
+ if self.__on_thing_shadow_get is not None:
2113
+ triggered_flag = 1
2114
+ self.__on_thing_shadow_get(self.__load_json(message.payload), self.__user_data)
2115
+ except Exception as e:
2116
+ self.__link_log.error("__on_thing_shadow_get process raise exception:%s" % e)
2117
+ elif message.topic.startswith("/ext/rrpc/"):
2118
+ triggered_flag = self.__try_parse_rrpc_topic(message)
2119
+ elif message.topic == self.__gateway_topic_topo_change:
2120
+ try:
2121
+ payload = self.__load_json(message.payload)
2122
+ request_id = payload["id"]
2123
+ params = payload["params"]
2124
+ if self.__on_gateway_topo_change is not None:
2125
+ triggered_flag = 1
2126
+ self.__on_gateway_topo_change(request_id, params, self.__user_data)
2127
+ except Exception as e:
2128
+ self.__link_log.error("__on_gateway_topo_change process raise exception:%s" % e)
2129
+ elif message.topic == self.__gateway_topic_add_subdev_topo_reply:
2130
+ try:
2131
+ payload = self.__load_json(message.payload)
2132
+ request_id = payload["id"]
2133
+ code = payload["code"]
2134
+ data = payload["data"]
2135
+ msg = payload["message"]
2136
+ self.__back_thing_request_id(request_id)
2137
+ if self.__on_gateway_add_subdev_topo_reply is not None:
2138
+ triggered_flag = 1
2139
+ self.__on_gateway_add_subdev_topo_reply(request_id, code, data, msg, self.__user_data)
2140
+ except Exception as e:
2141
+ self.__link_log.error("__on_gateway_add_subdev_topo_reply process raise exception:%s" % e)
2142
+ elif message.topic == self.__gateway_topic_delete_subdev_topo_reply:
2143
+ try:
2144
+ payload = self.__load_json(message.payload)
2145
+ request_id = payload["id"]
2146
+ code = payload["code"]
2147
+ data = payload["data"]
2148
+ msg = payload["message"]
2149
+ self.__back_thing_request_id(request_id)
2150
+ if self.__on_gateway_delete_subdev_topo_reply is not None:
2151
+ triggered_flag = 1
2152
+ self.__on_gateway_delete_subdev_topo_reply(request_id, code, data, msg, self.__user_data)
2153
+ except Exception as e:
2154
+ self.__link_log.error("__on_gateway_delete_subdev_topo_reply process raise exception:%s" % e)
2155
+ elif message.topic == self.__gateway_topic_login_subdev_reply:
2156
+ try:
2157
+ payload = self.__load_json(message.payload)
2158
+ request_id = payload["id"]
2159
+ code = payload["code"]
2160
+ data = payload["data"]
2161
+ msg = payload["message"]
2162
+ self.__back_thing_request_id(request_id)
2163
+ if self.__on_gateway_login_subdev_reply is not None:
2164
+ triggered_flag = 1
2165
+ self.__on_gateway_login_subdev_reply(request_id, code, data, msg, self.__user_data)
2166
+ except Exception as e:
2167
+ self.__link_log.error("__on_gateway_login_subdev_reply process raise exception:%s" % e)
2168
+ elif message.topic == self.__gateway_topic_logout_subdev_reply:
2169
+ try:
2170
+ payload = self.__load_json(message.payload)
2171
+ request_id = payload["id"]
2172
+ code = payload["code"]
2173
+ data = payload["data"]
2174
+ msg = payload["message"]
2175
+ self.__back_thing_request_id(request_id)
2176
+ if self.__on_gateway_logout_subdev_reply is not None:
2177
+ triggered_flag = 1
2178
+ self.__on_gateway_logout_subdev_reply(request_id, code, data, msg, self.__user_data)
2179
+ except Exception as e:
2180
+ self.__link_log.error("__on_gateway_logout_subdev_reply process raise exception:%s" % e)
2181
+ elif message.topic == self.__gateway_topic_register_subdev_reply:
2182
+ try:
2183
+ payload = self.__load_json(message.payload)
2184
+ request_id = payload["id"]
2185
+ code = payload["code"]
2186
+ data = payload["data"]
2187
+ msg = payload["message"]
2188
+ self.__back_thing_request_id(request_id)
2189
+ if self.__on_gateway_register_subdev_reply is not None:
2190
+ triggered_flag = 1
2191
+ self.__on_gateway_register_subdev_reply(request_id, code, data, msg, self.__user_data)
2192
+ except Exception as e:
2193
+ self.__link_log.error("__on_gateway_register_subdev_reply process raise exception:%s" % e)
2194
+ elif message.topic == self.__gateway_topic_product_register_subdev_reply:
2195
+ try:
2196
+ payload = self.__load_json(message.payload)
2197
+ request_id = payload["id"]
2198
+ code = payload["code"]
2199
+ data = payload["data"]
2200
+ msg = payload["message"]
2201
+ self.__back_thing_request_id(request_id)
2202
+ if self.__on_gateway_product_register_subdev_reply is not None:
2203
+ triggered_flag = 1
2204
+ self.__on_gateway_product_register_subdev_reply(request_id, code, data, msg, self.__user_data)
2205
+ except Exception as e:
2206
+ self.__link_log.error("__on_gateway_product_register_subdev_reply process raise exception:%s" % e)
2207
+ elif message.topic == self.__dynamic_register_topic:
2208
+ try:
2209
+ payload = self.__load_json(message.payload)
2210
+ device_secret = payload["deviceSecret"]
2211
+ self.disconnect()
2212
+ self.__dynamic_register_flag = 0
2213
+ if self.__on_device_dynamic_register is not None:
2214
+ triggered_flag = 1
2215
+ self.__on_device_dynamic_register(LinkKit.ErrorCode.SUCCESS.value, device_secret, None)
2216
+ except Exception as e:
2217
+ self.__link_log.error("__on_device_dynamic_register process raise exception:%s" % e)
2218
+
2219
+ elif message.topic == self.__dynamic_register_nwl_topic:
2220
+ # 一型一密免动态注册获取到username和token
2221
+ try:
2222
+ payload = self.__load_json(message.payload)
2223
+ client_id = payload["clientId"]
2224
+ client_id = client_id + "|authType=connwl,securemode=-2,_ss=1,ext=3,lan=%s,_v=%s|" % (
2225
+ self.__sdk_program_language,
2226
+ self.__sdk_version,
2227
+ )
2228
+ product_key = payload["productKey"]
2229
+ device_name = payload["deviceName"]
2230
+ username = device_name + "&" + product_key
2231
+ password = payload["deviceToken"]
2232
+ self.disconnect()
2233
+ self.__dynamic_register_nwl_flag = 0
2234
+ if self.__on_device_dynamic_register_nwl_reply is not None:
2235
+ triggered_flag = 1
2236
+ self.__on_device_dynamic_register_nwl_reply(
2237
+ LinkKit.ErrorCode.SUCCESS.value, client_id, username, password
2238
+ )
2239
+ except Exception as e:
2240
+ self.__link_log.error("__on_device_dynamic_register_nwl_reply process raise exception:%s" % e)
2241
+ elif message.topic == self.__ota_push_topic or message.topic == self.__ota_pull_reply_topic:
2242
+ try:
2243
+ payload = json.loads(self.__to_str(message.payload))
2244
+ data = payload.setdefault("data", "")
2245
+
2246
+ json_data = json.dumps(data)
2247
+ download_info = json.loads(str(json_data))
2248
+
2249
+ url = download_info.setdefault("url", "")
2250
+ version = download_info.setdefault("version", "")
2251
+ size = download_info.setdefault("size", "")
2252
+ sign_method = download_info.setdefault("signMethod", "")
2253
+ sign = download_info.setdefault("sign", "")
2254
+ extra = download_info.setdefault("extData", "")
2255
+ module = download_info.setdefault("module", "default")
2256
+
2257
+ if (
2258
+ not self.__is_valid_str(url)
2259
+ or not self.__is_valid_str(version)
2260
+ or not self.__is_valid_str(size)
2261
+ or not self.__is_valid_str(sign_method)
2262
+ or not self.__is_valid_str(sign)
2263
+ ):
2264
+ self.__link_log.error("invalid download params")
2265
+ return
2266
+
2267
+ ota_notice_type = 0
2268
+ if message.topic == self.__ota_push_topic:
2269
+ ota_notice_type = 1
2270
+ else:
2271
+ ota_notice_type = 2
2272
+
2273
+ if self.__on_ota_message_arrived is not None:
2274
+ triggered_flag = 1
2275
+ self.__on_ota_message_arrived(
2276
+ ota_notice_type, version, size, url, sign_method, sign, module, str(extra)
2277
+ )
2278
+ except Exception as e:
2279
+ self.__link_log.error("__on_ota_message_arrived process raise exception:%s" % e)
2280
+
2281
+ if triggered_flag == 1:
2282
+ return
2283
+
2284
+ if self.__on_topic_message is not None:
2285
+ try:
2286
+ self.__on_topic_message(message.topic, message.payload, message.qos, self.__user_data)
2287
+ except Exception as e:
2288
+ self.__link_log.error("on_topic_message process raise exception:%s" % e)
2289
+ else:
2290
+ self.__link_log.error("receive unscubscibe topic : %s" % message.topic)
2291
+
2292
+ def query_ota_firmware(self, module=None):
2293
+ request_id = self.__get_thing_request_id()
2294
+ if request_id is None:
2295
+ request_id = "1"
2296
+
2297
+ if self.__is_valid_str(module):
2298
+ payload = '{"id": %s, "params": {"module": "%s"}}' % (request_id, module)
2299
+ else:
2300
+ payload = '{"id": %s, "params": {}}' % request_id
2301
+
2302
+ rc = self.__mqtt_client.publish(self.__ota_pull_topic, payload, 0)
2303
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2304
+ return 0
2305
+ else:
2306
+ return LinkKit.ErrorCode.OTA_PUB_FAILED
2307
+
2308
+ def __cal_file_sign(self, filename, sign_method):
2309
+ if sign_method == "Md5":
2310
+ hash_tool = hashlib.md5()
2311
+ elif sign_method == "SHA256":
2312
+ hash_tool = hashlib.sha256()
2313
+ else:
2314
+ return LinkKit.ErrorCode.OTA_INVALID_SIGN_METHOD, -1
2315
+
2316
+ with open(filename, "rb") as f:
2317
+ while True:
2318
+ chunk = f.read(4096)
2319
+ if not chunk:
2320
+ break
2321
+ hash_tool.update(chunk)
2322
+ return LinkKit.ErrorCode.SUCCESS, hash_tool.hexdigest()
2323
+
2324
+ def ota_report_version(self, module, version):
2325
+ if not self.__is_valid_str(version):
2326
+ return LinkKit.ErrorCode.OTA_INVALID_PARAM
2327
+
2328
+ request_id = "1"
2329
+ if self.__is_valid_str(module):
2330
+ payload = '{"id": %s,"params": {"version":"%s","module": "%s"}}' % (request_id, version, module)
2331
+ else:
2332
+ payload = '{"id": %s, "params": {"version": "%s"}}' % (request_id, version)
2333
+
2334
+ rc = self.__mqtt_client.publish(self.__ota_report_version_topic, payload, 0)
2335
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2336
+ return LinkKit.ErrorCode.SUCCESS
2337
+ else:
2338
+ return LinkKit.ErrorCode.OTA_PUB_FAILED
2339
+
2340
+ def download_ota_firmware(self, url, local_path, sign_method, sign, download_step=10 * 1024):
2341
+ if (
2342
+ not self.__is_valid_str(url)
2343
+ or not self.__is_valid_str(local_path)
2344
+ or not self.__is_valid_str(sign_method)
2345
+ or not self.__is_valid_str(sign)
2346
+ ):
2347
+ return LinkKit.ErrorCode.OTA_INVALID_PARAM
2348
+
2349
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cadata=self.__aliyun_broker_ca_data)
2350
+
2351
+ try:
2352
+ conn = urllib.request.urlopen(url, context=context)
2353
+ except Exception as e:
2354
+ # Return code error (e.g. 404, 501, ...)
2355
+ self.__link_log.error("HTTPError: {} %s" % e)
2356
+ return LinkKit.ErrorCode.OTA_INVALID_URL
2357
+ else:
2358
+ # 200
2359
+ self.__link_log.info("https return 200")
2360
+
2361
+ try:
2362
+ file = open(local_path, "wb")
2363
+ except OSError as e:
2364
+ self.__link_log.error("open file error: {}" + e.filename)
2365
+ return LinkKit.ErrorCode.OTA_INVALID_PATH
2366
+ else:
2367
+ # Download ota file
2368
+ with file:
2369
+ while True:
2370
+ try:
2371
+ data = conn.read(download_step)
2372
+ except Exception as e:
2373
+ self.__link_log.error("download exception %s" % e)
2374
+ return LinkKit.ErrorCode.OTA_DOWNLOAD_FAIL
2375
+ else:
2376
+ if len(data) <= 0:
2377
+ break
2378
+ else:
2379
+ file.write(data)
2380
+
2381
+ # Compare checksum
2382
+ ret, firmware_sign = self.__cal_file_sign(local_path, sign_method)
2383
+ if ret == LinkKit.ErrorCode.SUCCESS and firmware_sign == sign:
2384
+ self.__link_log.info("sign match")
2385
+ return LinkKit.ErrorCode.SUCCESS
2386
+ else:
2387
+ self.__link_log.error("sign mismatch, expect:" + firmware_sign + ", actually:" + sign)
2388
+ return LinkKit.ErrorCode.OTA_DIGEST_MISMATCH
2389
+
2390
+ def __parse_raw_topic(self, topic):
2391
+ return re.search("/ext/rrpc/.*?(/.*)", topic).group(1)
2392
+
2393
+ def __tidy_topic(self, topic):
2394
+ if topic == None:
2395
+ return None
2396
+ topic = topic.strip()
2397
+ if len(topic) == 0:
2398
+ return None
2399
+ if topic[0] != "/":
2400
+ topic = "/" + topic
2401
+ return topic
2402
+
2403
+ def __push_rrpc_service(self, item) -> None:
2404
+ with self.__user_rrpc_request_ids_lock:
2405
+ if len(self.__user_rrpc_request_ids) > self.__user_rrpc_request_max_len:
2406
+ removed_item = self.__user_rrpc_request_ids.pop(0)
2407
+ del self.__user_rrpc_request_id_index_map[removed_item["id"]]
2408
+
2409
+ self.__user_rrpc_request_ids.append(item)
2410
+ self.__user_rrpc_request_id_index_map[item["id"]] = 0
2411
+
2412
+ def __pop_rrpc_service(self, id):
2413
+ with self.__user_rrpc_request_ids_lock:
2414
+ if id not in self.__user_rrpc_request_id_index_map:
2415
+ return None
2416
+ del self.__user_rrpc_request_id_index_map[id]
2417
+ for index in range(len(self.__user_rrpc_request_ids)):
2418
+ item = self.__user_rrpc_request_ids[index]
2419
+ if item["id"] == id:
2420
+ del self.__user_rrpc_request_ids[index]
2421
+ return item
2422
+ return None
2423
+
2424
+ def thing_answer_rrpc(self, id, response):
2425
+ item = self.__pop_rrpc_service("rrpc_" + id)
2426
+ if item == None:
2427
+ self.__link_log.error("answer_rrpc_topic, the id does not exist: %s" % id)
2428
+ return 1, None
2429
+ rc, mid = self.__mqtt_client.publish(item["topic"], response, 0)
2430
+ self.__link_log.debug("reply topic:%s" % item["topic"])
2431
+ return rc, mid
2432
+
2433
+ def __try_parse_rrpc_topic(self, message):
2434
+ self.__link_log.debug("receive a rrpc topic:%s" % message.topic)
2435
+ raw_topic = self.__parse_raw_topic(message.topic)
2436
+ triggered = 0
2437
+ # if it is a service, log it...
2438
+ if raw_topic.startswith("/sys") and raw_topic in self.__thing_topic_services:
2439
+ identifier = raw_topic.split("/", 6)[6]
2440
+ payload = self.__load_json(self.__to_str(message.payload))
2441
+ try:
2442
+ request_id = payload["id"]
2443
+ params = payload["params"]
2444
+ item_id = "alink_" + request_id
2445
+ item = {
2446
+ "id": item_id,
2447
+ "request_id": request_id,
2448
+ "payload": payload,
2449
+ "identifier": identifier,
2450
+ "topic": message.topic,
2451
+ }
2452
+ self.__push_rrpc_service(item)
2453
+ if self.__on_thing_call_service is not None:
2454
+ triggered = 1
2455
+ self.__on_thing_call_service(identifier, request_id, params, self.__user_data)
2456
+ except Exception as e:
2457
+ self.__link_log.error("on_thing_call_service raise exception: %s" % e)
2458
+ return triggered
2459
+
2460
+ # parse
2461
+ with self.__user_rrpc_topics_subscribe_request_lock:
2462
+ with self.__user_rrpc_topics_lock:
2463
+ if raw_topic not in self.__user_rrpc_topics:
2464
+ self.__link_log.error("%s is not in the rrpc-subscribed list" % raw_topic)
2465
+ return
2466
+ if not self.__on_topic_rrpc_message:
2467
+ return
2468
+ try:
2469
+ rrpc_id = message.topic.split("/", 4)[3]
2470
+ item_id = "rrpc_" + rrpc_id
2471
+ item = {"id": item_id, "payload": message.payload, "topic": message.topic}
2472
+ self.__push_rrpc_service(item)
2473
+ self.__on_topic_rrpc_message(rrpc_id, message.topic, message.payload, message.qos, self.__user_data)
2474
+ # self.__mqtt_client.publish(message.topic, response, 0)
2475
+ # self.__link_log.debug('reply topic:%s' % message.topic)
2476
+ except Exception as e:
2477
+ self.__link_log.error("on_topic_rrpc_message process raise exception:%r" % e)
2478
+
2479
+ def __try_parse_try_shadow(self, payload) -> None:
2480
+ try:
2481
+ self.__latest_shadow.set_latest_recevied_time(self.__timestamp())
2482
+ self.__latest_shadow.set_latest_recevied_payload(payload)
2483
+
2484
+ # parse the pay load
2485
+ msg = self.__load_json(payload)
2486
+ # set version
2487
+ if "version" in msg:
2488
+ self.__latest_shadow.set_version(msg["version"])
2489
+ elif "payload" in msg and "version" in msg["payload"]:
2490
+ self.__latest_shadow.set_version(msg["payload"]["version"])
2491
+
2492
+ # set timestamp
2493
+ if "timestamp" in msg:
2494
+ self.__latest_shadow.set_timestamp(msg["timestamp"])
2495
+ elif "payload" in msg and "timestamp" in msg["payload"]:
2496
+ self.__latest_shadow.set_timestamp(msg["payload"]["timestamp"])
2497
+
2498
+ # set state and metadata
2499
+ if "payload" in msg and msg["payload"]["status"] == "success":
2500
+ if "state" in msg["payload"]:
2501
+ self.__latest_shadow.set_state(msg["payload"]["state"])
2502
+ if "metadata" in msg["payload"]:
2503
+ self.__latest_shadow.set_metadata(msg["payload"]["metadata"])
2504
+ except Exception:
2505
+ pass
2506
+
2507
+ def thing_update_shadow(self, reported, version):
2508
+ request = {"state": {"reported": reported}, "method": "update", "version": version}
2509
+ return self.__thing_update_shadow(request)
2510
+
2511
+ def thing_get_shadow(self):
2512
+ request = {"method": "get"}
2513
+ return self.__thing_update_shadow(request)
2514
+
2515
+ def local_get_latest_shadow(self):
2516
+ return self.__latest_shadow
2517
+
2518
+ def __thing_update_shadow(self, request):
2519
+ if self.__linkkit_state is not LinkKit.LinkKitState.CONNECTED:
2520
+ self.__link_log.error("disconnected, update shadow fail")
2521
+ return LinkKit.ErrorCode.NETWORK_DISCONNECTED.value, None
2522
+ if not self.__thing_setup_state or not self.__thing_enable_state:
2523
+ return 1, None
2524
+ with self.__thing_shadow_mid_lock:
2525
+ rc, mid = self.__mqtt_client.publish(self.__thing_topic_shadow_update, json.dumps(request), 1)
2526
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2527
+ self.__thing_shadow_mid[mid] = self.__timestamp()
2528
+ return 0, mid
2529
+ else:
2530
+ return 1, None
2531
+
2532
+ def __on_internal_message(self, client, user_data, message) -> None:
2533
+ self.__link_log.info("__on_internal_message")
2534
+ self.__handler_task.post_message(self.__handler_task_cmd_on_message, (client, user_data, message))
2535
+ # self.__worker_thread.async_post_message(message)
2536
+
2537
+ def __handler_task_on_message_callback(self, value) -> None:
2538
+ client, user_data, message = value
2539
+ self.__on_internal_async_message(message)
2540
+
2541
+ def __on_internal_connect(self, client, user_data, session_flag, rc) -> None:
2542
+ self.__link_log.info("__on_internal_connect")
2543
+ if rc == 0:
2544
+ self.__reset_reconnect_wait()
2545
+ # self.__upload_device_interface_info()
2546
+ self.__handler_task.post_message(self.__handler_task_cmd_on_connect, (client, user_data, session_flag, rc))
2547
+
2548
+ def __handler_task_on_connect_callback(self, value) -> None:
2549
+ client, user_data, session_flag, rc = value
2550
+ self.__link_log.info("__on_internal_connect enter")
2551
+ self.__link_log.debug("session:%d, return code:%d" % (session_flag["session present"], rc))
2552
+ if rc == 0:
2553
+ self.__linkkit_state = LinkKit.LinkKitState.CONNECTED
2554
+ # self.__worker_thread.start()
2555
+ if self.__on_connect is not None:
2556
+ try:
2557
+ self.__on_connect(session_flag["session present"], rc, self.__user_data)
2558
+ except Exception as e:
2559
+ self.__link_log.error("on_connect process raise exception:%r" % e)
2560
+ if self.__thing_setup_state:
2561
+ self.__thing_enable_state = True
2562
+ if self.__on_thing_enable:
2563
+ self.__on_thing_enable(self.__user_data)
2564
+
2565
+ def __on_internal_disconnect(self, client, user_data, rc) -> None:
2566
+ self.__link_log.info("__on_internal_disconnect enter")
2567
+ if self.__linkkit_state == LinkKit.LinkKitState.DESTRUCTING:
2568
+ self.__linkkit_state = LinkKit.LinkKitState.DESTRUCTED
2569
+ elif (
2570
+ self.__linkkit_state == LinkKit.LinkKitState.DISCONNECTING
2571
+ or self.__linkkit_state == LinkKit.LinkKitState.CONNECTED
2572
+ ):
2573
+ self.__linkkit_state = LinkKit.LinkKitState.DISCONNECTED
2574
+ elif self.__linkkit_state == LinkKit.LinkKitState.DISCONNECTED:
2575
+ self.__link_log.error("__on_internal_disconnect enter from wrong state:%r" % self.__linkkit_state)
2576
+ return
2577
+ else:
2578
+ self.__link_log.error("__on_internal_disconnect enter from wrong state:%r" % self.__linkkit_state)
2579
+
2580
+ return
2581
+ self.__user_topics.clear()
2582
+ self.__user_topics_subscribe_request.clear()
2583
+ self.__user_topics_unsubscribe_request.clear()
2584
+
2585
+ self.__user_rrpc_topics.clear()
2586
+ self.__user_rrpc_topics_subscribe_request.clear()
2587
+ self.__user_rrpc_topics_unsubscribe_request.clear()
2588
+
2589
+ self.__thing_prop_post_mid.clear()
2590
+ self.__thing_event_post_mid.clear()
2591
+ self.__thing_answer_service_mid.clear()
2592
+ self.__thing_raw_down_reply_mid.clear()
2593
+ self.__thing_raw_up_mid.clear()
2594
+ self.__thing_shadow_mid.clear()
2595
+ self.__device_info_mid.clear()
2596
+ self.__thing_update_device_info_up_mid.clear()
2597
+ self.__thing_delete_device_info_up_mid.clear()
2598
+ self.__handler_task.post_message(self.__handler_task_cmd_on_disconnect, (client, user_data, rc))
2599
+ if self.__linkkit_state == LinkKit.LinkKitState.DESTRUCTED:
2600
+ self.__handler_task.stop()
2601
+
2602
+ def __handler_task_on_disconnect_callback(self, value) -> None:
2603
+ self.__link_log.info("__handler_task_on_disconnect_callback enter")
2604
+ client, user_data, rc = value
2605
+ if self.__thing_setup_state:
2606
+ if self.__thing_enable_state:
2607
+ self.__thing_enable_state = False
2608
+ if self.__on_thing_disable is not None:
2609
+ try:
2610
+ self.__on_thing_disable(self.__user_data)
2611
+ except Exception as e:
2612
+ self.__link_log.error("on_thing_disable process raise exception:%r" % e)
2613
+ if self.__on_disconnect is not None:
2614
+ try:
2615
+ self.__on_disconnect(rc, self.__user_data)
2616
+ except Exception as e:
2617
+ self.__link_log.error("on_disconnect process raise exception:%r" % e)
2618
+
2619
+ def __on_internal_publish(self, client, user_data, mid) -> None:
2620
+ self.__handler_task.post_message(self.__handler_task_cmd_on_publish, (client, user_data, mid))
2621
+
2622
+ def __gateway_add_subdev_topo(self, subdev_array):
2623
+ request_params = []
2624
+ for subdev in subdev_array:
2625
+ ret = self.__validate_subdev_param(subdev, 3)
2626
+ if ret != 0:
2627
+ return ret, None
2628
+
2629
+ pk = subdev[0]
2630
+ dn = subdev[1]
2631
+ ds = subdev[2]
2632
+ millis = str(self.__timestamp())
2633
+ client_id = pk + "." + dn
2634
+
2635
+ sign_content = "clientId%sdeviceName%sproductKey%stimestamp%s" % (client_id, dn, pk, millis)
2636
+ sign = hmac.new(ds.encode("utf-8"), sign_content.encode("utf-8"), hashlib.sha256).hexdigest()
2637
+ params = {
2638
+ "productKey": pk,
2639
+ "deviceName": dn,
2640
+ "clientId": client_id,
2641
+ "timestamp": millis,
2642
+ "signmethod": "hmacSha256",
2643
+ "sign": sign,
2644
+ }
2645
+ request_params.append(params)
2646
+ request_id = self.__get_thing_request_id()
2647
+ if request_id is None:
2648
+ return 1, None
2649
+ request = {
2650
+ "id": request_id,
2651
+ "version": "1.0",
2652
+ "params": request_params,
2653
+ }
2654
+ with self.__gateway_add_subdev_topo_mid_lock:
2655
+ rc, mid = self.__mqtt_client.publish(self.__gateway_topic_add_subdev_topo, json.dumps(request), 1)
2656
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2657
+ self.__gateway_add_subdev_topo_mid[mid] = self.__timestamp()
2658
+ return 0, request_id
2659
+ else:
2660
+ return 1, None
2661
+
2662
+ def gateway_add_subdev_topo(self, subdev_array):
2663
+ return self.__gateway_add_subdev_topo(subdev_array)
2664
+
2665
+ def __validate_subdev_param(self, subdev, expected_len):
2666
+ if subdev is None:
2667
+ return LinkKit.ErrorCode.NULL_SUBDEV_ERR.value
2668
+ if not isinstance(subdev, list):
2669
+ return LinkKit.ErrorCode.SUBDEV_NOT_ARRAY_ERR.value
2670
+ if len(subdev) < expected_len:
2671
+ self.__link_log.error("input subdev length mismatch")
2672
+ return LinkKit.ErrorCode.ARRAY_LENGTH_ERR.value
2673
+ else:
2674
+ return LinkKit.ErrorCode.SUCCESS.value
2675
+
2676
+ def __gateway_delete_subdev_topo(self, subdev_array):
2677
+ request_params = []
2678
+ for subdev in subdev_array:
2679
+ ret = self.__validate_subdev_param(subdev, 2)
2680
+ if ret != 0:
2681
+ return ret, None
2682
+
2683
+ pk = subdev[0]
2684
+ dn = subdev[1]
2685
+
2686
+ params = {
2687
+ "productKey": pk,
2688
+ "deviceName": dn,
2689
+ }
2690
+ request_params.append(params)
2691
+ request_id = self.__get_thing_request_id()
2692
+ if request_id is None:
2693
+ return 1, None
2694
+ request = {
2695
+ "id": request_id,
2696
+ "version": "1.0",
2697
+ "params": request_params,
2698
+ }
2699
+ with self.__gateway_delete_subdev_topo_mid_lock:
2700
+ rc, mid = self.__mqtt_client.publish(self.__gateway_topic_delete_subdev_topo, json.dumps(request), 1)
2701
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2702
+ self.__gateway_delete_subdev_topo_mid[mid] = self.__timestamp()
2703
+ return 0, request_id
2704
+ else:
2705
+ return 1, None
2706
+
2707
+ def gateway_delete_subdev_topo(self, subdev_array):
2708
+ return self.__gateway_delete_subdev_topo(subdev_array)
2709
+
2710
+ def __gateway_login_subdev(self, subdev_array):
2711
+ device_list = []
2712
+ for subdev in subdev_array:
2713
+ ret = self.__validate_subdev_param(subdev, 3)
2714
+ if ret != 0:
2715
+ return ret, None
2716
+
2717
+ pk = subdev[0]
2718
+ dn = subdev[1]
2719
+ ds = subdev[2]
2720
+ millis = str(self.__timestamp())
2721
+ client_id = pk + "." + dn + "|lan=Python,_v=2.2.1,_ss=1|"
2722
+
2723
+ sign_content = "clientId%sdeviceName%sproductKey%stimestamp%s" % (client_id, dn, pk, millis)
2724
+ sign = hmac.new(ds.encode("utf-8"), sign_content.encode("utf-8"), hashlib.sha256).hexdigest()
2725
+ dev_params = {
2726
+ "productKey": pk,
2727
+ "deviceName": dn,
2728
+ "clientId": client_id,
2729
+ "timestamp": millis,
2730
+ "cleanSession": "false",
2731
+ "sign": sign,
2732
+ }
2733
+ device_list.append(dev_params)
2734
+ request_params = {
2735
+ "signMethod": "hmacSha256",
2736
+ "deviceList": device_list,
2737
+ }
2738
+ request_id = self.__get_thing_request_id()
2739
+ if request_id is None:
2740
+ return 1, None
2741
+ request = {
2742
+ "id": request_id,
2743
+ "version": "1.0",
2744
+ "params": request_params,
2745
+ }
2746
+ rc, mid = self.__mqtt_client.publish(self.__gateway_topic_login_subdev, json.dumps(request), 0)
2747
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2748
+ return 0, request_id
2749
+ else:
2750
+ return 1, None
2751
+
2752
+ def gateway_login_subdev(self, subdev_array):
2753
+ return self.__gateway_login_subdev(subdev_array)
2754
+
2755
+ def gateway_logout_subdev(self, subdev_array):
2756
+ return self.__gateway_logout_subdev(subdev_array)
2757
+
2758
+ def __gateway_logout_subdev(self, subdev_array):
2759
+ request_params = []
2760
+ for subdev in subdev_array:
2761
+ ret = self.__validate_subdev_param(subdev, 2)
2762
+ if ret != 0:
2763
+ return ret, None
2764
+
2765
+ pk = subdev[0]
2766
+ dn = subdev[1]
2767
+
2768
+ params = {
2769
+ "productKey": pk,
2770
+ "deviceName": dn,
2771
+ }
2772
+ request_params.append(params)
2773
+ request_id = self.__get_thing_request_id()
2774
+ if request_id is None:
2775
+ return 1, None
2776
+ request = {
2777
+ "id": request_id,
2778
+ "version": "1.0",
2779
+ "params": request_params,
2780
+ }
2781
+ rc, mid = self.__mqtt_client.publish(self.__gateway_topic_logout_subdev, json.dumps(request), 0)
2782
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2783
+ return 0, request_id
2784
+ else:
2785
+ return 1, None
2786
+
2787
+ def __gateway_register_subdev(self, subdev_array):
2788
+ request_params = []
2789
+ for subdev in subdev_array:
2790
+ ret = self.__validate_subdev_param(subdev, 2)
2791
+ if ret != 0:
2792
+ return ret, None
2793
+
2794
+ pk = subdev[0]
2795
+ dn = subdev[1]
2796
+
2797
+ params = {
2798
+ "productKey": pk,
2799
+ "deviceName": dn,
2800
+ }
2801
+ request_params.append(params)
2802
+ request_id = self.__get_thing_request_id()
2803
+ if request_id is None:
2804
+ return 1, None
2805
+ request = {
2806
+ "id": request_id,
2807
+ "version": "1.0",
2808
+ "params": request_params,
2809
+ }
2810
+ rc, mid = self.__mqtt_client.publish(self.__gateway_topic_register_subdev, json.dumps(request), 0)
2811
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2812
+ return 0, request_id
2813
+ else:
2814
+ return 1, None
2815
+
2816
+ def gateway_register_subdev(self, subdev_array):
2817
+ return self.__gateway_register_subdev(subdev_array)
2818
+
2819
+ def gateway_product_register_subdev(self, subdev_array):
2820
+ return self.__gateway_product_register_subdev(subdev_array)
2821
+
2822
+ def __gateway_product_register_subdev(self, subdev_array):
2823
+ device_list = []
2824
+ for subdev in subdev_array:
2825
+ ret = self.__validate_subdev_param(subdev, 3)
2826
+ if ret != 0:
2827
+ return ret, None
2828
+
2829
+ pk = subdev[0]
2830
+ dn = subdev[1]
2831
+ ps = subdev[2]
2832
+ random_str = self.__generate_random_str(15)
2833
+
2834
+ sign_content = "deviceName%sproductKey%srandom%s" % (dn, pk, random_str)
2835
+ sign = hmac.new(ps.encode("utf-8"), sign_content.encode("utf-8"), hashlib.sha256).hexdigest()
2836
+ dev_params = {
2837
+ "productKey": pk,
2838
+ "deviceName": dn,
2839
+ "random": random_str,
2840
+ "signMethod": "hmacSha256",
2841
+ "sign": sign,
2842
+ }
2843
+ device_list.append(dev_params)
2844
+ request_params = {
2845
+ "proxieds": device_list,
2846
+ }
2847
+ request_id = self.__get_thing_request_id()
2848
+ if request_id is None:
2849
+ return 1, None
2850
+ request = {
2851
+ "id": request_id,
2852
+ "version": "1.0",
2853
+ "params": request_params,
2854
+ }
2855
+ rc, mid = self.__mqtt_client.publish(self.__gateway_topic_product_register_subdev, json.dumps(request), 0)
2856
+ if rc == mqtt.MQTT_ERR_SUCCESS:
2857
+ self.__link_log.debug("mid for product dynamic register:%d" % mid)
2858
+ return 0, request_id
2859
+ else:
2860
+ return 1, None
2861
+
2862
+ def __handler_task_on_publish_callback(self, value) -> None:
2863
+ client, user_data, mid = value
2864
+ self.__link_log.debug("__on_internal_publish message:%d" % mid)
2865
+ with self.__thing_event_post_mid_lock:
2866
+ if mid in self.__thing_event_post_mid:
2867
+ self.__thing_event_post_mid.pop(mid)
2868
+ self.__link_log.debug("__on_internal_publish event post mid removed")
2869
+ return
2870
+ with self.__thing_prop_post_mid_lock:
2871
+ if mid in self.__thing_prop_post_mid:
2872
+ self.__thing_prop_post_mid.pop(mid)
2873
+ self.__link_log.debug("__on_internal_publish prop post mid removed")
2874
+ return
2875
+ with self.__thing_prop_set_reply_mid_lock:
2876
+ if mid in self.__thing_prop_set_reply_mid:
2877
+ self.__thing_prop_set_reply_mid.pop(mid)
2878
+ self.__link_log.debug("__on_internal_publish prop set reply mid removed")
2879
+ return
2880
+ with self.__thing_answer_service_mid_lock:
2881
+ if mid in self.__thing_answer_service_mid:
2882
+ self.__thing_answer_service_mid.pop(mid)
2883
+ self.__link_log.debug("__thing_answer_service_mid mid removed")
2884
+ return
2885
+ with self.__thing_raw_up_mid_lock:
2886
+ if mid in self.__thing_raw_up_mid:
2887
+ self.__thing_raw_up_mid.pop(mid)
2888
+ self.__link_log.debug("__thing_raw_up_mid mid removed")
2889
+ return
2890
+ with self.__thing_raw_down_reply_mid_lock:
2891
+ if mid in self.__thing_raw_down_reply_mid:
2892
+ self.__thing_raw_down_reply_mid.pop(mid)
2893
+ self.__link_log.debug("__thing_raw_down_reply_mid mid removed")
2894
+ return
2895
+ with self.__device_info_mid_lock:
2896
+ if mid in self.__device_info_mid:
2897
+ self.__device_info_mid.pop(mid)
2898
+ self.__link_log.debug("__device_info_mid mid removed")
2899
+ return
2900
+ with self.__thing_shadow_mid_lock:
2901
+ if mid in self.__thing_shadow_mid:
2902
+ self.__thing_shadow_mid.pop(mid)
2903
+ self.__link_log.debug("__thing_shadow_mid mid removed")
2904
+ return
2905
+ with self.__thing_update_device_info_up_mid_lock:
2906
+ if mid in self.__thing_update_device_info_up_mid:
2907
+ self.__thing_update_device_info_up_mid.pop(mid)
2908
+ self.__link_log.debug("__thing_update_device_info_up_mid mid removed")
2909
+ return
2910
+ with self.__thing_delete_device_info_up_mid_lock:
2911
+ if mid in self.__thing_delete_device_info_up_mid:
2912
+ self.__thing_delete_device_info_up_mid.pop(mid)
2913
+ self.__link_log.debug("__thing_delete_device_info_up_mid mid removed")
2914
+ return
2915
+ with self.__gateway_add_subdev_topo_mid_lock:
2916
+ if mid in self.__gateway_add_subdev_topo_mid:
2917
+ self.__gateway_add_subdev_topo_mid.pop(mid)
2918
+ self.__link_log.debug("__gateway_add_subdev_topo_mid removed")
2919
+ return
2920
+ with self.__gateway_delete_subdev_topo_mid_lock:
2921
+ if mid in self.__gateway_delete_subdev_topo_mid:
2922
+ self.__gateway_delete_subdev_topo_mid.pop(mid)
2923
+ self.__link_log.debug("__gateway_delete_subdev_topo_mid removed")
2924
+ return
2925
+ if self.__on_publish_topic is not None:
2926
+ self.__on_publish_topic(mid, self.__user_data)
2927
+
2928
+ def __on_internal_subscribe(self, client, user_data, mid, granted_qos) -> None:
2929
+ self.__handler_task.post_message(self.__handler_task_cmd_on_subscribe, (client, user_data, mid, granted_qos))
2930
+
2931
+ def __handler_task_on_subscribe_callback(self, value) -> None:
2932
+ client, user_data, mid, granted_qos = value
2933
+ self.__link_log.debug(
2934
+ "__on_internal_subscribe mid:%d granted_qos:%s" % (mid, str(",".join("%s" % it for it in granted_qos)))
2935
+ )
2936
+ # try to read rrpc
2937
+ with self.__user_rrpc_topics_subscribe_request_lock:
2938
+ if mid in self.__user_rrpc_topics_subscribe_request:
2939
+ self.__user_rrpc_topics_subscribe_request.pop(mid)
2940
+ if self.__on_subscribe_rrpc_topic:
2941
+ try:
2942
+ self.__on_subscribe_rrpc_topic(mid, granted_qos, self.__user_data)
2943
+ except Exception as err:
2944
+ self.__link_log.error("Caught exception in on_subscribe_topic: %s", err)
2945
+ return
2946
+
2947
+ # try to read other topic
2948
+ topics_requests = None
2949
+ self.__user_topics_request_lock.acquire()
2950
+ if mid in self.__user_topics_subscribe_request:
2951
+ topics_requests = self.__user_topics_subscribe_request.pop(mid)
2952
+ self.__user_topics_request_lock.release()
2953
+ if topics_requests is not None:
2954
+ return_topics = []
2955
+ for index in range(len(topics_requests)):
2956
+ if granted_qos[index] < 0 or granted_qos[index] > 1:
2957
+ self.__link_log.error("topics:%s, granted wrong:%d" % (topics_requests[index], granted_qos[index]))
2958
+ else:
2959
+ self.__user_topics[topics_requests[index][0]] = granted_qos[index]
2960
+ return_topics.append((topics_requests[index], granted_qos[index]))
2961
+ if self.__on_subscribe_topic is not None:
2962
+ try:
2963
+ self.__on_subscribe_topic(mid, granted_qos, self.__user_data)
2964
+ except Exception as err:
2965
+ self.__link_log.error("Caught exception in on_subscribe_topic: %s", err)
2966
+
2967
+ def __on_internal_unsubscribe(self, client, user_data, mid) -> None:
2968
+ self.__handler_task.post_message(self.__handler_task_cmd_on_unsubscribe, (client, user_data, mid))
2969
+
2970
+ def __handler_task_on_unsubscribe_callback(self, value) -> None:
2971
+ client, user_data, mid = value
2972
+ self.__link_log.debug("__on_internal_unsubscribe mid:%d" % mid)
2973
+ unsubscribe_request = None
2974
+ # try to read rrpc
2975
+ with self.__user_rrpc_topics_unsubscribe_request_lock:
2976
+ if mid in self.__user_rrpc_topics_unsubscribe_request:
2977
+ self.__user_rrpc_topics_unsubscribe_request.pop(mid)
2978
+ if self.__on_unsubscribe_rrpc_topic:
2979
+ try:
2980
+ self.__on_unsubscribe_rrpc_topic(mid, self.__user_data)
2981
+ except Exception as err:
2982
+ self.__link_log.error("Caught exception in on_unsubscribe_rrpc_topic: %s", err)
2983
+ return
2984
+
2985
+ with self.__user_topics_unsubscribe_request_lock:
2986
+ if mid in self.__user_topics_unsubscribe_request:
2987
+ unsubscribe_request = self.__user_topics_unsubscribe_request.pop(mid)
2988
+ if unsubscribe_request is not None:
2989
+ for t in unsubscribe_request:
2990
+ self.__link_log.debug("__user_topics:%s" % str(self.__user_topics))
2991
+ try:
2992
+ self.__user_topics.pop(t)
2993
+ except Exception as e:
2994
+ self.__link_log.error("__on_internal_unsubscribe e:" + str(e))
2995
+ return
2996
+ if self.__on_unsubscribe_topic is not None:
2997
+ try:
2998
+ self.__on_unsubscribe_topic(mid, self.__user_data)
2999
+ except Exception as err:
3000
+ self.__link_log.error("Caught exception in on_unsubscribe_topic: %s", err)
3001
+
3002
+ def dump_user_topics(self):
3003
+ return self.__user_topics
3004
+
3005
+ def force_reconnect(self) -> None:
3006
+ self.__link_log.error("force reconnecting")
3007
+ self.__force_reconnect = True
3008
+
3009
+ @staticmethod
3010
+ def to_user_topic(topic):
3011
+ topic_section = topic.split("/", 3)
3012
+ user_topic = topic_section[3]
3013
+ return user_topic
3014
+
3015
+ def to_full_topic(self, topic):
3016
+ return self.__USER_TOPIC_PREFIX % (self.__product_key, self.__device_name, topic)
3017
+
3018
+ def __is_valid_str(self, user_str) -> bool:
3019
+ if user_str is None or user_str == "":
3020
+ return False
3021
+ return True
3022
+
3023
+ @staticmethod
3024
+ def __timestamp():
3025
+ return int(time.time() * 1000)