pymammotion 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymammotion might be problematic. Click here for more details.
- pymammotion/aliyun/cloud_gateway.py +9 -2
- pymammotion/data/model/location.py +5 -5
- pymammotion/mammotion/devices/mammotion.py +94 -67
- pymammotion/mqtt/mammotion_future.py +25 -0
- pymammotion/mqtt/mammotion_mqtt.py +22 -13
- {pymammotion-0.2.5.dist-info → pymammotion-0.2.7.dist-info}/METADATA +2 -1
- {pymammotion-0.2.5.dist-info → pymammotion-0.2.7.dist-info}/RECORD +9 -8
- {pymammotion-0.2.5.dist-info → pymammotion-0.2.7.dist-info}/LICENSE +0 -0
- {pymammotion-0.2.5.dist-info → pymammotion-0.2.7.dist-info}/WHEEL +0 -0
|
@@ -9,7 +9,7 @@ import random
|
|
|
9
9
|
import string
|
|
10
10
|
import time
|
|
11
11
|
import uuid
|
|
12
|
-
from logging import getLogger
|
|
12
|
+
from logging import getLogger, exception
|
|
13
13
|
|
|
14
14
|
from aiohttp import ClientSession
|
|
15
15
|
from alibabacloud_iot_api_gateway.client import Client
|
|
@@ -45,6 +45,10 @@ MOVE_HEADERS = (
|
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
class SetupException(Exception):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
48
52
|
class CloudIOTGateway:
|
|
49
53
|
"""Class for interacting with Aliyun Cloud IoT Gateway."""
|
|
50
54
|
|
|
@@ -524,7 +528,10 @@ class CloudIOTGateway:
|
|
|
524
528
|
str(response_body_dict.get("code")),
|
|
525
529
|
str(response_body_dict.get("msg")),
|
|
526
530
|
)
|
|
527
|
-
|
|
531
|
+
if response_body_dict.get("code") == 29003:
|
|
532
|
+
raise SetupException(response_body_dict.get("code"))
|
|
533
|
+
if response_body_dict.get("code") == 6205:
|
|
534
|
+
"""Device is offline."""
|
|
528
535
|
|
|
529
536
|
return message_id
|
|
530
537
|
|
|
@@ -7,8 +7,8 @@ from dataclasses import dataclass
|
|
|
7
7
|
class Point:
|
|
8
8
|
"""Returns a lat long."""
|
|
9
9
|
|
|
10
|
-
latitude: float
|
|
11
|
-
longitude: float
|
|
10
|
+
latitude: float = 0.0
|
|
11
|
+
longitude: float = 0.0
|
|
12
12
|
|
|
13
13
|
def __init__(self, latitude=0.0, longitude=0.0):
|
|
14
14
|
self.latitude = latitude
|
|
@@ -19,7 +19,7 @@ class Point:
|
|
|
19
19
|
class Dock(Point):
|
|
20
20
|
"""Stores robot dock position."""
|
|
21
21
|
|
|
22
|
-
rotation: int
|
|
22
|
+
rotation: int = 0
|
|
23
23
|
|
|
24
24
|
def __init__(self):
|
|
25
25
|
super().__init__()
|
|
@@ -33,8 +33,8 @@ class Location:
|
|
|
33
33
|
device: Point
|
|
34
34
|
RTK: Point
|
|
35
35
|
dock: Dock
|
|
36
|
-
position_type: int
|
|
37
|
-
orientation: int
|
|
36
|
+
position_type: int = 0
|
|
37
|
+
orientation: int = 0 # 360 degree rotation +-
|
|
38
38
|
|
|
39
39
|
def __init__(self):
|
|
40
40
|
self.device = Point()
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import queue
|
|
6
|
+
import threading
|
|
5
7
|
import asyncio
|
|
6
8
|
import base64
|
|
7
9
|
import codecs
|
|
8
10
|
import json
|
|
9
11
|
import logging
|
|
10
12
|
from abc import abstractmethod
|
|
13
|
+
from collections import deque
|
|
11
14
|
from enum import Enum
|
|
12
15
|
from functools import cache
|
|
13
16
|
from typing import Any, Callable, Optional, cast
|
|
@@ -38,8 +41,10 @@ from pymammotion.data.state_manager import StateManager
|
|
|
38
41
|
from pymammotion.http.http import connect_http
|
|
39
42
|
from pymammotion.mammotion.commands.mammotion_command import MammotionCommand
|
|
40
43
|
from pymammotion.mqtt import MammotionMQTT
|
|
44
|
+
from pymammotion.mqtt.mammotion_future import MammotionFuture
|
|
41
45
|
from pymammotion.proto.luba_msg import LubaMsg
|
|
42
46
|
from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck
|
|
47
|
+
from pymammotion.utility.rocker_util import RockerControlUtil
|
|
43
48
|
|
|
44
49
|
|
|
45
50
|
class CharacteristicMissingError(Exception):
|
|
@@ -87,10 +92,10 @@ def slashescape(err):
|
|
|
87
92
|
codecs.register_error("slashescape", slashescape)
|
|
88
93
|
|
|
89
94
|
|
|
90
|
-
def find_next_integer(lst: list[int],
|
|
95
|
+
def find_next_integer(lst: list[int], current_hash: float) -> int | None:
|
|
91
96
|
try:
|
|
92
97
|
# Find the index of the current integer
|
|
93
|
-
current_index = lst.index(
|
|
98
|
+
current_index = lst.index(current_hash)
|
|
94
99
|
|
|
95
100
|
# Check if there is a next integer in the list
|
|
96
101
|
if current_index + 1 < len(lst):
|
|
@@ -114,10 +119,12 @@ async def _handle_retry(fut: asyncio.Future[None], func, command: bytes) -> None
|
|
|
114
119
|
await func(command)
|
|
115
120
|
|
|
116
121
|
|
|
117
|
-
async def _handle_retry_cloud(fut: asyncio.Future[None], func,
|
|
122
|
+
async def _handle_retry_cloud(self, fut: asyncio.Future[None], func, iot_id: str, command: bytes) -> None:
|
|
118
123
|
"""Handle a retry."""
|
|
124
|
+
|
|
119
125
|
if not fut.done():
|
|
120
|
-
|
|
126
|
+
self._operation_lock.release()
|
|
127
|
+
await self.loop.run_in_executor(None, func, iot_id, command)
|
|
121
128
|
|
|
122
129
|
|
|
123
130
|
class ConnectionPreference(Enum):
|
|
@@ -223,10 +230,8 @@ class Mammotion(object):
|
|
|
223
230
|
|
|
224
231
|
async def initiate_cloud_connection(self, cloud_client: CloudIOTGateway) -> None:
|
|
225
232
|
if self._mammotion_mqtt is not None:
|
|
226
|
-
if
|
|
227
|
-
|
|
228
|
-
await loop.run_in_executor(None, self._mammotion_mqtt.connect_async)
|
|
229
|
-
return
|
|
233
|
+
if self._mammotion_mqtt.is_connected:
|
|
234
|
+
return
|
|
230
235
|
|
|
231
236
|
|
|
232
237
|
self._mammotion_mqtt = MammotionMQTT(region_id=cloud_client._region.data.regionId,
|
|
@@ -244,6 +249,11 @@ class Mammotion(object):
|
|
|
244
249
|
if device.deviceName.startswith(("Luba-", "Yuka-")):
|
|
245
250
|
self.devices.add_device(MammotionMixedDeviceManager(name=device.deviceName, cloud_device=device, mqtt=self._mammotion_mqtt))
|
|
246
251
|
|
|
252
|
+
def set_disconnect_strategy(self, disconnect: bool):
|
|
253
|
+
for device_name, device in self.devices.devices:
|
|
254
|
+
if device.ble() is not None:
|
|
255
|
+
ble_device: MammotionBaseBLEDevice = device.ble()
|
|
256
|
+
ble_device.set_disconnect_strategy(disconnect)
|
|
247
257
|
|
|
248
258
|
@staticmethod
|
|
249
259
|
async def login(account: str, password: str) -> CloudIOTGateway:
|
|
@@ -328,7 +338,6 @@ class MammotionBaseDevice:
|
|
|
328
338
|
self._raw_data = LubaMsg().to_dict(casing=betterproto.Casing.SNAKE)
|
|
329
339
|
self._mower = device
|
|
330
340
|
self._state_manager = StateManager(self._mower)
|
|
331
|
-
|
|
332
341
|
self._state_manager.gethash_ack_callback.add_subscribers(self.datahash_response)
|
|
333
342
|
self._state_manager.get_commondata_ack_callback.add_subscribers(self.commdata_response)
|
|
334
343
|
self._notify_future: asyncio.Future[bytes] | None = None
|
|
@@ -504,6 +513,28 @@ class MammotionBaseDevice:
|
|
|
504
513
|
# jobs list
|
|
505
514
|
# hash_list_result = await self._send_command_with_args("get_all_boundary_hash_list", sub_cmd=3)
|
|
506
515
|
|
|
516
|
+
async def move_forward(self):
|
|
517
|
+
linear_speed = 1.0
|
|
518
|
+
angular_speed = 0.0
|
|
519
|
+
transfrom3 = RockerControlUtil.getInstance().transfrom3(90, 1000)
|
|
520
|
+
transform4 = RockerControlUtil.getInstance().transfrom3(0, 0)
|
|
521
|
+
|
|
522
|
+
if transfrom3 is not None and len(transfrom3) > 0:
|
|
523
|
+
linear_speed = transfrom3[0] * 10
|
|
524
|
+
angular_speed = int(transform4[1] * 4.5)
|
|
525
|
+
await self._send_command_with_args("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
|
|
526
|
+
|
|
527
|
+
async def move_stop(self):
|
|
528
|
+
linear_speed = 0.0
|
|
529
|
+
angular_speed = 0.0
|
|
530
|
+
transfrom3 = RockerControlUtil.getInstance().transfrom3(0, 0)
|
|
531
|
+
transform4 = RockerControlUtil.getInstance().transfrom3(0, 0)
|
|
532
|
+
|
|
533
|
+
if transfrom3 is not None and len(transfrom3) > 0:
|
|
534
|
+
linear_speed = transfrom3[0] * 10
|
|
535
|
+
angular_speed = int(transform4[1] * 4.5)
|
|
536
|
+
await self._send_command_with_args("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
|
|
537
|
+
|
|
507
538
|
async def command(self, key: str, **kwargs):
|
|
508
539
|
"""Send a command to the device."""
|
|
509
540
|
return await self._send_command_with_args(key, **kwargs)
|
|
@@ -515,6 +546,7 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
|
|
|
515
546
|
def __init__(self, mowing_state: MowingDevice, device: BLEDevice, interface: int = 0, **kwargs: Any) -> None:
|
|
516
547
|
"""Initialize MammotionBaseBLEDevice."""
|
|
517
548
|
super().__init__(mowing_state)
|
|
549
|
+
self._disconnect_strategy = True
|
|
518
550
|
self._ble_sync_task = None
|
|
519
551
|
self._prev_notification = None
|
|
520
552
|
self._interface = f"hci{interface}"
|
|
@@ -621,7 +653,10 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
|
|
|
621
653
|
@property
|
|
622
654
|
def rssi(self) -> int:
|
|
623
655
|
"""Return RSSI of device."""
|
|
624
|
-
|
|
656
|
+
try:
|
|
657
|
+
return self._mower.device.sys.toapp_report_data.connect.ble_rssi
|
|
658
|
+
finally:
|
|
659
|
+
return 0
|
|
625
660
|
|
|
626
661
|
async def _ensure_connected(self):
|
|
627
662
|
"""Ensure connection to device is established."""
|
|
@@ -651,12 +686,11 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
|
|
|
651
686
|
return
|
|
652
687
|
_LOGGER.debug("%s: Connecting; RSSI: %s", self.name, self.rssi)
|
|
653
688
|
client: BleakClientWithServiceCache = await establish_connection(
|
|
654
|
-
|
|
689
|
+
BleakClientWithServiceCache,
|
|
655
690
|
self._device,
|
|
656
691
|
self.name,
|
|
657
692
|
self._disconnected,
|
|
658
693
|
max_attempts=10,
|
|
659
|
-
use_services_cache=True,
|
|
660
694
|
ble_device_callback=lambda: self._device,
|
|
661
695
|
)
|
|
662
696
|
_LOGGER.debug("%s: Connected; RSSI: %s", self.name, self.rssi)
|
|
@@ -838,6 +872,8 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
|
|
|
838
872
|
|
|
839
873
|
async def _execute_timed_disconnect(self) -> None:
|
|
840
874
|
"""Execute timed disconnection."""
|
|
875
|
+
if not self._disconnect_strategy:
|
|
876
|
+
return
|
|
841
877
|
_LOGGER.debug(
|
|
842
878
|
"%s: Executing timed disconnect after timeout of %s",
|
|
843
879
|
self.name,
|
|
@@ -887,6 +923,9 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
|
|
|
887
923
|
if self._client is not None:
|
|
888
924
|
return await self._client.disconnect()
|
|
889
925
|
|
|
926
|
+
def set_disconnect_strategy(self, disconnect):
|
|
927
|
+
self._disconnect_strategy = disconnect
|
|
928
|
+
|
|
890
929
|
|
|
891
930
|
class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
892
931
|
"""Base class for Mammotion Cloud devices."""
|
|
@@ -909,7 +948,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
909
948
|
self._commands: MammotionCommand = MammotionCommand(cloud_device.deviceName)
|
|
910
949
|
self.currentID = ""
|
|
911
950
|
self.on_ready_callback: Optional[Callable[[], None]] = None
|
|
912
|
-
self.
|
|
951
|
+
self._waiting_queue = deque()
|
|
913
952
|
|
|
914
953
|
self._mqtt_client.on_connected = self.on_connected
|
|
915
954
|
self._mqtt_client.on_disconnected = self.on_disconnected
|
|
@@ -922,19 +961,19 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
922
961
|
# temporary for testing only
|
|
923
962
|
# self._start_sync_task = self.loop.call_later(30, lambda: asyncio.ensure_future(self.start_sync(0)))
|
|
924
963
|
|
|
925
|
-
def on_ready(self):
|
|
964
|
+
async def on_ready(self):
|
|
926
965
|
"""Callback for when MQTT is subscribed to events."""
|
|
927
966
|
if self.on_ready_callback:
|
|
928
967
|
self.on_ready_callback()
|
|
929
968
|
|
|
930
|
-
|
|
931
|
-
|
|
969
|
+
await self._ble_sync()
|
|
970
|
+
await self.run_periodic_sync_task()
|
|
932
971
|
|
|
933
|
-
def on_connected(self):
|
|
972
|
+
async def on_connected(self):
|
|
934
973
|
"""Callback for when MQTT connects."""
|
|
935
974
|
|
|
936
975
|
|
|
937
|
-
def on_disconnected(self):
|
|
976
|
+
async def on_disconnected(self):
|
|
938
977
|
"""Callback for when MQTT disconnects."""
|
|
939
978
|
|
|
940
979
|
async def _ble_sync(self):
|
|
@@ -957,27 +996,24 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
957
996
|
160, lambda: asyncio.ensure_future(self.run_periodic_sync_task())
|
|
958
997
|
)
|
|
959
998
|
|
|
960
|
-
def _on_mqtt_message(self, topic: str, payload: str, iot_id: str) -> None:
|
|
999
|
+
async def _on_mqtt_message(self, topic: str, payload: str, iot_id: str) -> None:
|
|
961
1000
|
"""Handle incoming MQTT messages."""
|
|
962
|
-
_LOGGER.debug("MQTT message received on topic %s: %s", topic, payload)
|
|
1001
|
+
_LOGGER.debug("MQTT message received on topic %s: %s", topic, payload, iot_id)
|
|
963
1002
|
|
|
964
1003
|
json_str = json.dumps(payload)
|
|
965
1004
|
payload = json.loads(json_str)
|
|
966
1005
|
|
|
967
|
-
self._handle_mqtt_message(topic, payload)
|
|
1006
|
+
await self._handle_mqtt_message(topic, payload)
|
|
968
1007
|
|
|
969
1008
|
async def _send_command(self, key: str, retry: int | None = None) -> bytes | None:
|
|
970
1009
|
"""Send command to device via MQTT and read response."""
|
|
971
|
-
if self._operation_lock.locked():
|
|
972
|
-
_LOGGER.debug("%s: Operation already in progress, waiting for it to complete;", self.device.nickName)
|
|
973
1010
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
raise
|
|
1011
|
+
try:
|
|
1012
|
+
command_bytes = getattr(self._commands, key)()
|
|
1013
|
+
return await self._send_command_locked(key, command_bytes)
|
|
1014
|
+
except Exception as ex:
|
|
1015
|
+
_LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
|
|
1016
|
+
raise
|
|
981
1017
|
|
|
982
1018
|
async def _send_command_locked(self, key: str, command: bytes) -> bytes:
|
|
983
1019
|
"""Send command to device and read response."""
|
|
@@ -996,33 +1032,16 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
996
1032
|
async def _execute_command_locked(self, key: str, command: bytes) -> bytes:
|
|
997
1033
|
"""Execute command and read response."""
|
|
998
1034
|
assert self._mqtt_client is not None
|
|
999
|
-
self._notify_future = self.loop.create_future()
|
|
1000
1035
|
self._key = key
|
|
1001
1036
|
_LOGGER.debug("%s: Sending command: %s", self.device.nickName, key)
|
|
1002
|
-
loop
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
retry_handle = self.loop.call_at(
|
|
1006
|
-
self.loop.time() + 20,
|
|
1007
|
-
lambda: asyncio.ensure_future(
|
|
1008
|
-
_handle_retry_cloud(
|
|
1009
|
-
self._notify_future, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command
|
|
1010
|
-
)
|
|
1011
|
-
),
|
|
1012
|
-
)
|
|
1037
|
+
await self.loop.run_in_executor(None, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command)
|
|
1038
|
+
future = MammotionFuture()
|
|
1039
|
+
self._waiting_queue.append(future)
|
|
1013
1040
|
timeout = 20
|
|
1014
|
-
timeout_handle = self.loop.call_at(self.loop.time() + timeout, _handle_timeout, self._notify_future)
|
|
1015
|
-
timeout_expired = False
|
|
1016
1041
|
try:
|
|
1017
|
-
notify_msg = await
|
|
1042
|
+
notify_msg = await future.async_get(timeout)
|
|
1018
1043
|
except asyncio.TimeoutError:
|
|
1019
|
-
timeout_expired = True
|
|
1020
1044
|
raise
|
|
1021
|
-
finally:
|
|
1022
|
-
if not timeout_expired:
|
|
1023
|
-
timeout_handle.cancel()
|
|
1024
|
-
retry_handle.cancel()
|
|
1025
|
-
self._notify_future = None
|
|
1026
1045
|
|
|
1027
1046
|
_LOGGER.debug("%s: Message received", self.device.nickName)
|
|
1028
1047
|
|
|
@@ -1030,15 +1049,12 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
1030
1049
|
|
|
1031
1050
|
async def _send_command_with_args(self, key: str, **kwargs: any) -> bytes | None:
|
|
1032
1051
|
"""Send command with arguments to device via MQTT and read response."""
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
except Exception as ex:
|
|
1040
|
-
_LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
|
|
1041
|
-
raise
|
|
1052
|
+
try:
|
|
1053
|
+
command_bytes = getattr(self._commands, key)(**kwargs)
|
|
1054
|
+
return await self._send_command_locked(key, command_bytes)
|
|
1055
|
+
except Exception as ex:
|
|
1056
|
+
_LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
|
|
1057
|
+
raise
|
|
1042
1058
|
|
|
1043
1059
|
def _extract_message_id(self, payload: dict) -> str:
|
|
1044
1060
|
"""Extract the message ID from the payload."""
|
|
@@ -1053,7 +1069,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
1053
1069
|
_LOGGER.error("Error extracting encoded message. Payload: %s", payload)
|
|
1054
1070
|
return ""
|
|
1055
1071
|
|
|
1056
|
-
def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
|
|
1072
|
+
async def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
|
|
1057
1073
|
"""Parse the MQTT response."""
|
|
1058
1074
|
if topic.endswith("/app/down/thing/events"):
|
|
1059
1075
|
_LOGGER.debug("Thing event received")
|
|
@@ -1064,14 +1080,25 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
1064
1080
|
binary_data = base64.b64decode(params.value.get("content", ""))
|
|
1065
1081
|
self._update_raw_data(cast(bytes, binary_data))
|
|
1066
1082
|
new_msg = LubaMsg().parse(cast(bytes, binary_data))
|
|
1067
|
-
if self._notify_future and not self._notify_future.done():
|
|
1068
|
-
self._notify_future.set_result(new_msg)
|
|
1069
|
-
asyncio.run(self._state_manager.notification(new_msg))
|
|
1070
1083
|
|
|
1071
|
-
|
|
1084
|
+
if self._commands.get_device_product_key() == "" and self._commands.get_device_name() == event.params.deviceName:
|
|
1085
|
+
self._commands.set_device_product_key(event.params.productKey)
|
|
1086
|
+
|
|
1087
|
+
if betterproto.serialized_on_wire(new_msg.net):
|
|
1088
|
+
if new_msg.net.todev_ble_sync != 0 or has_field(new_msg.net.toapp_wifi_iot_status):
|
|
1089
|
+
return
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
if len(self._waiting_queue) > 0:
|
|
1093
|
+
fut: MammotionFuture = self._waiting_queue.popleft()
|
|
1094
|
+
fut.resolve(cast(bytes, binary_data))
|
|
1095
|
+
await self._state_manager.notification(new_msg)
|
|
1096
|
+
|
|
1097
|
+
async def _handle_mqtt_message(self, topic: str, payload: dict) -> None:
|
|
1072
1098
|
"""Async handler for incoming MQTT messages."""
|
|
1073
|
-
self._parse_mqtt_response(topic=topic, payload=payload)
|
|
1099
|
+
await self._parse_mqtt_response(topic=topic, payload=payload)
|
|
1074
1100
|
|
|
1075
|
-
|
|
1101
|
+
def _disconnect(self):
|
|
1076
1102
|
"""Disconnect the MQTT client."""
|
|
1077
1103
|
self._mqtt_client.disconnect()
|
|
1104
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from asyncio import Future
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import async_timeout
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MammotionFuture:
|
|
8
|
+
"""Create futures for each MQTT Message."""
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.fut: Future = Future()
|
|
11
|
+
self.loop = self.fut.get_loop()
|
|
12
|
+
|
|
13
|
+
def _resolve(self, item: bytes) -> None:
|
|
14
|
+
if not self.fut.cancelled():
|
|
15
|
+
self.fut.set_result(item)
|
|
16
|
+
|
|
17
|
+
def resolve(self, item: bytes) -> None:
|
|
18
|
+
self.loop.call_soon_threadsafe(self._resolve, item)
|
|
19
|
+
|
|
20
|
+
async def async_get(self, timeout: float | int) -> bytes:
|
|
21
|
+
try:
|
|
22
|
+
async with async_timeout.timeout(timeout):
|
|
23
|
+
return await self.fut
|
|
24
|
+
finally:
|
|
25
|
+
self.fut.cancel()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""MammotionMQTT."""
|
|
2
|
-
|
|
2
|
+
import asyncio
|
|
3
3
|
import hashlib
|
|
4
4
|
import hmac
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
7
|
from logging import getLogger
|
|
8
|
-
from typing import Callable, Optional, cast
|
|
8
|
+
from typing import Callable, Optional, cast, Awaitable
|
|
9
9
|
|
|
10
10
|
from linkkit.linkkit import LinkKit
|
|
11
11
|
from paho.mqtt.client import MQTTMessage
|
|
@@ -36,11 +36,11 @@ class MammotionMQTT:
|
|
|
36
36
|
self._cloud_client = None
|
|
37
37
|
self.is_connected = False
|
|
38
38
|
self.is_ready = False
|
|
39
|
-
self.on_connected: Optional[Callable[[],
|
|
40
|
-
self.on_ready: Optional[Callable[[],
|
|
41
|
-
self.on_error: Optional[Callable[[str],
|
|
42
|
-
self.on_disconnected: Optional[Callable[[],
|
|
43
|
-
self.on_message: Optional[Callable[[str, str, str],
|
|
39
|
+
self.on_connected: Optional[Callable[[],Awaitable[None]]] = None
|
|
40
|
+
self.on_ready: Optional[Callable[[],Awaitable[None]]] = None
|
|
41
|
+
self.on_error: Optional[Callable[[str],Awaitable[None]]] = None
|
|
42
|
+
self.on_disconnected: Optional[Callable[[],Awaitable[None]]] = None
|
|
43
|
+
self.on_message: Optional[Callable[[str, str, str],Awaitable[None]]] = None
|
|
44
44
|
|
|
45
45
|
self._product_key = product_key
|
|
46
46
|
self._device_name = device_name
|
|
@@ -57,6 +57,7 @@ class MammotionMQTT:
|
|
|
57
57
|
).hexdigest()
|
|
58
58
|
|
|
59
59
|
self._client_id = client_id
|
|
60
|
+
self.loop = asyncio.get_event_loop()
|
|
60
61
|
|
|
61
62
|
self._linkkit_client = LinkKit(
|
|
62
63
|
region_id,
|
|
@@ -79,9 +80,8 @@ class MammotionMQTT:
|
|
|
79
80
|
def connect_async(self):
|
|
80
81
|
"""Connect async to MQTT Server."""
|
|
81
82
|
logger.info("Connecting...")
|
|
82
|
-
self._linkkit_client.thing_setup()
|
|
83
83
|
self._linkkit_client.connect_async()
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
|
|
86
86
|
def disconnect(self):
|
|
87
87
|
"""Disconnect from MQTT Server."""
|
|
@@ -91,6 +91,7 @@ class MammotionMQTT:
|
|
|
91
91
|
def _thing_on_thing_enable(self, user_data):
|
|
92
92
|
"""Is called when Thing is enabled."""
|
|
93
93
|
logger.debug("on_thing_enable")
|
|
94
|
+
self.is_connected = True
|
|
94
95
|
# logger.debug('subscribe_topic, topic:%s' % echo_topic)
|
|
95
96
|
# self._linkkit_client.subscribe_topic(echo_topic, 0)
|
|
96
97
|
self._linkkit_client.subscribe_topic(
|
|
@@ -120,7 +121,8 @@ class MammotionMQTT:
|
|
|
120
121
|
|
|
121
122
|
if self.on_ready:
|
|
122
123
|
self.is_ready = True
|
|
123
|
-
self.on_ready()
|
|
124
|
+
future = asyncio.run_coroutine_threadsafe(self.on_ready(), self.loop)
|
|
125
|
+
asyncio.wrap_future(future, loop=self.loop)
|
|
124
126
|
# self._linkkit_client.query_ota_firmware()
|
|
125
127
|
# command = MammotionCommand(device_name="Luba")
|
|
126
128
|
# self._cloud_client.send_cloud_command(command.get_report_cfg())
|
|
@@ -136,13 +138,17 @@ class MammotionMQTT:
|
|
|
136
138
|
payload = json.loads(payload)
|
|
137
139
|
iot_id = payload.get("params", {}).get("iotId", "")
|
|
138
140
|
if iot_id != "" and self.on_message:
|
|
139
|
-
self.on_message(topic, payload, iot_id)
|
|
141
|
+
future = asyncio.run_coroutine_threadsafe(self.on_message(topic, payload, iot_id), self.loop)
|
|
142
|
+
return asyncio.wrap_future(future, loop=self.loop)
|
|
143
|
+
|
|
140
144
|
|
|
141
145
|
def _thing_on_connect(self, session_flag, rc, user_data):
|
|
142
146
|
"""Is called on thing connect."""
|
|
143
147
|
self.is_connected = True
|
|
144
148
|
if self.on_connected is not None:
|
|
145
|
-
self.on_connected()
|
|
149
|
+
future = asyncio.run_coroutine_threadsafe(self.on_connected(), self.loop)
|
|
150
|
+
asyncio.wrap_future(future, loop=self.loop)
|
|
151
|
+
|
|
146
152
|
logger.debug("on_connect, session_flag:%d, rc:%d", session_flag, rc)
|
|
147
153
|
|
|
148
154
|
# self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/#")
|
|
@@ -153,7 +159,9 @@ class MammotionMQTT:
|
|
|
153
159
|
self.is_connected = False
|
|
154
160
|
self.is_ready = False
|
|
155
161
|
if self.on_disconnected:
|
|
156
|
-
self.on_disconnected()
|
|
162
|
+
future = asyncio.run_coroutine_threadsafe(self.on_disconnected(), self.loop)
|
|
163
|
+
asyncio.wrap_future(future, loop=self.loop)
|
|
164
|
+
|
|
157
165
|
|
|
158
166
|
def _on_message(self, _client, _userdata, message: MQTTMessage):
|
|
159
167
|
"""Is called when message is received."""
|
|
@@ -184,3 +192,4 @@ class MammotionMQTT:
|
|
|
184
192
|
def get_cloud_client(self) -> Optional[CloudIOTGateway]:
|
|
185
193
|
"""Return internal cloud client."""
|
|
186
194
|
return self._cloud_client
|
|
195
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pymammotion
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary:
|
|
5
5
|
License: GNU-3.0
|
|
6
6
|
Author: Michael Arthur
|
|
@@ -17,6 +17,7 @@ Requires-Dist: alibabacloud-iot-api-gateway (>=0.0.4,<0.0.5)
|
|
|
17
17
|
Requires-Dist: alicloud-gateway-iot (>=1.0.0,<2.0.0)
|
|
18
18
|
Requires-Dist: aliyun-iot-linkkit (>=1.2.12,<2.0.0)
|
|
19
19
|
Requires-Dist: aliyun-python-sdk-iot (>=8.57.0,<9.0.0)
|
|
20
|
+
Requires-Dist: async-timeout (>=4.0.3,<5.0.0)
|
|
20
21
|
Requires-Dist: betterproto (>=1.2.5,<2.0.0)
|
|
21
22
|
Requires-Dist: bleak (>=0.21.0)
|
|
22
23
|
Requires-Dist: bleak-retry-connector (>=3.5.0,<4.0.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pymammotion/__init__.py,sha256=kmnjdt3AEMejIz5JK7h1tTJj5ZriAgKwZBa3ScA4-Ao,1516
|
|
2
2
|
pymammotion/aliyun/__init__.py,sha256=T1lkX7TRYiL4nqYanG4l4MImV-SlavSbuooC-W-uUGw,29
|
|
3
|
-
pymammotion/aliyun/cloud_gateway.py,sha256=
|
|
3
|
+
pymammotion/aliyun/cloud_gateway.py,sha256=ZjGSRdfxujOEHgL2gr8rZsGsYlJtGdS2YWGEWLXaEhg,18681
|
|
4
4
|
pymammotion/aliyun/cloud_service.py,sha256=YWcKuKK6iRWy5mTnBYgHxcCusiRGGzQt3spSf7dGDss,2183
|
|
5
5
|
pymammotion/aliyun/dataclass/aep_response.py,sha256=8f6GIP58ve8gd6AL3HBoXxsy0n2q4ygWvjELGnoOnVc,452
|
|
6
6
|
pymammotion/aliyun/dataclass/connect_response.py,sha256=Yz-fEbDzgGPTo5Of2oAjmFkSv08T7ze80pQU4k-gKIU,824
|
|
@@ -28,7 +28,7 @@ pymammotion/data/model/excute_boarder_params.py,sha256=kadSth4y-VXlXIZ6R-Ng-kDvB
|
|
|
28
28
|
pymammotion/data/model/execute_boarder.py,sha256=oDb2h5tFtOQIa8OCNYaDugqCgCZBLjQRzQTNVcJVAGQ,1072
|
|
29
29
|
pymammotion/data/model/generate_route_information.py,sha256=5w1MM1-gXGXb_AoEap_I5xTxAFbNSzXuqQFEkw4NmDs,4301
|
|
30
30
|
pymammotion/data/model/hash_list.py,sha256=z4y0mzbW8LQ5nsaHbMlt1y6uS4suB4D_VljAyIEjFR0,1171
|
|
31
|
-
pymammotion/data/model/location.py,sha256=
|
|
31
|
+
pymammotion/data/model/location.py,sha256=mXiJUnD2AIpcN7HgV8OLVL3QFLNAiVWqaVDFGmDjFXU,807
|
|
32
32
|
pymammotion/data/model/mowing_modes.py,sha256=2GAF-xaHpv6CSGobYoNtfSi2if8_jW0nonCqN50SNx0,665
|
|
33
33
|
pymammotion/data/model/plan.py,sha256=7JvqAo0a9Yg1Vtifd4J3Dx3StEppxrMOfmq2-877kYg,2891
|
|
34
34
|
pymammotion/data/model/rapid_state.py,sha256=_e9M-65AbkvIqXyMYzLKBxbNvpso42qD8R-JSt66THY,986
|
|
@@ -58,9 +58,10 @@ pymammotion/mammotion/commands/messages/video.py,sha256=_8lJsU4sLm2CGnc7RDkueA0A
|
|
|
58
58
|
pymammotion/mammotion/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
59
|
pymammotion/mammotion/control/joystick.py,sha256=EWV20MMzQuhbLlNlXbsyZKSEpeM7x1CQL7saU4Pn0-g,6165
|
|
60
60
|
pymammotion/mammotion/devices/__init__.py,sha256=T72jt0ejtMjo1rPmn_FeMF3pmp0LLeRRpc9WcDKEYYY,126
|
|
61
|
-
pymammotion/mammotion/devices/mammotion.py,sha256=
|
|
61
|
+
pymammotion/mammotion/devices/mammotion.py,sha256=zoFNPcZGgucFUCpPKE4m_5U7TW6kMGsgyYmXHibNT40,44308
|
|
62
62
|
pymammotion/mqtt/__init__.py,sha256=Ocs5e-HLJvTuDpVXyECEsWIvwsUaxzj7lZ9mSYutNDY,105
|
|
63
|
-
pymammotion/mqtt/
|
|
63
|
+
pymammotion/mqtt/mammotion_future.py,sha256=WKnHqeHiS2Ut-SaDBNOxqh1jDLeTiyLTsJ7PNUexrjk,687
|
|
64
|
+
pymammotion/mqtt/mammotion_mqtt.py,sha256=eCAdOx7ikDiaetXyy9wusisds3klCx6n3DNH-8ZQaHA,8002
|
|
64
65
|
pymammotion/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
66
|
pymammotion/proto/basestation.proto,sha256=_x5gAz3FkZXS1jtq4GgZgaDCuRU-UV-7HTFdsfQ3zbo,1034
|
|
66
67
|
pymammotion/proto/basestation.py,sha256=js64_N2xQYRxWPRdVNEapO0qe7vBlfYnjW5sE8hi7hw,2026
|
|
@@ -110,7 +111,7 @@ pymammotion/utility/device_type.py,sha256=KYawu2glZMVlPmxRbA4kVFujXz3miHp3rJiOWR
|
|
|
110
111
|
pymammotion/utility/map.py,sha256=aoi-Luzuph02hKynTofMoq3mnPstanx75MDAVv49CuY,2211
|
|
111
112
|
pymammotion/utility/periodic.py,sha256=9wJMfwXPlx6Mbp3Fws7LLTI34ZDKphH1bva_Ggyk32g,3281
|
|
112
113
|
pymammotion/utility/rocker_util.py,sha256=syPL0QN4zMzHiTIkUKS7RXBBptjdbkfNlPddwUD5V3A,7171
|
|
113
|
-
pymammotion-0.2.
|
|
114
|
-
pymammotion-0.2.
|
|
115
|
-
pymammotion-0.2.
|
|
116
|
-
pymammotion-0.2.
|
|
114
|
+
pymammotion-0.2.7.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
115
|
+
pymammotion-0.2.7.dist-info/METADATA,sha256=WPR_bP14ZVgPXEUbRRSNOLP_cI75tPF0rVZBle-6pwU,3968
|
|
116
|
+
pymammotion-0.2.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
117
|
+
pymammotion-0.2.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|