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.

@@ -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
- return ""
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 # 360 degree rotation +-
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], current_int: int) -> int | None:
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(current_int)
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, iotId: str, command: bytes) -> None:
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
- func(iotId, command)
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 not self._mammotion_mqtt.is_connected:
227
- loop = asyncio.get_running_loop()
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
- return 0
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
- BleakClient,
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._operation_lock = asyncio.Lock()
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
- asyncio.run(self._ble_sync())
931
- asyncio.run(self.run_periodic_sync_task())
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
- async with self._operation_lock:
975
- try:
976
- command_bytes = getattr(self._commands, key)()
977
- return await self._send_command_locked(key, command_bytes)
978
- except Exception as ex:
979
- _LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
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 = asyncio.get_running_loop()
1003
- await loop.run_in_executor(None, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command)
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 self._notify_future
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
- if self._operation_lock.locked():
1034
- _LOGGER.debug("%s: Operation already in progress, waiting for it to complete;", self.device.nickName)
1035
- async with self._operation_lock:
1036
- try:
1037
- command_bytes = getattr(self._commands, key)(**kwargs)
1038
- return await self._send_command_locked(key, command_bytes)
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
- def _handle_mqtt_message(self, topic: str, payload: dict) -> None:
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
- async def _disconnect(self):
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[[], None]] = None
40
- self.on_ready: Optional[Callable[[], None]] = None
41
- self.on_error: Optional[Callable[[str], None]] = None
42
- self.on_disconnected: Optional[Callable[[], None]] = None
43
- self.on_message: Optional[Callable[[str, str, str], None]] = None
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
- self._linkkit_client.start_worker_loop()
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.5
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=1-VgNDJCG1XJl-P3OnQ1FaKiF8OhAT0N-HMs0ZZV3WE,18427
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=Fw6r4PFCjBmbUPsI6b0Swke6RgUlzoW7NXjR8EGjgzk,784
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=M8DYRH0sSjGIM9hYmsSUCf4BHaJgNSwD3zdGeLiT118,43221
61
+ pymammotion/mammotion/devices/mammotion.py,sha256=zoFNPcZGgucFUCpPKE4m_5U7TW6kMGsgyYmXHibNT40,44308
62
62
  pymammotion/mqtt/__init__.py,sha256=Ocs5e-HLJvTuDpVXyECEsWIvwsUaxzj7lZ9mSYutNDY,105
63
- pymammotion/mqtt/mammotion_mqtt.py,sha256=DzNF29VCbIHa4SpUqH0mPgh_PiIDcPEX-Tx-l-YMh1k,7489
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.5.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
114
- pymammotion-0.2.5.dist-info/METADATA,sha256=6ykL_7ot9ackweOAJcco8C_LxfNsMWJNCvbH15RKuE0,3922
115
- pymammotion-0.2.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
116
- pymammotion-0.2.5.dist-info/RECORD,,
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,,