pymammotion 0.4.0a3__py3-none-any.whl → 0.4.0a4__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.
@@ -695,7 +695,6 @@ class CloudIOTGateway:
695
695
  raise SetupException(response_body_dict.get("code"))
696
696
  if response_body_dict.get("code") == 6205:
697
697
  raise DeviceOfflineException(response_body_dict.get("code"))
698
- """Device is offline."""
699
698
 
700
699
  return message_id
701
700
 
@@ -169,7 +169,7 @@ class Mammotion:
169
169
  self, ble_device: BLEDevice, preference: ConnectionPreference = ConnectionPreference.BLUETOOTH
170
170
  ) -> None:
171
171
  if ble_device:
172
- self.devices.add_device(
172
+ self.device_manager.add_device(
173
173
  MammotionMixedDeviceManager(name=ble_device.name, ble_device=ble_device, preference=preference)
174
174
  )
175
175
 
@@ -205,9 +205,9 @@ class Mammotion:
205
205
 
206
206
  def add_cloud_devices(self, mqtt_client: MammotionCloud) -> None:
207
207
  for device in mqtt_client.cloud_client.devices_by_account_response.data.data:
208
- mower_device = self.devices.get_device(device.deviceName)
208
+ mower_device = self.device_manager.get_device(device.deviceName)
209
209
  if device.deviceName.startswith(("Luba-", "Yuka-")) and mower_device is None:
210
- self.devices.add_device(
210
+ self.device_manager.add_device(
211
211
  MammotionMixedDeviceManager(
212
212
  name=device.deviceName,
213
213
  cloud_device=device,
@@ -222,7 +222,7 @@ class Mammotion:
222
222
  mower_device.replace_mqtt(mqtt_client)
223
223
 
224
224
  def set_disconnect_strategy(self, disconnect: bool) -> None:
225
- for device_name, device in self.devices.devices.items():
225
+ for device_name, device in self.device_manager.devices.items():
226
226
  if device.ble() is not None:
227
227
  ble_device: MammotionBaseBLEDevice = device.ble()
228
228
  ble_device.set_disconnect_strategy(disconnect)
@@ -249,10 +249,10 @@ class Mammotion:
249
249
  return cloud_client
250
250
 
251
251
  async def remove_device(self, name: str) -> None:
252
- await self.devices.remove_device(name)
252
+ await self.device_manager.remove_device(name)
253
253
 
254
254
  def get_device_by_name(self, name: str) -> MammotionMixedDeviceManager:
255
- return self.devices.get_device(name)
255
+ return self.device_manager.get_device(name)
256
256
 
257
257
  async def send_command(self, name: str, key: str):
258
258
  """Send a command to the device."""
@@ -0,0 +1,5 @@
1
+ """Package for linkkit."""
2
+
3
+ from .linkkit import LinkKit
4
+
5
+ __all__ = ["LinkKit"]
@@ -0,0 +1,546 @@
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
+ import hyper
20
+ import time
21
+ import hmac
22
+ import ssl
23
+ import logging
24
+ import os
25
+ import threading
26
+ import crcmod
27
+ import concurrent.futures
28
+ import hashlib
29
+
30
+
31
+ def _assert_value(condition, error_msg):
32
+ if not condition:
33
+ raise ValueError(error_msg)
34
+
35
+
36
+ _H2_OPT_HEART_BEAT_TIME_DEFAULT = 25
37
+ _H2_OPT_PORT_DEFAULT = 443
38
+ _H2_MAX_FILE_SIZE = 1024 * 1024 * 1024
39
+
40
+
41
+ def h2_set_option(opt, value):
42
+ if ('heart_beat_interval' == opt):
43
+ global _H2_OPT_HEART_BEAT_TIME_DEFAULT
44
+ _H2_OPT_HEART_BEAT_TIME_DEFAULT = value
45
+ elif ('port' == opt):
46
+ global _H2_OPT_PORT_DEFAULT
47
+ _H2_OPT_PORT_DEFAULT = value
48
+ elif ('max_file_size' == opt):
49
+ global _H2_MAX_FILE_SIZE
50
+ _H2_MAX_FILE_SIZE = value
51
+
52
+
53
+ class StreamHandler():
54
+ def __init__(self):
55
+ pass
56
+
57
+ def __enter__(self):
58
+ pass
59
+
60
+ def __exit__(self, type, value, trace):
61
+ pass
62
+
63
+ def get_content_length(self):
64
+ return None
65
+
66
+ def next(self):
67
+ return None
68
+
69
+ def has_next(self):
70
+ return False
71
+
72
+
73
+ class FileStreamHandler(StreamHandler):
74
+ def __init__(self, filename, block_size=512 * 1024, opt_crc64=False):
75
+ self.__filename = filename
76
+ self.__block_size = block_size;
77
+ self.__size = os.stat(filename).st_size
78
+ self.__opt_crc64 = opt_crc64
79
+ self.__last_crc = 0
80
+ self.__read_size = 0
81
+
82
+ def get_content_length(self):
83
+ return self.__size
84
+
85
+ def __enter__(self):
86
+ logging.debug('open the file, filename:%s' % self.__filename)
87
+ self.__f = open(self.__filename, 'rb')
88
+ self.__read_size = 0
89
+
90
+ def __exit__(self, type, value, trace):
91
+ if self.__f:
92
+ self.__f.close()
93
+ self.__f = None
94
+
95
+ def next(self):
96
+ if not self.__f or self.__read_size >= self.__size:
97
+ return None
98
+ data = self.__f.read(self.__block_size)
99
+ if data:
100
+ self.__read_size += len(data)
101
+ if self.__opt_crc64:
102
+ do_crc64 = crcmod.mkCrcFun(0x142F0E1EBA9EA3693, initCrc=self.__last_crc, xorOut=0xffffffffffffffff,
103
+ rev=True)
104
+ self.__last_crc = do_crc64(data)
105
+ return data
106
+
107
+ def has_next(self):
108
+ return self.__f.tell() < self.__size
109
+
110
+ def get_crc64(self):
111
+ return self.__last_crc
112
+
113
+ def get_read_size(self):
114
+ return self.__read_size
115
+
116
+
117
+ class H2Exception(Exception):
118
+ def __init__(self, code, msg):
119
+ Exception.__init__(self, msg)
120
+ self.__code = code
121
+ self.__msg = msg
122
+
123
+ def get_code(self):
124
+ return self.__code
125
+
126
+ def get_msg(self):
127
+ return self.__msg
128
+
129
+ def __name__(self):
130
+ return 'H2Exception'
131
+
132
+
133
+ class UploadFileInfo:
134
+ def __init__(self, local_filename, remote_filename=None,
135
+ overwrite=True):
136
+ self.local_filename = local_filename
137
+ self.opt_overwrite = overwrite
138
+ if not remote_filename:
139
+ self.remote_filename = os.path.basename(local_filename)
140
+ else:
141
+ self.remote_filename = remote_filename
142
+
143
+ def __name__(self):
144
+ return 'UploadFileInfo'
145
+
146
+
147
+ class UploadFileResult:
148
+ def __init__(self, code=None, exception=None, upload_size=None, total_size=None, file_store_id=None):
149
+ self.upload_size = upload_size
150
+ self.total_size = total_size
151
+ self.file_store_id = file_store_id
152
+ self.code = code
153
+ self.exception = exception
154
+
155
+ def __name__(self):
156
+ return 'UploadFileResult'
157
+
158
+
159
+ class H2FileUploadSink:
160
+ def on_file_upload_start(self, id, upload_file_info, user_data):
161
+ pass
162
+
163
+ def on_file_upload_end(self, id, upload_file_info, upload_file_result, user_data):
164
+ pass
165
+
166
+ def on_file_upload_progress(self, id, upload_file_info, upload_file_result, user_data):
167
+ pass
168
+
169
+
170
+ class H2FileTask:
171
+ def __init__(self, id, file_info, future_result):
172
+ self.__file_info = file_info
173
+ self.__future_result = future_result
174
+ self.__id = id
175
+
176
+ def get_file_info(self):
177
+ return self.__file_info
178
+
179
+ def get_future_result(self):
180
+ return self.__future_result
181
+
182
+ def result(self, timeout=None):
183
+ return self.__future_result.result(timeout)
184
+
185
+ def cancel(self):
186
+ self.__future_result.call()
187
+
188
+ def get_id(self):
189
+ return self.__id
190
+
191
+ def __name__(self):
192
+ return 'H2FileTask'
193
+
194
+
195
+ class H2Stream:
196
+ def __init__(self, client, id):
197
+ self.__client = client
198
+ self.__conn = None
199
+ # self.__length = None
200
+ self.__total_sent_size = 0
201
+ self.__path = None
202
+ self.__id = id
203
+ self.__stream_id = None
204
+ self.__x_request_id = None
205
+ self.__x_data_stream_id = None
206
+
207
+ def __name__(self):
208
+ return 'H2Stream'
209
+
210
+ def get_id(self):
211
+ return self.__id
212
+
213
+ def open(self, path, header):
214
+ _assert_value(path, "path is required")
215
+
216
+ with self.__client._get_auth_lock():
217
+ url = '/stream/open' + path
218
+ self.__conn = self.__client.get_connect()
219
+
220
+ # self.__length = length
221
+ self.__total_sent_size = 0
222
+ self.__path = path
223
+
224
+ logging.debug("request url: %s" % url)
225
+
226
+ # open the stream
227
+ conn_header = self.__client.get_default_header()
228
+ if header:
229
+ conn_header.update(header)
230
+
231
+ req_id = self.__conn.request('GET', url, None, conn_header)
232
+ response = self.__conn.get_response(req_id)
233
+
234
+ self.__check_response(response)
235
+ self.__x_request_id = response.headers['x-request-id'][0]
236
+ self.__x_data_stream_id = response.headers['x-data-stream-id'][0]
237
+
238
+ logging.debug("x_request_id: %s" % self.__x_request_id)
239
+ logging.debug("x_data_stream_id: %s" % self.__x_data_stream_id)
240
+
241
+ return response
242
+
243
+ def close(self, header):
244
+ logging.debug('close the stream')
245
+ final_header = {'x-request-id': self.__x_request_id,
246
+ 'x-data-stream-id': self.__x_data_stream_id}
247
+ final_header.update(header)
248
+ req_id = self.__conn.request('GET', '/stream/close/' + self.__path, None, final_header)
249
+ response = self.__conn.get_response(req_id)
250
+ self.__check_response(response)
251
+ return response
252
+
253
+ def send(self, headers, data_handler):
254
+ # prepare for sending
255
+ with self.__client._get_auth_lock():
256
+ url = '/stream/send' + self.__path
257
+ logging.debug("request url: %s" % url)
258
+ self.__stream_id = self.__conn.putrequest('GET', url)
259
+ self.__conn.putheader('x-request-id', self.__x_request_id, stream_id=self.__stream_id)
260
+ self.__conn.putheader('x-data-stream-id', self.__x_data_stream_id, stream_id=self.__stream_id)
261
+ content_length = data_handler.get_content_length()
262
+ if content_length:
263
+ self.__conn.putheader('content-length', '%s' % (content_length), self.__stream_id)
264
+ for k, v in headers.items():
265
+ self.__conn.putheader(k, v, self.__stream_id)
266
+ self.__conn.endheaders(stream_id=self.__stream_id)
267
+
268
+ with data_handler:
269
+ final = False
270
+ while not final:
271
+ data = data_handler.next()
272
+ if data == None or len(data) == 0:
273
+ break
274
+ final = not data_handler.has_next()
275
+ self.__conn.send(data, final, stream_id=self.__stream_id)
276
+
277
+ response = self.__conn.get_response(self.__stream_id)
278
+ # response.read()
279
+ self.__check_response(response)
280
+ return response
281
+
282
+ def __check_response(self, response, msg=None):
283
+ if (response.status != 200):
284
+ raise H2Exception(response.status,
285
+ msg if msg else 'fail to request http/2, code:%d' % (response.status))
286
+
287
+ def __str__(self):
288
+ return 'H2Stream(id=%s,stream_x_id=%s,x_request_id=%s,x_data_stream_id:%s' % (self.__id,
289
+ self.__stream_id,
290
+ self.__x_request_id,
291
+ self.__x_data_stream_id)
292
+
293
+
294
+ class H2Client:
295
+ def __init__(self, region, product_key, device_name, device_secret, client_id=None, opt_max_thread_num=4,
296
+ endpoint=None):
297
+ _assert_value(region, "region is not empty")
298
+ _assert_value(product_key, "product_key is not empty")
299
+ _assert_value(device_name, "device_name is not empty")
300
+
301
+ self.__product_key = product_key
302
+ self.__device_name = device_name
303
+ self.__client_id = client_id
304
+ self.__device_secret = device_secret
305
+ self.__region = region
306
+ self.__endpoint = endpoint
307
+ self.__opt_free_idle_connect = False
308
+ self.__connected = False
309
+ self.__port = _H2_OPT_PORT_DEFAULT
310
+ self.__conn = None
311
+ self.__opt_heart_beat_time = _H2_OPT_HEART_BEAT_TIME_DEFAULT
312
+ self.__conn_lock = threading.RLock()
313
+ self.__lock = threading.RLock()
314
+ self.__stream_list = []
315
+ self.__stream_list_lock = threading.RLock()
316
+ self.__thread_executor = concurrent.futures.ThreadPoolExecutor(max_workers=opt_max_thread_num)
317
+ self.__auth_lock = threading.RLock()
318
+ self.__id = 0
319
+ self.__heart_beat_lock = threading.RLock()
320
+ self.__timer = None
321
+
322
+ def get_endpoint(self):
323
+ return self.__endpoint
324
+
325
+ def get_actual_endpoint(self):
326
+ return self.__generate_endpoint()
327
+
328
+ def __generate_endpoint(self):
329
+ if self.__endpoint:
330
+ return self.__endpoint
331
+ else:
332
+ return self.__product_key + ".iot-as-http2.%s.aliyuncs.com" % (self.__region)
333
+
334
+ def open(self):
335
+ with self.__conn_lock:
336
+ if self.__conn:
337
+ logging.info('the client is opened')
338
+ return -1
339
+ return self.__connect()
340
+
341
+ def close(self):
342
+ with self.__conn_lock:
343
+ return self.__close_connect()
344
+ self.__close_all_streams()
345
+
346
+ def upload_file_async(self, local_filename, remote_filename=None, over_write=True,
347
+ upload_file_sink=None, upload_sink_user_data=None):
348
+ _assert_value(local_filename, 'local_filename is required')
349
+ self.__check_file(local_filename)
350
+
351
+ file_info = UploadFileInfo(local_filename, remote_filename, over_write)
352
+
353
+ future_result = self.__thread_executor.submit(self.__post_file_task, file_info, upload_file_sink,
354
+ upload_sink_user_data)
355
+ return H2FileTask(id, file_info, future_result)
356
+
357
+ def upload_file_sync(self, local_filename, remote_filename=None, over_write=True, timeout=None,
358
+ upload_file_sink=None, upload_sink_user_data=None):
359
+ self.__check_file(local_filename)
360
+ f = self.upload_file_async(local_filename, remote_filename, over_write,
361
+ upload_file_sink, upload_sink_user_data)
362
+ return f.result(timeout)
363
+
364
+ def __create_stream_id(self):
365
+ with self.__lock:
366
+ self.__id += 1
367
+ return self.__id
368
+
369
+ def new_stream(self):
370
+ return H2Stream(self, self.__create_stream_id())
371
+
372
+ def _get_auth_lock(self):
373
+ return self.__auth_lock
374
+
375
+ def __crc_equal(self, value1, value2):
376
+ if value1 == value2:
377
+ return True
378
+ return self.__to_unsign(value1) == self.__to_unsign(value2)
379
+
380
+ def __to_unsign(self, value):
381
+ return value if value > 0 else (0xffffffffffffffff + 1 + value)
382
+
383
+ def __check_file(self, path):
384
+ stat_info = os.stat(path)
385
+ if (stat_info.st_size >= _H2_MAX_FILE_SIZE):
386
+ raise ValueError('maximum file size exceeded')
387
+
388
+ def __post_file_task(self, file_info, sink=None, user_data=None):
389
+ local_filename = file_info.local_filename
390
+ remote_filename = file_info.remote_filename
391
+ over_write = file_info.opt_overwrite
392
+ fs = None
393
+ file_store_id = None
394
+ exception = None
395
+ code = 0
396
+ x_file_upload_id = None
397
+
398
+ stream = self.new_stream()
399
+ self.__on_new_stream(stream)
400
+ try:
401
+ logging.info('start to post file, local_filename:%s, remote:%s, over_write:%d' % (
402
+ local_filename, remote_filename, over_write))
403
+
404
+ # callback
405
+ if sink:
406
+ sink.on_file_upload_start(stream.get_id(), file_info, user_data)
407
+
408
+ # open stream
409
+ header = {'x-file-name': remote_filename, 'x-file-overwrite': '1' if over_write else '0'}
410
+ response = stream.open('/c/iot/sys/thing/file/upload', header)
411
+ x_file_upload_id = response.headers['x-file-upload-id'][0]
412
+
413
+ # send stream
414
+ header = {'x-file-upload-id': x_file_upload_id}
415
+ fs = FileStreamHandler(local_filename, opt_crc64=True)
416
+ stream.send(header, fs)
417
+
418
+ # close stream
419
+ response = stream.close(header)
420
+ remote_crc64 = int(response.headers['x-file-crc64ecma'][0])
421
+ logging.info('crc64, local:%ld, remote:%ld' % (fs.get_crc64(), remote_crc64))
422
+ if not self.__crc_equal(fs.get_crc64(), remote_crc64):
423
+ raise Exception('fail to check crc64, local:%ld, remote:%ld' % (fs.get_crc64(), remote_crc64))
424
+ file_store_id = response.headers['x-file-store-id'][0]
425
+ logging.info('finish uploading file, local_filename:%s, remote:%s, over_write:%d, file_store_id:%s'
426
+ % (local_filename, remote_filename, over_write, file_store_id))
427
+
428
+ return UploadFileResult(code, exception, fs.get_read_size(), fs.get_content_length,
429
+ file_store_id)
430
+ except H2Exception as e:
431
+ logging.error(
432
+ "fail to upload the file, local_filename:%s, remote:%s, over_write:%d, x_file_upload_id:%s, stream:%s, code:%s, error:%s"
433
+ % (local_filename, remote_filename, over_write, x_file_upload_id, stream, e.get_code(), e))
434
+ return UploadFileResult(e.get_code(), exception,
435
+ (fs.get_read_size() if fs else -1),
436
+ (fs.get_content_length() if fs else -1), file_store_id)
437
+ except Exception as e:
438
+ logging.error(
439
+ "fail to upload the file, local_filename:%s, remote:%s, over_write:%d, x_file_upload_id:%s, stream:%s, error:%s"
440
+ % (local_filename, remote_filename, over_write, x_file_upload_id, stream, e))
441
+ return UploadFileResult(-1, exception,
442
+ (fs.get_read_size() if fs else -1),
443
+ (fs.get_content_length() if fs else -1), file_store_id)
444
+ # raise e
445
+ finally:
446
+ self.__on_free_stream(stream)
447
+ if sink:
448
+ result = UploadFileResult(code, exception,
449
+ (fs.get_read_size() if fs else -1),
450
+ (fs.get_content_length() if fs else -1), file_store_id)
451
+ sink.on_file_upload_end(stream.get_id(), file_info, result, user_data)
452
+
453
+ def __connect(self):
454
+ with self.__conn_lock:
455
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
456
+ h2_endpoint = self.__generate_endpoint()
457
+ logging.debug('http/2 endpoint:%s' % (h2_endpoint))
458
+ self.__conn = hyper.HTTP20Connection(h2_endpoint, port=self.__port,
459
+ force_proto=hyper.tls.NPN_PROTOCOL,
460
+ ssl_context=ctx)
461
+ return 0
462
+
463
+ def get_connect(self):
464
+ with self.__conn_lock:
465
+ if self.__conn:
466
+ return self.__conn
467
+ return self.__connect()
468
+
469
+ def __fill_auth_header(self, header):
470
+ client_id = self.__client_id or self.__device_name
471
+ timestamp = str(int(time.time() * 1000))
472
+ sign_content = "clientId" + client_id + "deviceName" + self.__device_name + "productKey" + self.__product_key \
473
+ + "timestamp" + timestamp
474
+ sign = hmac.new(self.__device_secret.encode("utf-8"), sign_content.encode("utf-8"), hashlib.sha256).hexdigest()
475
+ header['x-auth-param-timestamp'] = timestamp
476
+ header['x-auth-param-signmethod'] = 'hmacsha256'
477
+ header['x-auth-param-sign'] = sign
478
+ header['x-auth-param-product-key'] = self.__product_key
479
+ header['x-auth-param-device-name'] = self.__device_name
480
+ header['x-auth-param-client-id'] = client_id
481
+ header['x-auth-name'] = 'devicename'
482
+ return header
483
+
484
+ def __fill_sdk_header(self, header):
485
+ header['x-sdk-version'] = '1.2.0'
486
+ header['x-sdk-version-name'] = '1.2.0'
487
+ header['x-sdk-platform'] = 'python'
488
+ return header
489
+
490
+ def get_default_header(self):
491
+ header = {}
492
+ self.__fill_auth_header(header)
493
+ self.__fill_sdk_header(header)
494
+ return header
495
+
496
+ def __close_connect(self):
497
+ with self.__conn_lock:
498
+ if self.__conn:
499
+ self.__conn.close(0)
500
+ return 0
501
+
502
+ def __close_all_streams(self):
503
+ with self.__stream_list_lock:
504
+ self.__stream_list.clear()
505
+ self.__stream_list = None
506
+ self.__stop_heart_beat()
507
+
508
+ def __on_new_stream(self, stream):
509
+ with self.__stream_list_lock:
510
+ self.__stream_list.append(stream)
511
+
512
+ if len(self.__stream_list) == 1:
513
+ self.__start_heart_beat()
514
+
515
+ def __on_free_stream(self, stream):
516
+ with self.__stream_list_lock:
517
+ self.__stream_list.remove(stream)
518
+
519
+ if len(self.__stream_list) == 0:
520
+ self.__stop_heart_beat()
521
+
522
+ def __start_heart_beat(self):
523
+ logging.debug('start heart_beat')
524
+ self.__schedule_heart_beat()
525
+
526
+ def __handle_heart_beat(self):
527
+ logging.debug('heart...')
528
+ self.__conn.ping(b'PINGPONG')
529
+ self.__schedule_heart_beat()
530
+
531
+ def __stop_heart_beat(self):
532
+ logging.debug('stop heart')
533
+ self.__cancel_heart_beat()
534
+
535
+ def __schedule_heart_beat(self):
536
+ with self.__heart_beat_lock:
537
+ if self.__opt_heart_beat_time and self.__opt_heart_beat_time > 0:
538
+ self.__timer = threading.Timer(self.__opt_heart_beat_time, self.__handle_heart_beat)
539
+ self.__timer.start()
540
+
541
+ def __cancel_heart_beat(self):
542
+ with self.__heart_beat_lock:
543
+ if self.__timer:
544
+ self.__timer.cancel()
545
+ self.__timer = None
546
+