pymammotion 0.2.6__tar.gz → 0.2.7__tar.gz
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-0.2.6 → pymammotion-0.2.7}/PKG-INFO +2 -1
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/cloud_gateway.py +2 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/location.py +5 -5
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/devices/mammotion.py +52 -61
- pymammotion-0.2.7/pymammotion/mqtt/mammotion_future.py +25 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mqtt/mammotion_mqtt.py +19 -11
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pyproject.toml +4 -3
- {pymammotion-0.2.6 → pymammotion-0.2.7}/LICENSE +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/README.md +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/cloud_service.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/aep_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/connect_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/dev_by_account_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/login_by_oauth_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/regions_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/session_by_authcode_response.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/tmp_constant.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/ble.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/ble_message.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/const.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/data/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/data/convert.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/data/framectrldata.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/bluetooth/data/notifydata.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/const.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/account.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/device.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/device_config.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/enums.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/excute_boarder_params.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/execute_boarder.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/generate_route_information.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/hash_list.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/mowing_modes.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/plan.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/rapid_state.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/region_data.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/report_info.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/mqtt/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/mqtt/event.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/mqtt/properties.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/mqtt/status.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/state_manager.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/event/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/event/event.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/http/_init_.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/http/http.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/abstract_message.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/mammotion_command.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/driver.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/media.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/navigation.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/network.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/ota.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/system.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/video.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/control/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/control/joystick.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/devices/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mqtt/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/basestation.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/basestation.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/basestation_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/basestation_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/common.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/common.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/common_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/common_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/dev_net.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/dev_net.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/dev_net_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/dev_net_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_msg.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_msg.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_msg_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_msg_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_mul.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_mul.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_mul_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/luba_mul_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_driver.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_driver.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_driver_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_driver_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_nav.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_nav.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_nav_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_nav_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_ota.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_ota.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_ota_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_ota_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_pept.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_pept.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_pept_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_pept_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_sys.proto +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_sys.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_sys_pb2.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/proto/mctrl_sys_pb2.pyi +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/py.typed +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/constant/__init__.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/constant/device_constant.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/datatype_converter.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/device_type.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/map.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/periodic.py +0 -0
- {pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/utility/rocker_util.py +0 -0
|
@@ -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)
|
|
@@ -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,6 +41,7 @@ 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
|
|
43
47
|
from pymammotion.utility.rocker_util import RockerControlUtil
|
|
@@ -88,10 +92,10 @@ def slashescape(err):
|
|
|
88
92
|
codecs.register_error("slashescape", slashescape)
|
|
89
93
|
|
|
90
94
|
|
|
91
|
-
def find_next_integer(lst: list[int],
|
|
95
|
+
def find_next_integer(lst: list[int], current_hash: float) -> int | None:
|
|
92
96
|
try:
|
|
93
97
|
# Find the index of the current integer
|
|
94
|
-
current_index = lst.index(
|
|
98
|
+
current_index = lst.index(current_hash)
|
|
95
99
|
|
|
96
100
|
# Check if there is a next integer in the list
|
|
97
101
|
if current_index + 1 < len(lst):
|
|
@@ -115,12 +119,12 @@ async def _handle_retry(fut: asyncio.Future[None], func, command: bytes) -> None
|
|
|
115
119
|
await func(command)
|
|
116
120
|
|
|
117
121
|
|
|
118
|
-
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:
|
|
119
123
|
"""Handle a retry."""
|
|
120
124
|
|
|
121
125
|
if not fut.done():
|
|
122
|
-
|
|
123
|
-
await loop.run_in_executor(None, func,
|
|
126
|
+
self._operation_lock.release()
|
|
127
|
+
await self.loop.run_in_executor(None, func, iot_id, command)
|
|
124
128
|
|
|
125
129
|
|
|
126
130
|
class ConnectionPreference(Enum):
|
|
@@ -334,7 +338,6 @@ class MammotionBaseDevice:
|
|
|
334
338
|
self._raw_data = LubaMsg().to_dict(casing=betterproto.Casing.SNAKE)
|
|
335
339
|
self._mower = device
|
|
336
340
|
self._state_manager = StateManager(self._mower)
|
|
337
|
-
|
|
338
341
|
self._state_manager.gethash_ack_callback.add_subscribers(self.datahash_response)
|
|
339
342
|
self._state_manager.get_commondata_ack_callback.add_subscribers(self.commdata_response)
|
|
340
343
|
self._notify_future: asyncio.Future[bytes] | None = None
|
|
@@ -945,7 +948,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
945
948
|
self._commands: MammotionCommand = MammotionCommand(cloud_device.deviceName)
|
|
946
949
|
self.currentID = ""
|
|
947
950
|
self.on_ready_callback: Optional[Callable[[], None]] = None
|
|
948
|
-
self.
|
|
951
|
+
self._waiting_queue = deque()
|
|
949
952
|
|
|
950
953
|
self._mqtt_client.on_connected = self.on_connected
|
|
951
954
|
self._mqtt_client.on_disconnected = self.on_disconnected
|
|
@@ -958,19 +961,19 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
958
961
|
# temporary for testing only
|
|
959
962
|
# self._start_sync_task = self.loop.call_later(30, lambda: asyncio.ensure_future(self.start_sync(0)))
|
|
960
963
|
|
|
961
|
-
def on_ready(self):
|
|
964
|
+
async def on_ready(self):
|
|
962
965
|
"""Callback for when MQTT is subscribed to events."""
|
|
963
966
|
if self.on_ready_callback:
|
|
964
967
|
self.on_ready_callback()
|
|
965
968
|
|
|
966
|
-
|
|
967
|
-
|
|
969
|
+
await self._ble_sync()
|
|
970
|
+
await self.run_periodic_sync_task()
|
|
968
971
|
|
|
969
|
-
def on_connected(self):
|
|
972
|
+
async def on_connected(self):
|
|
970
973
|
"""Callback for when MQTT connects."""
|
|
971
974
|
|
|
972
975
|
|
|
973
|
-
def on_disconnected(self):
|
|
976
|
+
async def on_disconnected(self):
|
|
974
977
|
"""Callback for when MQTT disconnects."""
|
|
975
978
|
|
|
976
979
|
async def _ble_sync(self):
|
|
@@ -993,27 +996,24 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
993
996
|
160, lambda: asyncio.ensure_future(self.run_periodic_sync_task())
|
|
994
997
|
)
|
|
995
998
|
|
|
996
|
-
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:
|
|
997
1000
|
"""Handle incoming MQTT messages."""
|
|
998
|
-
_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)
|
|
999
1002
|
|
|
1000
1003
|
json_str = json.dumps(payload)
|
|
1001
1004
|
payload = json.loads(json_str)
|
|
1002
1005
|
|
|
1003
|
-
self._handle_mqtt_message(topic, payload)
|
|
1006
|
+
await self._handle_mqtt_message(topic, payload)
|
|
1004
1007
|
|
|
1005
1008
|
async def _send_command(self, key: str, retry: int | None = None) -> bytes | None:
|
|
1006
1009
|
"""Send command to device via MQTT and read response."""
|
|
1007
|
-
if self._operation_lock.locked():
|
|
1008
|
-
_LOGGER.debug("%s: Operation already in progress, waiting for it to complete;", self.device.nickName)
|
|
1009
1010
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
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
|
|
1017
1017
|
|
|
1018
1018
|
async def _send_command_locked(self, key: str, command: bytes) -> bytes:
|
|
1019
1019
|
"""Send command to device and read response."""
|
|
@@ -1032,33 +1032,16 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
1032
1032
|
async def _execute_command_locked(self, key: str, command: bytes) -> bytes:
|
|
1033
1033
|
"""Execute command and read response."""
|
|
1034
1034
|
assert self._mqtt_client is not None
|
|
1035
|
-
self._notify_future = self.loop.create_future()
|
|
1036
1035
|
self._key = key
|
|
1037
1036
|
_LOGGER.debug("%s: Sending command: %s", self.device.nickName, key)
|
|
1038
|
-
loop
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
retry_handle = self.loop.call_at(
|
|
1042
|
-
self.loop.time() + 10,
|
|
1043
|
-
lambda: asyncio.ensure_future(
|
|
1044
|
-
_handle_retry_cloud(
|
|
1045
|
-
self._notify_future, self._mqtt_client.get_cloud_client().send_cloud_command, self.iot_id, command
|
|
1046
|
-
)
|
|
1047
|
-
),
|
|
1048
|
-
)
|
|
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)
|
|
1049
1040
|
timeout = 20
|
|
1050
|
-
timeout_handle = self.loop.call_at(self.loop.time() + timeout, _handle_timeout, self._notify_future)
|
|
1051
|
-
timeout_expired = False
|
|
1052
1041
|
try:
|
|
1053
|
-
notify_msg = await
|
|
1042
|
+
notify_msg = await future.async_get(timeout)
|
|
1054
1043
|
except asyncio.TimeoutError:
|
|
1055
|
-
timeout_expired = True
|
|
1056
1044
|
raise
|
|
1057
|
-
finally:
|
|
1058
|
-
if not timeout_expired:
|
|
1059
|
-
timeout_handle.cancel()
|
|
1060
|
-
retry_handle.cancel()
|
|
1061
|
-
self._notify_future = None
|
|
1062
1045
|
|
|
1063
1046
|
_LOGGER.debug("%s: Message received", self.device.nickName)
|
|
1064
1047
|
|
|
@@ -1066,15 +1049,12 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
1066
1049
|
|
|
1067
1050
|
async def _send_command_with_args(self, key: str, **kwargs: any) -> bytes | None:
|
|
1068
1051
|
"""Send command with arguments to device via MQTT and read response."""
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
except Exception as ex:
|
|
1076
|
-
_LOGGER.exception("%s: error in sending command - %s", self.device.nickName, ex)
|
|
1077
|
-
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
|
|
1078
1058
|
|
|
1079
1059
|
def _extract_message_id(self, payload: dict) -> str:
|
|
1080
1060
|
"""Extract the message ID from the payload."""
|
|
@@ -1089,7 +1069,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
1089
1069
|
_LOGGER.error("Error extracting encoded message. Payload: %s", payload)
|
|
1090
1070
|
return ""
|
|
1091
1071
|
|
|
1092
|
-
def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
|
|
1072
|
+
async def _parse_mqtt_response(self, topic: str, payload: dict) -> None:
|
|
1093
1073
|
"""Parse the MQTT response."""
|
|
1094
1074
|
if topic.endswith("/app/down/thing/events"):
|
|
1095
1075
|
_LOGGER.debug("Thing event received")
|
|
@@ -1100,14 +1080,25 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
|
1100
1080
|
binary_data = base64.b64decode(params.value.get("content", ""))
|
|
1101
1081
|
self._update_raw_data(cast(bytes, binary_data))
|
|
1102
1082
|
new_msg = LubaMsg().parse(cast(bytes, binary_data))
|
|
1103
|
-
if self._notify_future and not self._notify_future.done():
|
|
1104
|
-
self._notify_future.set_result(new_msg)
|
|
1105
|
-
asyncio.run(self._state_manager.notification(new_msg))
|
|
1106
1083
|
|
|
1107
|
-
|
|
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:
|
|
1108
1098
|
"""Async handler for incoming MQTT messages."""
|
|
1109
|
-
self._parse_mqtt_response(topic=topic, payload=payload)
|
|
1099
|
+
await self._parse_mqtt_response(topic=topic, payload=payload)
|
|
1110
1100
|
|
|
1111
|
-
|
|
1101
|
+
def _disconnect(self):
|
|
1112
1102
|
"""Disconnect the MQTT client."""
|
|
1113
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()
|
|
@@ -5,7 +5,7 @@ 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,7 +80,6 @@ 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
|
|
|
@@ -121,7 +121,8 @@ class MammotionMQTT:
|
|
|
121
121
|
|
|
122
122
|
if self.on_ready:
|
|
123
123
|
self.is_ready = True
|
|
124
|
-
self.on_ready()
|
|
124
|
+
future = asyncio.run_coroutine_threadsafe(self.on_ready(), self.loop)
|
|
125
|
+
asyncio.wrap_future(future, loop=self.loop)
|
|
125
126
|
# self._linkkit_client.query_ota_firmware()
|
|
126
127
|
# command = MammotionCommand(device_name="Luba")
|
|
127
128
|
# self._cloud_client.send_cloud_command(command.get_report_cfg())
|
|
@@ -137,13 +138,17 @@ class MammotionMQTT:
|
|
|
137
138
|
payload = json.loads(payload)
|
|
138
139
|
iot_id = payload.get("params", {}).get("iotId", "")
|
|
139
140
|
if iot_id != "" and self.on_message:
|
|
140
|
-
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
|
+
|
|
141
144
|
|
|
142
145
|
def _thing_on_connect(self, session_flag, rc, user_data):
|
|
143
146
|
"""Is called on thing connect."""
|
|
144
147
|
self.is_connected = True
|
|
145
148
|
if self.on_connected is not None:
|
|
146
|
-
self.on_connected()
|
|
149
|
+
future = asyncio.run_coroutine_threadsafe(self.on_connected(), self.loop)
|
|
150
|
+
asyncio.wrap_future(future, loop=self.loop)
|
|
151
|
+
|
|
147
152
|
logger.debug("on_connect, session_flag:%d, rc:%d", session_flag, rc)
|
|
148
153
|
|
|
149
154
|
# self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/#")
|
|
@@ -154,7 +159,9 @@ class MammotionMQTT:
|
|
|
154
159
|
self.is_connected = False
|
|
155
160
|
self.is_ready = False
|
|
156
161
|
if self.on_disconnected:
|
|
157
|
-
self.on_disconnected()
|
|
162
|
+
future = asyncio.run_coroutine_threadsafe(self.on_disconnected(), self.loop)
|
|
163
|
+
asyncio.wrap_future(future, loop=self.loop)
|
|
164
|
+
|
|
158
165
|
|
|
159
166
|
def _on_message(self, _client, _userdata, message: MQTTMessage):
|
|
160
167
|
"""Is called when message is received."""
|
|
@@ -185,3 +192,4 @@ class MammotionMQTT:
|
|
|
185
192
|
def get_cloud_client(self) -> Optional[CloudIOTGateway]:
|
|
186
193
|
"""Return internal cloud client."""
|
|
187
194
|
return self._cloud_client
|
|
195
|
+
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pymammotion"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.7"
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "pymammotion"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.7"
|
|
8
8
|
license = "GNU-3.0"
|
|
9
9
|
description = ""
|
|
10
10
|
readme = "README.md"
|
|
@@ -36,6 +36,7 @@ betterproto = "^1.2.5"
|
|
|
36
36
|
pyjoystick = "^1.2.4"
|
|
37
37
|
nest-asyncio = "^1.6.0"
|
|
38
38
|
numpy = "^1.26.0"
|
|
39
|
+
async-timeout = "^4.0.3"
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -52,7 +53,7 @@ mypy = "^1.10.0"
|
|
|
52
53
|
pre-commit = "^3.7.1"
|
|
53
54
|
|
|
54
55
|
[tool.bumpver]
|
|
55
|
-
current_version = "0.2.
|
|
56
|
+
current_version = "0.2.7"
|
|
56
57
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
57
58
|
commit_message = "Bump version {old_version} -> {new_version}"
|
|
58
59
|
commit = true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/dev_by_account_response.py
RENAMED
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/login_by_oauth_response.py
RENAMED
|
File without changes
|
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/aliyun/dataclass/session_by_authcode_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/data/model/generate_route_information.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymammotion-0.2.6 → pymammotion-0.2.7}/pymammotion/mammotion/commands/messages/navigation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|