pymammotion 0.2.41__py3-none-any.whl → 0.2.43__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.
- pymammotion/aliyun/cloud_gateway.py +90 -7
- pymammotion/aliyun/{dataclass → model}/dev_by_account_response.py +1 -1
- pymammotion/aliyun/model/stream_subscription_response.py +19 -0
- pymammotion/bluetooth/ble_message.py +9 -7
- pymammotion/data/model/device.py +12 -1
- pymammotion/data/model/device_config.py +1 -1
- pymammotion/data/model/rapid_state.py +3 -1
- pymammotion/data/model/report_info.py +9 -9
- pymammotion/data/state_manager.py +7 -1
- pymammotion/http/http.py +17 -0
- pymammotion/http/model/http.py +4 -0
- pymammotion/mammotion/commands/messages/network.py +15 -15
- pymammotion/mammotion/commands/messages/system.py +50 -19
- pymammotion/mammotion/devices/base.py +5 -1
- pymammotion/mammotion/devices/mammotion.py +31 -25
- pymammotion/mammotion/devices/mammotion_bluetooth.py +5 -0
- pymammotion/mammotion/devices/mammotion_cloud.py +37 -8
- pymammotion/mqtt/mammotion_mqtt.py +12 -0
- {pymammotion-0.2.41.dist-info → pymammotion-0.2.43.dist-info}/METADATA +1 -1
- {pymammotion-0.2.41.dist-info → pymammotion-0.2.43.dist-info}/RECORD +27 -26
- /pymammotion/aliyun/{dataclass → model}/aep_response.py +0 -0
- /pymammotion/aliyun/{dataclass → model}/connect_response.py +0 -0
- /pymammotion/aliyun/{dataclass → model}/login_by_oauth_response.py +0 -0
- /pymammotion/aliyun/{dataclass → model}/regions_response.py +0 -0
- /pymammotion/aliyun/{dataclass → model}/session_by_authcode_response.py +0 -0
- {pymammotion-0.2.41.dist-info → pymammotion-0.2.43.dist-info}/LICENSE +0 -0
- {pymammotion-0.2.41.dist-info → pymammotion-0.2.43.dist-info}/WHEEL +0 -0
@@ -17,14 +17,14 @@ from alibabacloud_iot_api_gateway.models import CommonParams, Config, IoTApiRequ
|
|
17
17
|
from alibabacloud_tea_util.client import Client as UtilClient
|
18
18
|
from alibabacloud_tea_util.models import RuntimeOptions
|
19
19
|
|
20
|
-
from pymammotion.aliyun.
|
21
|
-
from pymammotion.aliyun.
|
22
|
-
from pymammotion.aliyun.
|
20
|
+
from pymammotion.aliyun.model.aep_response import AepResponse
|
21
|
+
from pymammotion.aliyun.model.connect_response import ConnectResponse
|
22
|
+
from pymammotion.aliyun.model.dev_by_account_response import (
|
23
23
|
ListingDevByAccountResponse,
|
24
24
|
)
|
25
|
-
from pymammotion.aliyun.
|
26
|
-
from pymammotion.aliyun.
|
27
|
-
from pymammotion.aliyun.
|
25
|
+
from pymammotion.aliyun.model.login_by_oauth_response import LoginByOAuthResponse
|
26
|
+
from pymammotion.aliyun.model.regions_response import RegionResponse
|
27
|
+
from pymammotion.aliyun.model.session_by_authcode_response import (
|
28
28
|
SessionByAuthCodeResponse,
|
29
29
|
)
|
30
30
|
from pymammotion.const import ALIYUN_DOMAIN, APP_KEY, APP_SECRET, APP_VERSION
|
@@ -436,6 +436,49 @@ class CloudIOTGateway:
|
|
436
436
|
|
437
437
|
return response.body
|
438
438
|
|
439
|
+
def sign_out(self) -> None:
|
440
|
+
config = Config(
|
441
|
+
app_key=self._app_key,
|
442
|
+
app_secret=self._app_secret,
|
443
|
+
domain=self._region_response.data.apiGatewayEndpoint,
|
444
|
+
)
|
445
|
+
client = Client(config)
|
446
|
+
|
447
|
+
# build request
|
448
|
+
request = CommonParams(api_ver="1.0.4", language="en-US")
|
449
|
+
body = IoTApiRequest(
|
450
|
+
id=str(uuid.uuid4()),
|
451
|
+
params={
|
452
|
+
"request": {
|
453
|
+
"refreshToken": self._session_by_authcode_response.data.refreshToken,
|
454
|
+
"identityId": self._session_by_authcode_response.data.identityId,
|
455
|
+
}
|
456
|
+
},
|
457
|
+
request=request,
|
458
|
+
version="1.0",
|
459
|
+
)
|
460
|
+
|
461
|
+
# send request
|
462
|
+
# possibly need to do this ourselves
|
463
|
+
response = client.do_request(
|
464
|
+
"/iotx/account/invalidSession",
|
465
|
+
"https",
|
466
|
+
"POST",
|
467
|
+
None,
|
468
|
+
body,
|
469
|
+
RuntimeOptions(),
|
470
|
+
)
|
471
|
+
logger.debug(response.status_message)
|
472
|
+
logger.debug(response.headers)
|
473
|
+
logger.debug(response.status_code)
|
474
|
+
logger.debug(response.body)
|
475
|
+
|
476
|
+
# Decode the response body
|
477
|
+
response_body_str = response.body.decode("utf-8")
|
478
|
+
|
479
|
+
# Load the JSON string into a dictionary
|
480
|
+
response_body_dict = json.loads(response_body_str)
|
481
|
+
|
439
482
|
def check_or_refresh_session(self):
|
440
483
|
"""Check or refresh the session."""
|
441
484
|
logger.debug("Try to refresh token")
|
@@ -542,6 +585,47 @@ class CloudIOTGateway:
|
|
542
585
|
self._devices_by_account_response = ListingDevByAccountResponse.from_dict(response_body_dict)
|
543
586
|
return self._devices_by_account_response
|
544
587
|
|
588
|
+
def list_binding_by_dev(self, iot_id: str):
|
589
|
+
config = Config(
|
590
|
+
app_key=self._app_key,
|
591
|
+
app_secret=self._app_secret,
|
592
|
+
domain=self._region_response.data.apiGatewayEndpoint,
|
593
|
+
)
|
594
|
+
|
595
|
+
client = Client(config)
|
596
|
+
|
597
|
+
# build request
|
598
|
+
request = CommonParams(
|
599
|
+
api_ver="1.0.8",
|
600
|
+
language="en-US",
|
601
|
+
iot_token=self._session_by_authcode_response.data.iotToken,
|
602
|
+
)
|
603
|
+
body = IoTApiRequest(
|
604
|
+
id=str(uuid.uuid4()),
|
605
|
+
params={"pageSize": 100, "pageNo": 1, "iotId": iot_id},
|
606
|
+
request=request,
|
607
|
+
version="1.0",
|
608
|
+
)
|
609
|
+
|
610
|
+
# send request
|
611
|
+
response = client.do_request("/uc/listBindingByDev", "https", "POST", None, body, RuntimeOptions())
|
612
|
+
logger.debug(response.status_message)
|
613
|
+
logger.debug(response.headers)
|
614
|
+
logger.debug(response.status_code)
|
615
|
+
logger.debug(response.body)
|
616
|
+
|
617
|
+
# Decode the response body
|
618
|
+
response_body_str = response.body.decode("utf-8")
|
619
|
+
|
620
|
+
# Load the JSON string into a dictionary
|
621
|
+
response_body_dict = json.loads(response_body_str)
|
622
|
+
|
623
|
+
if int(response_body_dict.get("code")) != 200:
|
624
|
+
raise Exception("Error in creating session: " + response_body_dict["msg"])
|
625
|
+
|
626
|
+
self._devices_by_account_response = ListingDevByAccountResponse.from_dict(response_body_dict)
|
627
|
+
return self._devices_by_account_response
|
628
|
+
|
545
629
|
def send_cloud_command(self, iot_id: str, command: bytes) -> str:
|
546
630
|
"""Send a cloud command to the specified IoT device."""
|
547
631
|
|
@@ -587,7 +671,6 @@ class CloudIOTGateway:
|
|
587
671
|
version="1.0",
|
588
672
|
)
|
589
673
|
logger.debug(self.converter.printBase64Binary(command))
|
590
|
-
|
591
674
|
# send request
|
592
675
|
response = client.do_request("/thing/service/invoke", "https", "POST", None, body, RuntimeOptions())
|
593
676
|
logger.debug(response.status_message)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
5
|
+
|
6
|
+
|
7
|
+
@dataclass
|
8
|
+
class Camera(DataClassORJSONMixin):
|
9
|
+
cameraId: int
|
10
|
+
token: str
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class StreamSubscriptionResponse(DataClassORJSONMixin):
|
15
|
+
appid: str
|
16
|
+
cameras: List[Camera]
|
17
|
+
channelName: str
|
18
|
+
token: str
|
19
|
+
uid: int
|
@@ -16,7 +16,6 @@ from pymammotion.bluetooth.data.convert import parse_custom_data
|
|
16
16
|
from pymammotion.bluetooth.data.framectrldata import FrameCtrlData
|
17
17
|
from pymammotion.bluetooth.data.notifydata import BlufiNotifyData
|
18
18
|
from pymammotion.data.model.execute_boarder import ExecuteBorder
|
19
|
-
from pymammotion.mammotion.commands.messages.navigation import MessageNavigation
|
20
19
|
from pymammotion.proto import (
|
21
20
|
dev_net_pb2,
|
22
21
|
luba_msg_pb2,
|
@@ -49,7 +48,6 @@ class BleMessage:
|
|
49
48
|
mReadSequence: iter
|
50
49
|
mAck: queue
|
51
50
|
notification: BlufiNotifyData
|
52
|
-
messageNavigation: MessageNavigation = MessageNavigation()
|
53
51
|
|
54
52
|
def __init__(self, client: BleakClient) -> None:
|
55
53
|
self.client = client
|
@@ -123,9 +121,9 @@ class BleMessage:
|
|
123
121
|
|
124
122
|
async def requestDeviceStatus(self) -> None:
|
125
123
|
request = False
|
126
|
-
type = self.
|
124
|
+
type = self.getTypeValue(0, 5)
|
127
125
|
try:
|
128
|
-
request = await self.
|
126
|
+
request = await self.post(BleMessage.mEncrypted, BleMessage.mChecksum, False, type, None)
|
129
127
|
# _LOGGER.debug(request)
|
130
128
|
except Exception as err:
|
131
129
|
# Log.w(TAG, "post requestDeviceStatus interrupted")
|
@@ -137,9 +135,9 @@ class BleMessage:
|
|
137
135
|
|
138
136
|
async def requestDeviceVersion(self) -> None:
|
139
137
|
request = False
|
140
|
-
type = self.
|
138
|
+
type = self.getTypeValue(0, 7)
|
141
139
|
try:
|
142
|
-
request = await self.
|
140
|
+
request = await self.post(BleMessage.mEncrypted, BleMessage.mChecksum, False, type, None)
|
143
141
|
# _LOGGER.debug(request)
|
144
142
|
except Exception as err:
|
145
143
|
# Log.w(TAG, "post requestDeviceStatus interrupted")
|
@@ -147,7 +145,7 @@ class BleMessage:
|
|
147
145
|
_LOGGER.error(err)
|
148
146
|
|
149
147
|
async def sendBorderPackage(self, executeBorder: ExecuteBorder) -> None:
|
150
|
-
await self.
|
148
|
+
await self.post_custom_data(serialize(executeBorder))
|
151
149
|
|
152
150
|
async def gatt_write(self, data: bytes) -> None:
|
153
151
|
await self.client.write_gatt_char(UUID_WRITE_CHARACTERISTIC, data, True)
|
@@ -340,6 +338,10 @@ class BleMessage:
|
|
340
338
|
# onPostCustomDataResult(status, data)
|
341
339
|
except Exception as err:
|
342
340
|
_LOGGER.debug(err)
|
341
|
+
# we might be constantly connected and in a bad state
|
342
|
+
self.mSendSequence = itertools.count()
|
343
|
+
self.mReadSequence = itertools.count()
|
344
|
+
await self.client.disconnect()
|
343
345
|
|
344
346
|
async def post(
|
345
347
|
self,
|
pymammotion/data/model/device.py
CHANGED
@@ -109,10 +109,21 @@ class MowingDevice(DataClassORJSONMixin):
|
|
109
109
|
location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
|
110
110
|
)
|
111
111
|
|
112
|
-
self.report_data
|
112
|
+
self.report_data.update(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE))
|
113
|
+
|
113
114
|
|
114
115
|
def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg) -> None:
|
116
|
+
coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude)
|
115
117
|
self.mowing_state = RapidState().from_raw(rapid_state.rapid_state_data)
|
118
|
+
self.location.position_type = self.mowing_state.pos_type
|
119
|
+
self.location.orientation = self.mowing_state.toward / 10000
|
120
|
+
self.location.device = coordinate_converter.enu_to_lla(
|
121
|
+
parse_double(self.mowing_state.pos_y, 4.0), parse_double(self.mowing_state.pos_x, 4.0)
|
122
|
+
)
|
123
|
+
if self.mowing_state.zone_hash:
|
124
|
+
self.location.work_zone = (
|
125
|
+
self.mowing_state.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0
|
126
|
+
)
|
116
127
|
|
117
128
|
def mow_info(self, toapp_mow_info: MowToAppInfoT) -> None:
|
118
129
|
pass
|
@@ -8,7 +8,7 @@ from pymammotion.utility.device_type import DeviceType
|
|
8
8
|
@dataclass
|
9
9
|
class DeviceLimits(DataClassORJSONMixin):
|
10
10
|
blade_height_min: int = 30
|
11
|
-
blade_height_max: int =
|
11
|
+
blade_height_max: int = 100
|
12
12
|
working_speed_min: float = 0.2
|
13
13
|
working_speed_max: float = 1.2
|
14
14
|
working_path_min: int = 15
|
@@ -1,6 +1,8 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
2
|
from enum import Enum
|
3
3
|
|
4
|
+
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
5
|
+
|
4
6
|
from pymammotion.utility.conversions import parse_double
|
5
7
|
|
6
8
|
|
@@ -11,7 +13,7 @@ class RTKStatus(Enum):
|
|
11
13
|
|
12
14
|
|
13
15
|
@dataclass
|
14
|
-
class RapidState:
|
16
|
+
class RapidState(DataClassORJSONMixin):
|
15
17
|
pos_x: float = 0
|
16
18
|
pos_y: float = 0
|
17
19
|
rtk_status: RTKStatus = RTKStatus.NONE
|
@@ -63,22 +63,22 @@ class WorkData(DataClassORJSONMixin):
|
|
63
63
|
|
64
64
|
|
65
65
|
@dataclass
|
66
|
-
class ReportData:
|
66
|
+
class ReportData(DataClassORJSONMixin):
|
67
67
|
connect: ConnectData = field(default_factory=ConnectData)
|
68
68
|
dev: DeviceData = field(default_factory=DeviceData)
|
69
69
|
rtk: RTKData = field(default_factory=RTKData)
|
70
70
|
locations: list[LocationData] = field(default_factory=list)
|
71
71
|
work: WorkData = field(default_factory=WorkData)
|
72
72
|
|
73
|
-
def
|
73
|
+
def update(self, data: dict):
|
74
74
|
locations = self.locations
|
75
75
|
if data.get("locations") is not None:
|
76
76
|
locations = [LocationData.from_dict(loc) for loc in data.get("locations", [])]
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
78
|
+
|
79
|
+
self.connect=ConnectData.from_dict(data.get("connect", asdict(self.connect)))
|
80
|
+
self.dev=DeviceData.from_dict(data.get("dev", asdict(self.dev)))
|
81
|
+
self.rtk=RTKData.from_dict(data.get("rtk", asdict(self.rtk)))
|
82
|
+
self.locations=locations
|
83
|
+
self.work=WorkData.from_dict(data.get("work", asdict(self.work)))
|
84
|
+
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Manage state from notifications into MowingDevice."""
|
2
2
|
|
3
|
+
from datetime import datetime
|
3
4
|
from typing import Any, Awaitable, Callable, Optional
|
4
5
|
|
5
6
|
import betterproto
|
@@ -8,12 +9,14 @@ from pymammotion.data.model.device import MowingDevice
|
|
8
9
|
from pymammotion.data.model.hash_list import AreaHashNameList
|
9
10
|
from pymammotion.proto.luba_msg import LubaMsg
|
10
11
|
from pymammotion.proto.mctrl_nav import AppGetAllAreaHashName, NavGetCommDataAck, NavGetHashListAck
|
12
|
+
from pymammotion.utility.constant import WorkMode
|
11
13
|
|
12
14
|
|
13
15
|
class StateManager:
|
14
16
|
"""Manage state."""
|
15
17
|
|
16
18
|
_device: MowingDevice
|
19
|
+
last_updated_at: datetime = datetime.now()
|
17
20
|
|
18
21
|
def __init__(self, device: MowingDevice) -> None:
|
19
22
|
self._device = device
|
@@ -21,6 +24,7 @@ class StateManager:
|
|
21
24
|
self.get_commondata_ack_callback: Optional[Callable[[NavGetCommDataAck], Awaitable[None]]] = None
|
22
25
|
self.on_notification_callback: Optional[Callable[[], Awaitable[None]]] = None
|
23
26
|
self.queue_command_callback: Optional[Callable[[str, dict[str, Any]], Awaitable[bytes]]] = None
|
27
|
+
self.last_updated_at = datetime.now()
|
24
28
|
|
25
29
|
def get_device(self) -> MowingDevice:
|
26
30
|
"""Get device."""
|
@@ -33,6 +37,7 @@ class StateManager:
|
|
33
37
|
async def notification(self, message: LubaMsg) -> None:
|
34
38
|
"""Handle protobuf notifications."""
|
35
39
|
res = betterproto.which_one_of(message, "LubaSubMsg")
|
40
|
+
self.last_updated_at = datetime.now()
|
36
41
|
|
37
42
|
match res[0]:
|
38
43
|
case "nav":
|
@@ -78,7 +83,8 @@ class StateManager:
|
|
78
83
|
case "toapp_report_data":
|
79
84
|
self._device.update_report_data(sys_msg[1])
|
80
85
|
if self.queue_command_callback:
|
81
|
-
|
86
|
+
if self._device.sys.toapp_report_data.dev.sys_status != WorkMode.MODE_WORKING:
|
87
|
+
await self.queue_command_callback("get_report_cfg_stop")
|
82
88
|
case "mow_to_app_info":
|
83
89
|
self._device.mow_info(sys_msg[1])
|
84
90
|
case "system_tard_state_tunnel":
|
pymammotion/http/http.py
CHANGED
@@ -3,6 +3,7 @@ from typing import cast
|
|
3
3
|
|
4
4
|
from aiohttp import ClientSession
|
5
5
|
|
6
|
+
from pymammotion.aliyun.model.stream_subscription_response import StreamSubscriptionResponse
|
6
7
|
from pymammotion.const import (
|
7
8
|
MAMMOTION_API_DOMAIN,
|
8
9
|
MAMMOTION_CLIENT_ID,
|
@@ -43,6 +44,22 @@ class MammotionHTTP:
|
|
43
44
|
data = await resp.json()
|
44
45
|
response = Response.from_dict(data)
|
45
46
|
|
47
|
+
async def get_stream_subscription(self, iot_id: str) -> Response[StreamSubscriptionResponse]:
|
48
|
+
"""Get agora.io data for view camera stream"""
|
49
|
+
async with ClientSession(MAMMOTION_API_DOMAIN) as session:
|
50
|
+
async with session.post(
|
51
|
+
"/device-server/v1/stream/subscription",
|
52
|
+
json={"deviceId": iot_id},
|
53
|
+
headers={
|
54
|
+
"Authorization": f"{self._headers.get('Authorization', "")}",
|
55
|
+
"Content-Type": "application/json",
|
56
|
+
},
|
57
|
+
) as resp:
|
58
|
+
data = await resp.json()
|
59
|
+
# TODO catch errors from mismatch like token expire etc
|
60
|
+
# Assuming the data format matches the expected structure
|
61
|
+
return Response[StreamSubscriptionResponse].from_dict(data)
|
62
|
+
|
46
63
|
@classmethod
|
47
64
|
async def login(cls, session: ClientSession, username: str, password: str) -> Response[LoginResponseData]:
|
48
65
|
async with session.post(
|
pymammotion/http/model/http.py
CHANGED
@@ -2,6 +2,7 @@ from dataclasses import dataclass
|
|
2
2
|
from typing import Generic, Literal, Optional, TypeVar
|
3
3
|
|
4
4
|
from mashumaro import DataClassDictMixin
|
5
|
+
from mashumaro.config import BaseConfig
|
5
6
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
6
7
|
|
7
8
|
DataT = TypeVar("DataT")
|
@@ -51,6 +52,9 @@ class Response(DataClassDictMixin, Generic[DataT]):
|
|
51
52
|
msg: str
|
52
53
|
data: DataT | None = None
|
53
54
|
|
55
|
+
class Config(BaseConfig):
|
56
|
+
omit_default = True
|
57
|
+
|
54
58
|
|
55
59
|
@dataclass
|
56
60
|
class LoginResponseUserInformation(DataClassORJSONMixin):
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# === sendOrderMsg_Net ===
|
2
|
-
|
2
|
+
from pymammotion import logger
|
3
3
|
from pymammotion.mammotion.commands.messages.navigation import MessageNavigation
|
4
4
|
from pymammotion.proto import dev_net_pb2, luba_msg_pb2
|
5
5
|
|
@@ -34,12 +34,12 @@ class MessageNetwork:
|
|
34
34
|
|
35
35
|
def get_4g_module_info(self) -> bytes:
|
36
36
|
build = dev_net_pb2.DevNet(todev_get_mnet_cfg_req=dev_net_pb2.DevNet().todev_get_mnet_cfg_req)
|
37
|
-
|
37
|
+
logger.debug("Send command -- Get device 4G network module information")
|
38
38
|
return self.send_order_msg_net(build)
|
39
39
|
|
40
40
|
def get_4g_info(self) -> bytes:
|
41
41
|
build = dev_net_pb2.DevNet(todev_mnet_info_req=dev_net_pb2.DevNet().todev_mnet_info_req)
|
42
|
-
|
42
|
+
logger.debug("Send command -- Get device 4G network information")
|
43
43
|
return self.send_order_msg_net(build)
|
44
44
|
|
45
45
|
def set_zmq_enable(self) -> bytes:
|
@@ -50,12 +50,12 @@ class MessageNetwork:
|
|
50
50
|
tx_zmq_url="tcp://0.0.0.0:5555",
|
51
51
|
)
|
52
52
|
)
|
53
|
-
|
53
|
+
logger.debug("Send command -- Set vision ZMQ to enable")
|
54
54
|
return self.send_order_msg_net(build)
|
55
55
|
|
56
56
|
def set_iot_setting(self, iot_control_type: dev_net_pb2.iot_conctrl_type) -> bytes:
|
57
57
|
build = dev_net_pb2.DevNet(todev_set_iot_offline_req=iot_control_type)
|
58
|
-
|
58
|
+
logger.debug("Send command -- Device re-online")
|
59
59
|
return self.send_order_msg_net(build)
|
60
60
|
|
61
61
|
def set_device_log_upload(
|
@@ -75,7 +75,7 @@ class MessageNetwork:
|
|
75
75
|
num=number,
|
76
76
|
type=type,
|
77
77
|
)
|
78
|
-
|
78
|
+
logger.debug(
|
79
79
|
f"Send log====Feedback====Command======requestID:{request_id} operation:{operation} serverIp:{server_ip} type:{type}"
|
80
80
|
)
|
81
81
|
return self.send_order_msg_net(dev_net_pb2.DevNet(todev_ble_sync=1, todev_uploadfile_req=build))
|
@@ -98,7 +98,7 @@ class MessageNetwork:
|
|
98
98
|
num=number,
|
99
99
|
type=type,
|
100
100
|
)
|
101
|
-
|
101
|
+
logger.debug(
|
102
102
|
f"Send log====Feedback====Command======requestID:{request_id} operation:{operation} serverIp:{server_ip} type:{type}"
|
103
103
|
)
|
104
104
|
return self.send_order_msg_net(dev_net_pb2.DevNet(todev_ble_sync=1, todev_uploadfile_req=build))
|
@@ -126,7 +126,7 @@ class MessageNetwork:
|
|
126
126
|
|
127
127
|
def get_device_network_info(self) -> bytes:
|
128
128
|
build = dev_net_pb2.DevNet(todev_networkinfo_req=dev_net_pb2.GetNetworkInfoReq(req_ids=1))
|
129
|
-
|
129
|
+
logger.debug("Send command - get device network information")
|
130
130
|
return self.send_order_msg_net(build)
|
131
131
|
|
132
132
|
def set_device_4g_enable_status(self, new_4g_status: bool) -> bytes:
|
@@ -141,7 +141,7 @@ class MessageNetwork:
|
|
141
141
|
),
|
142
142
|
)
|
143
143
|
|
144
|
-
|
144
|
+
logger.debug(f"Send command - set 4G (on/off status). newWifiStatus={new_4g_status}")
|
145
145
|
return self.send_order_msg_net(build)
|
146
146
|
|
147
147
|
def set_device_wifi_enable_status(self, new_wifi_status: bool) -> bytes:
|
@@ -149,11 +149,11 @@ class MessageNetwork:
|
|
149
149
|
todev_ble_sync=1,
|
150
150
|
todev_Wifi_Configuration=dev_net_pb2.DrvWifiSet(configParam=4, wifi_enable=new_wifi_status),
|
151
151
|
)
|
152
|
-
|
152
|
+
logger.debug(f"szNetwork: Send command - set network (on/off status). newWifiStatus={new_wifi_status}")
|
153
153
|
return self.send_order_msg_net(build)
|
154
154
|
|
155
155
|
def wifi_connectinfo_update(self, device_name: str, is_binary: bool) -> bytes:
|
156
|
-
|
156
|
+
logger.debug(
|
157
157
|
f"Send command - get Wifi connection information.wifiConnectinfoUpdate().deviceName={device_name}.isBinary={is_binary}"
|
158
158
|
)
|
159
159
|
if is_binary:
|
@@ -161,7 +161,7 @@ class MessageNetwork:
|
|
161
161
|
todev_ble_sync=1,
|
162
162
|
todev_WifiMsgUpload=dev_net_pb2.DrvWifiUpload(wifi_msg_upload=1),
|
163
163
|
)
|
164
|
-
|
164
|
+
logger.debug("Send command - get Wifi connection information")
|
165
165
|
return self.send_order_msg_net(build)
|
166
166
|
self.wifi_connectinfo_update2()
|
167
167
|
|
@@ -171,10 +171,10 @@ class MessageNetwork:
|
|
171
171
|
# 68, hash_map)) # ToDo: Fix this
|
172
172
|
|
173
173
|
def get_record_wifi_list(self, is_binary: bool) -> bytes:
|
174
|
-
|
174
|
+
logger.debug(f"getRecordWifiList().isBinary={is_binary}")
|
175
175
|
if is_binary:
|
176
176
|
build = dev_net_pb2.DevNet(todev_ble_sync=1, todev_WifiListUpload=dev_net_pb2.DrvWifiList())
|
177
|
-
|
177
|
+
logger.debug("Send command - get memorized WiFi list upload command")
|
178
178
|
return self.send_order_msg_net(build)
|
179
179
|
self.get_record_wifi_list2()
|
180
180
|
|
@@ -189,7 +189,7 @@ class MessageNetwork:
|
|
189
189
|
todev_ble_sync=1,
|
190
190
|
todev_Wifi_Configuration=dev_net_pb2.DrvWifiSet(configParam=status, Confssid=ssid),
|
191
191
|
)
|
192
|
-
|
192
|
+
logger.debug(
|
193
193
|
f"Send command - set network (disconnect, direct connect, forget, no operation reconnect) operation command (downlink ssid={ssid}, status={status})"
|
194
194
|
)
|
195
195
|
return self.send_order_msg_net(build)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
import datetime
|
3
3
|
from abc import ABC
|
4
4
|
|
5
|
+
from pymammotion import logger
|
5
6
|
from pymammotion.mammotion.commands.abstract_message import AbstractMessage
|
6
7
|
from pymammotion.mammotion.commands.messages.navigation import MessageNavigation
|
7
8
|
from pymammotion.proto.luba_msg import LubaMsg, MsgAttr, MsgCmdType, MsgDevice
|
@@ -36,7 +37,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
36
37
|
|
37
38
|
def reset_system(self):
|
38
39
|
build = MctlSys(todev_reset_system=1)
|
39
|
-
|
40
|
+
logger.debug("Send command - send factory reset")
|
40
41
|
return self.send_order_msg_sys(build)
|
41
42
|
|
42
43
|
def set_blade_control(self, on_off: int):
|
@@ -72,24 +73,24 @@ class MessageSystem(AbstractMessage, ABC):
|
|
72
73
|
end_hour=0,
|
73
74
|
end_min=0,
|
74
75
|
)
|
75
|
-
|
76
|
+
logger.debug(f"Send read and write sidelight command is_sidelight:{
|
76
77
|
is_sidelight}, operate:{operate}")
|
77
78
|
build2 = MctlSys(todev_time_ctrl_light=build)
|
78
|
-
|
79
|
+
logger.debug(f"Send command - send read and write sidelight command is_sidelight:{
|
79
80
|
is_sidelight}, operate:{operate}, timeCtrlLight:{build}")
|
80
81
|
return self.send_order_msg_sys(build2)
|
81
82
|
|
82
83
|
def test_tool_order_to_sys(self, sub_cmd: int, param_id: int, param_value: list[int]):
|
83
84
|
build = MCtrlSimulationCmdData(sub_cmd=sub_cmd, param_id=param_id, param_value=param_value)
|
84
|
-
|
85
|
+
logger.debug(f"Send tool test command: subCmd={sub_cmd}, param_id:{
|
85
86
|
param_id}, param_value={param_value}")
|
86
87
|
build2 = MctlSys(simulation_cmd=build)
|
87
|
-
|
88
|
+
logger.debug(f"Send tool test command: subCmd={sub_cmd}, param_id:{
|
88
89
|
param_id}, param_value={param_value}")
|
89
90
|
return self.send_order_msg_sys(build2)
|
90
91
|
|
91
|
-
def read_and_set_rtk_paring_code(self, op: int, cgf: str):
|
92
|
-
|
92
|
+
def read_and_set_rtk_paring_code(self, op: int, cgf: str | None = None):
|
93
|
+
logger.debug(f"Send read and write base station configuration quality op:{
|
93
94
|
op}, cgf:{cgf}")
|
94
95
|
return self.send_order_msg_sys(MctlSys(todev_lora_cfg_req=LoraCfgReq(op=op, cfg=cgf)))
|
95
96
|
|
@@ -98,7 +99,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
98
99
|
self.messageNavigation.allpowerfull_rw_adapter_x3(id, context, rw)
|
99
100
|
return
|
100
101
|
build = MctlSys(bidire_comm_cmd=SysCommCmd(id=id, context=context, rw=rw))
|
101
|
-
|
102
|
+
logger.debug(f"Send command - 9 general read and write command id={id}, context={context}, rw={rw}")
|
102
103
|
if id == 5:
|
103
104
|
# This logic doesnt make snese, but its what they had so..
|
104
105
|
return self.send_order_msg_sys(build)
|
@@ -107,7 +108,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
107
108
|
# Commented out as not needed and too many refs to try fix up
|
108
109
|
# def factory_test_order(self, test_id: int, test_duration: int, expect: str):
|
109
110
|
# new_builder = mow_to_app_qctools_info_t.Builder()
|
110
|
-
#
|
111
|
+
# logger.debug(f"Factory tool logger.debug, expect={expect}")
|
111
112
|
# if not expect:
|
112
113
|
# build = new_builder.set_type_value(
|
113
114
|
# test_id).set_time_of_duration(test_duration).build()
|
@@ -157,11 +158,11 @@ class MessageSystem(AbstractMessage, ABC):
|
|
157
158
|
# else:
|
158
159
|
# build = new_builder.set_type_value(
|
159
160
|
# test_id).set_time_of_duration(test_duration).build()
|
160
|
-
#
|
161
|
+
# logger.debug(f"Factory tool logger.debug, mow_to_app_qctools_info_t={
|
161
162
|
# build.except_count}, mow_to_app_qctools_info_t22={build.except_list}")
|
162
163
|
# build2 = MctlSys(mow_to_app_qctools_info=build)
|
163
|
-
#
|
164
|
-
# test_id}, testDuration={test_duration}", "Factory tool
|
164
|
+
# logger.debug(f"Send command - factory tool test command testId={
|
165
|
+
# test_id}, testDuration={test_duration}", "Factory tool logger.debug222", True)
|
165
166
|
# return self.send_order_msg_sys(build2)
|
166
167
|
|
167
168
|
def send_sys_set_date_time(self):
|
@@ -175,7 +176,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
175
176
|
i7 = calendar.second
|
176
177
|
i8 = calendar.utcoffset().total_seconds() // 60 if calendar.utcoffset() else 0
|
177
178
|
i9 = 1 if calendar.dst() else 0
|
178
|
-
|
179
|
+
logger.debug(f"Print time zone, time zone={
|
179
180
|
i8}, daylight saving time={i9} week={i4}")
|
180
181
|
build = MctlSys(
|
181
182
|
todev_data_time=SysSetDateTime(
|
@@ -190,7 +191,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
190
191
|
daylight=i9,
|
191
192
|
)
|
192
193
|
)
|
193
|
-
|
194
|
+
logger.debug(
|
194
195
|
f"Send command - synchronize time zone={i8}, daylight saving time={i9} week={i4}, day:{
|
195
196
|
i3}, month:{i2}, hours:{i5}, minutes:{i6}, seconds:{i7}, year={i}",
|
196
197
|
"Time synchronization",
|
@@ -222,16 +223,46 @@ class MessageSystem(AbstractMessage, ABC):
|
|
222
223
|
count=count,
|
223
224
|
)
|
224
225
|
)
|
225
|
-
|
226
|
+
logger.debug(f"Send command==== IOT slim data Act {
|
226
227
|
build.todev_report_cfg.act} {build}")
|
227
228
|
return self.send_order_msg_sys(build)
|
228
229
|
|
229
|
-
def
|
230
|
-
self, timeout: int = 10000, period: int = 1000, no_change_period: int = 2000, stop: bool = False
|
231
|
-
):
|
230
|
+
def get_report_cfg_stop(self, timeout: int = 10000, period: int = 1000, no_change_period: int = 1000):
|
232
231
|
mctlsys = MctlSys(
|
233
232
|
todev_report_cfg=ReportInfoCfg(
|
234
|
-
act=RptAct.RPT_STOP
|
233
|
+
act=RptAct.RPT_STOP,
|
234
|
+
timeout=timeout,
|
235
|
+
period=period,
|
236
|
+
no_change_period=no_change_period,
|
237
|
+
count=1,
|
238
|
+
)
|
239
|
+
)
|
240
|
+
|
241
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_CONNECT)
|
242
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_RTK)
|
243
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_DEV_LOCAL)
|
244
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_WORK)
|
245
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_DEV_STA)
|
246
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_MAINTAIN)
|
247
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VISION_POINT)
|
248
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VIO)
|
249
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VISION_STATISTIC)
|
250
|
+
|
251
|
+
lubaMsg = LubaMsg()
|
252
|
+
lubaMsg.msgtype = MsgCmdType.MSG_CMD_TYPE_EMBED_SYS
|
253
|
+
lubaMsg.sender = MsgDevice.DEV_MOBILEAPP
|
254
|
+
lubaMsg.rcver = MsgDevice.DEV_MAINCTL
|
255
|
+
lubaMsg.msgattr = MsgAttr.MSG_ATTR_REQ
|
256
|
+
lubaMsg.seqs = 1
|
257
|
+
lubaMsg.version = 1
|
258
|
+
lubaMsg.subtype = 1
|
259
|
+
lubaMsg.sys = mctlsys
|
260
|
+
return lubaMsg.SerializeToString()
|
261
|
+
|
262
|
+
def get_report_cfg(self, timeout: int = 10000, period: int = 1000, no_change_period: int = 2000):
|
263
|
+
mctlsys = MctlSys(
|
264
|
+
todev_report_cfg=ReportInfoCfg(
|
265
|
+
act=RptAct.RPT_START,
|
235
266
|
timeout=timeout,
|
236
267
|
period=period,
|
237
268
|
no_change_period=no_change_period,
|
@@ -5,7 +5,7 @@ from typing import Any, Awaitable, Callable
|
|
5
5
|
|
6
6
|
import betterproto
|
7
7
|
|
8
|
-
from pymammotion.aliyun.
|
8
|
+
from pymammotion.aliyun.model.dev_by_account_response import Device
|
9
9
|
from pymammotion.data.model import RegionData
|
10
10
|
from pymammotion.data.model.device import MowingDevice
|
11
11
|
from pymammotion.data.state_manager import StateManager
|
@@ -190,6 +190,10 @@ class MammotionBaseDevice:
|
|
190
190
|
async def _ble_sync(self):
|
191
191
|
"""Send ble sync command every 3 seconds or sooner."""
|
192
192
|
|
193
|
+
@abstractmethod
|
194
|
+
def stop(self):
|
195
|
+
"""Stop everything ready for destroying."""
|
196
|
+
|
193
197
|
async def start_sync(self, retry: int) -> None:
|
194
198
|
"""Start synchronization with the device."""
|
195
199
|
await self.queue_command("get_device_base_info")
|
@@ -8,12 +8,10 @@ from enum import Enum
|
|
8
8
|
from functools import cache
|
9
9
|
from typing import Any
|
10
10
|
|
11
|
-
from aiohttp import ClientSession
|
12
11
|
from bleak.backends.device import BLEDevice
|
13
12
|
|
14
13
|
from pymammotion.aliyun.cloud_gateway import CloudIOTGateway
|
15
|
-
from pymammotion.aliyun.
|
16
|
-
from pymammotion.const import MAMMOTION_DOMAIN
|
14
|
+
from pymammotion.aliyun.model.dev_by_account_response import Device
|
17
15
|
from pymammotion.data.model.account import Credentials
|
18
16
|
from pymammotion.data.model.device import MowingDevice
|
19
17
|
from pymammotion.http.http import connect_http
|
@@ -105,7 +103,7 @@ class MammotionDevices:
|
|
105
103
|
def get_device(self, mammotion_device_name: str) -> MammotionMixedDeviceManager:
|
106
104
|
return self.devices.get(mammotion_device_name)
|
107
105
|
|
108
|
-
def remove_device(self, name) -> None:
|
106
|
+
async def remove_device(self, name) -> None:
|
109
107
|
device_for_removal = self.devices.pop(name)
|
110
108
|
if device_for_removal.has_cloud():
|
111
109
|
should_disconnect = {
|
@@ -115,6 +113,11 @@ class MammotionDevices:
|
|
115
113
|
}
|
116
114
|
if len(should_disconnect) == 0:
|
117
115
|
device_for_removal.cloud()._mqtt.disconnect()
|
116
|
+
await device_for_removal.cloud().stop()
|
117
|
+
if device_for_removal.has_ble():
|
118
|
+
await device_for_removal.ble().stop()
|
119
|
+
|
120
|
+
del device_for_removal
|
118
121
|
|
119
122
|
|
120
123
|
async def create_devices(
|
@@ -164,10 +167,7 @@ class Mammotion:
|
|
164
167
|
exists: MammotionCloud | None = self.mqtt_list.get(account)
|
165
168
|
if not exists or force:
|
166
169
|
cloud_client = await self.login(account, password)
|
167
|
-
|
168
|
-
cloud_client = exists.cloud_client
|
169
|
-
|
170
|
-
await self.initiate_cloud_connection(account, cloud_client)
|
170
|
+
await self.initiate_cloud_connection(account, cloud_client)
|
171
171
|
|
172
172
|
async def initiate_cloud_connection(self, account: str, cloud_client: CloudIOTGateway) -> None:
|
173
173
|
if self.mqtt_list.get(account) is not None:
|
@@ -176,7 +176,6 @@ class Mammotion:
|
|
176
176
|
self.add_cloud_devices(self.mqtt_list.get(account))
|
177
177
|
return
|
178
178
|
|
179
|
-
self.cloud_client = cloud_client
|
180
179
|
mammotion_cloud = MammotionCloud(
|
181
180
|
MammotionMQTT(
|
182
181
|
region_id=cloud_client.region_response.data.regionId,
|
@@ -222,23 +221,22 @@ class Mammotion:
|
|
222
221
|
async def login(self, account: str, password: str) -> CloudIOTGateway:
|
223
222
|
"""Login to mammotion cloud."""
|
224
223
|
cloud_client = CloudIOTGateway()
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
await loop.run_in_executor(None, cloud_client.session_by_auth_code)
|
224
|
+
mammotion_http = await connect_http(account, password)
|
225
|
+
country_code = mammotion_http.login_info.userInformation.domainAbbreviation
|
226
|
+
_LOGGER.debug("CountryCode: " + country_code)
|
227
|
+
_LOGGER.debug("AuthCode: " + mammotion_http.login_info.authorization_code)
|
228
|
+
cloud_client.set_http(mammotion_http)
|
229
|
+
loop = asyncio.get_running_loop()
|
230
|
+
await loop.run_in_executor(
|
231
|
+
None, cloud_client.get_region, country_code, mammotion_http.login_info.authorization_code
|
232
|
+
)
|
233
|
+
await cloud_client.connect()
|
234
|
+
await cloud_client.login_by_oauth(country_code, mammotion_http.login_info.authorization_code)
|
235
|
+
await loop.run_in_executor(None, cloud_client.aep_handle)
|
236
|
+
await loop.run_in_executor(None, cloud_client.session_by_auth_code)
|
239
237
|
|
240
|
-
|
241
|
-
|
238
|
+
await loop.run_in_executor(None, cloud_client.list_binding_by_account)
|
239
|
+
return cloud_client
|
242
240
|
|
243
241
|
def remove_device(self, name: str) -> None:
|
244
242
|
self.devices.remove_device(name)
|
@@ -284,6 +282,14 @@ class Mammotion:
|
|
284
282
|
return await device.cloud().start_map_sync()
|
285
283
|
# TODO work with both with EITHER
|
286
284
|
|
285
|
+
async def get_stream_subscription(self, name: str):
|
286
|
+
device = self.get_device_by_name(name)
|
287
|
+
if self._preference is ConnectionPreference.WIFI:
|
288
|
+
if device.has_cloud():
|
289
|
+
_stream_response = await self.cloud().cloud_client.get_stream_subscription(device.cloud().iot_id)
|
290
|
+
_LOGGER.debug(_stream_response)
|
291
|
+
return _stream_response
|
292
|
+
|
287
293
|
def mower(self, name: str):
|
288
294
|
device = self.get_device_by_name(name)
|
289
295
|
if device:
|
@@ -112,6 +112,11 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
|
|
112
112
|
130, lambda: asyncio.ensure_future(self.run_periodic_sync_task())
|
113
113
|
)
|
114
114
|
|
115
|
+
async def stop(self) -> None:
|
116
|
+
"""Stop all tasks and disconnect."""
|
117
|
+
self._ble_sync_task.cancel()
|
118
|
+
await self._client.disconnect()
|
119
|
+
|
115
120
|
async def queue_command(self, key: str, **kwargs: Any) -> bytes | None:
|
116
121
|
return await self._send_command_with_args(key, **kwargs)
|
117
122
|
|
@@ -2,13 +2,15 @@ import asyncio
|
|
2
2
|
import base64
|
3
3
|
import json
|
4
4
|
import logging
|
5
|
+
from asyncio import TimerHandle
|
5
6
|
from collections import deque
|
6
7
|
from typing import Any, Awaitable, Callable, Optional, cast
|
7
8
|
|
8
9
|
import betterproto
|
9
10
|
|
10
11
|
from pymammotion import CloudIOTGateway, MammotionMQTT
|
11
|
-
from pymammotion.aliyun.
|
12
|
+
from pymammotion.aliyun.cloud_gateway import DeviceOfflineException, SetupException
|
13
|
+
from pymammotion.aliyun.model.dev_by_account_response import Device
|
12
14
|
from pymammotion.data.model.device import MowingDevice
|
13
15
|
from pymammotion.data.mqtt.event import ThingEventMessage
|
14
16
|
from pymammotion.event.event import DataEvent
|
@@ -27,12 +29,12 @@ class MammotionCloud:
|
|
27
29
|
def __init__(self, mqtt_client: MammotionMQTT, cloud_client: CloudIOTGateway) -> None:
|
28
30
|
self.cloud_client = cloud_client
|
29
31
|
self.loop = asyncio.get_event_loop()
|
30
|
-
self._ble_sync_task = None
|
31
32
|
self.is_ready = False
|
32
33
|
self.command_queue = asyncio.Queue()
|
33
34
|
self._waiting_queue = deque()
|
34
35
|
self.mqtt_message_event = DataEvent()
|
35
36
|
self.on_ready_event = DataEvent()
|
37
|
+
self.on_disconnected_event = DataEvent()
|
36
38
|
self._operation_lock = asyncio.Lock()
|
37
39
|
self._mqtt_client = mqtt_client
|
38
40
|
self._mqtt_client.on_connected = self.on_connected
|
@@ -65,6 +67,7 @@ class MammotionCloud:
|
|
65
67
|
|
66
68
|
async def on_disconnected(self) -> None:
|
67
69
|
"""Callback for when MQTT disconnects."""
|
70
|
+
await self.on_disconnected_event.data_event(None)
|
68
71
|
|
69
72
|
async def process_queue(self) -> None:
|
70
73
|
while True:
|
@@ -145,6 +148,8 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
145
148
|
def __init__(self, mqtt: MammotionCloud, cloud_device: Device, mowing_state: MowingDevice) -> None:
|
146
149
|
"""Initialize MammotionBaseCloudDevice."""
|
147
150
|
super().__init__(mowing_state, cloud_device)
|
151
|
+
self._ble_sync_task: TimerHandle | None = None
|
152
|
+
self.stopped = False
|
148
153
|
self.on_ready_callback: Optional[Callable[[], Awaitable[None]]] = None
|
149
154
|
self.loop = asyncio.get_event_loop()
|
150
155
|
self._mqtt = mqtt
|
@@ -156,6 +161,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
156
161
|
self.currentID = ""
|
157
162
|
self._mqtt.mqtt_message_event.add_subscribers(self._parse_message_for_device)
|
158
163
|
self._mqtt.on_ready_event.add_subscribers(self.on_ready)
|
164
|
+
self._mqtt.on_disconnected_event.add_subscribers(self.on_disconnect)
|
159
165
|
self.set_queue_callback(self.queue_command)
|
160
166
|
|
161
167
|
if self._mqtt.is_ready:
|
@@ -163,10 +169,32 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
163
169
|
|
164
170
|
async def on_ready(self) -> None:
|
165
171
|
"""Callback for when MQTT is subscribed to events."""
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
await self.
|
172
|
+
if self.stopped:
|
173
|
+
return
|
174
|
+
try:
|
175
|
+
await self._ble_sync()
|
176
|
+
if self._ble_sync_task is None or self._ble_sync_task.cancelled():
|
177
|
+
await self.run_periodic_sync_task()
|
178
|
+
if self.on_ready_callback:
|
179
|
+
await self.on_ready_callback()
|
180
|
+
except DeviceOfflineException:
|
181
|
+
await self.stop()
|
182
|
+
except SetupException:
|
183
|
+
await self.stop()
|
184
|
+
|
185
|
+
async def on_disconnect(self) -> None:
|
186
|
+
if self._ble_sync_task:
|
187
|
+
self._ble_sync_task.cancel()
|
188
|
+
loop = asyncio.get_event_loop()
|
189
|
+
self._mqtt.disconnect()
|
190
|
+
await loop.run_in_executor(None, self._mqtt.cloud_client.sign_out)
|
191
|
+
|
192
|
+
async def stop(self) -> None:
|
193
|
+
"""Stop all tasks and disconnect."""
|
194
|
+
if self._ble_sync_task:
|
195
|
+
self._ble_sync_task.cancel()
|
196
|
+
self._mqtt.on_ready_event.remove_subscribers(self.on_ready)
|
197
|
+
self.stopped = True
|
170
198
|
|
171
199
|
async def _ble_sync(self) -> None:
|
172
200
|
command_bytes = self._commands.send_todev_ble_sync(3)
|
@@ -176,10 +204,11 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
176
204
|
async def run_periodic_sync_task(self) -> None:
|
177
205
|
"""Send ble sync to robot."""
|
178
206
|
try:
|
179
|
-
if not self._mqtt._operation_lock.locked():
|
207
|
+
if not self._mqtt._operation_lock.locked() or not self.stopped:
|
180
208
|
await self._ble_sync()
|
181
209
|
finally:
|
182
|
-
self.
|
210
|
+
if not self.stopped:
|
211
|
+
self.schedule_ble_sync()
|
183
212
|
|
184
213
|
def schedule_ble_sync(self) -> None:
|
185
214
|
"""Periodically sync to keep connection alive."""
|
@@ -91,6 +91,9 @@ class MammotionMQTT:
|
|
91
91
|
def disconnect(self) -> None:
|
92
92
|
"""Disconnect from MQTT Server."""
|
93
93
|
logger.info("Disconnecting...")
|
94
|
+
|
95
|
+
|
96
|
+
|
94
97
|
self._linkkit_client.disconnect()
|
95
98
|
|
96
99
|
def _thing_on_thing_enable(self, user_data) -> None:
|
@@ -105,6 +108,15 @@ class MammotionMQTT:
|
|
105
108
|
self._linkkit_client.subscribe_topic(
|
106
109
|
f"/sys/{self._product_key}/{self._device_name}/app/down/thing/event/property/post_reply"
|
107
110
|
)
|
111
|
+
self._linkkit_client.subscribe_topic(
|
112
|
+
f"/sys/{self._product_key}/{self._device_name}/app/down/thing/wifi/status/notify"
|
113
|
+
)
|
114
|
+
self._linkkit_client.subscribe_topic(
|
115
|
+
f"/sys/{self._product_key}/{self._device_name}/app/down/thing/wifi/connect/event/notify"
|
116
|
+
)
|
117
|
+
self._linkkit_client.subscribe_topic(
|
118
|
+
f"/sys/{self._product_key}/{self._device_name}/app/down/_thing/event/notify"
|
119
|
+
)
|
108
120
|
self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/app/down/thing/events")
|
109
121
|
self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/app/down/thing/status")
|
110
122
|
self._linkkit_client.subscribe_topic(f"/sys/{self._product_key}/{self._device_name}/app/down/thing/properties")
|
@@ -1,17 +1,18 @@
|
|
1
1
|
pymammotion/__init__.py,sha256=jHCQrpJaG1jAoID9T4RT3g4JsZc0JpJqIcqjnA7cXd0,1605
|
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=vMNt5MquL-t7C0-yDAXQEYA37RR4cj2rdLfh5iQ131o,25561
|
4
4
|
pymammotion/aliyun/cloud_service.py,sha256=px7dUKow5Z7VyebjYzuKkzkm77XbUXYiFiYO_2e-UQ0,2207
|
5
|
-
pymammotion/aliyun/
|
6
|
-
pymammotion/aliyun/
|
7
|
-
pymammotion/aliyun/
|
8
|
-
pymammotion/aliyun/
|
9
|
-
pymammotion/aliyun/
|
10
|
-
pymammotion/aliyun/
|
5
|
+
pymammotion/aliyun/model/aep_response.py,sha256=8f6GIP58ve8gd6AL3HBoXxsy0n2q4ygWvjELGnoOnVc,452
|
6
|
+
pymammotion/aliyun/model/connect_response.py,sha256=Yz-fEbDzgGPTo5Of2oAjmFkSv08T7ze80pQU4k-gKIU,824
|
7
|
+
pymammotion/aliyun/model/dev_by_account_response.py,sha256=QZeopszFlGI7wltla-XDyGMbg5QSnb4Q1wTINkkaJyA,1026
|
8
|
+
pymammotion/aliyun/model/login_by_oauth_response.py,sha256=IXSLZ6XnOliOnyXo5Bh0ErqFjA11puACh_9NH0sSJGQ,1262
|
9
|
+
pymammotion/aliyun/model/regions_response.py,sha256=TBBWKF9j5iWjX4YbvMBHNDOnr0cEhbdfgApgCyJZhVg,651
|
10
|
+
pymammotion/aliyun/model/session_by_authcode_response.py,sha256=qW0pnnRXfyEuMbb9ORc013sYqZwO8z4i_h_SfSaA_bw,419
|
11
|
+
pymammotion/aliyun/model/stream_subscription_response.py,sha256=po765WASQDboVCosbPEfDHNlanBf-3WMrA6iV3ezooY,357
|
11
12
|
pymammotion/aliyun/tmp_constant.py,sha256=M4Hq_lrGB3LZdX6R2XohRPFoK1NDnNV-pTJwJcJ9838,6650
|
12
13
|
pymammotion/bluetooth/__init__.py,sha256=LAl8jqZ1fPh-3mLmViNQsP3s814C1vsocYUa6oSaXt0,36
|
13
14
|
pymammotion/bluetooth/ble.py,sha256=YfkfEK3TLJ8BaidjAXfUVFv8reLCu6U_lYa3Bo0pddw,2449
|
14
|
-
pymammotion/bluetooth/ble_message.py,sha256=
|
15
|
+
pymammotion/bluetooth/ble_message.py,sha256=jQ_vece7a5WH_SW3pC_sdk6_Ch84bgWrRw3BatEje0c,16188
|
15
16
|
pymammotion/bluetooth/const.py,sha256=CCqyHsYbB0BAYjwdhXt_n6eWWxmhlUrAFjvVv57mbvE,1749
|
16
17
|
pymammotion/bluetooth/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
18
|
pymammotion/bluetooth/data/convert.py,sha256=6DMwvzVr9FWCoQFIKSI2poFXjISc_m6X59g8FlVO0-o,800
|
@@ -21,8 +22,8 @@ pymammotion/const.py,sha256=lWRxvTVdXnNHuxqvRkjO5ziK0Ic-fZMM6J2dbe5M6Nc,385
|
|
21
22
|
pymammotion/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
23
|
pymammotion/data/model/__init__.py,sha256=aSyroxYQQS-WMRi6WmWm2js4wLa9nmsi160gx9tts4o,323
|
23
24
|
pymammotion/data/model/account.py,sha256=vJM-KTf2q6eBfVC-UlNHBSmJvqHiCawZ40vnuhXhaz8,140
|
24
|
-
pymammotion/data/model/device.py,sha256=
|
25
|
-
pymammotion/data/model/device_config.py,sha256=
|
25
|
+
pymammotion/data/model/device.py,sha256=s7S6Nicuk3acR8jxeeqZlVTZXB94DjYzo7gYdwjleUs,12071
|
26
|
+
pymammotion/data/model/device_config.py,sha256=RkEbnkubJQ8nXPfZpuGPe_h9mGZGtxCxDSc8mY7UCT8,2824
|
26
27
|
pymammotion/data/model/enums.py,sha256=EpKmO8yVUZyEnTY4yH0DMMVKYNQM42zpW1maUu0i3IE,1582
|
27
28
|
pymammotion/data/model/excute_boarder_params.py,sha256=9CpUqrygcle1C_1hDW-riLmm4map4ZbE842NXjcomEI,1394
|
28
29
|
pymammotion/data/model/execute_boarder.py,sha256=9rd_h4fbcsXxgnLOd2rO2hWyD1abnTGc47QTEpp8DD0,1103
|
@@ -31,19 +32,19 @@ pymammotion/data/model/hash_list.py,sha256=efUSelsj39IArxV_GEN7niM2fsVa1OL8ZjTox
|
|
31
32
|
pymammotion/data/model/location.py,sha256=PwmITejfI4pm7PI4rzqSuuHetwle6IJr_CV95435s2M,871
|
32
33
|
pymammotion/data/model/mowing_modes.py,sha256=5TrHSijUyPtIDWpNtgzx_vFQukRJWRz4gIrUaXggKPw,827
|
33
34
|
pymammotion/data/model/plan.py,sha256=mcadkSL7fQXy0iJ0q786I3GEQY4i6kmQXfW6Ri69lcQ,2906
|
34
|
-
pymammotion/data/model/rapid_state.py,sha256=
|
35
|
+
pymammotion/data/model/rapid_state.py,sha256=mIdhAG_LZXpVcybxqTLgLXkNOmVmDTn04B9PGIDA8Ls,1251
|
35
36
|
pymammotion/data/model/region_data.py,sha256=OTV15vRyn9JORXsQPjWMNF1ZujuNhsOKl25KeqwMObA,3007
|
36
|
-
pymammotion/data/model/report_info.py,sha256=
|
37
|
+
pymammotion/data/model/report_info.py,sha256=gY3L4UGEH37PS314e8d7DWuIczQxTIMfcf66xoVe0sY,2210
|
37
38
|
pymammotion/data/mqtt/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
38
39
|
pymammotion/data/mqtt/event.py,sha256=plt53w3kHulB_MfbkxK9j7AfdQ5ahVU2s4kiQMoLio4,4612
|
39
40
|
pymammotion/data/mqtt/properties.py,sha256=HkBPghr26L9_b4QaOi1DtPgb0UoPIOGSe9wb3kgnM6Y,2815
|
40
41
|
pymammotion/data/mqtt/status.py,sha256=zqnlo-MzejEQZszl0i0Wucoc3E76x6UtI9JLxoBnu54,1067
|
41
|
-
pymammotion/data/state_manager.py,sha256=
|
42
|
+
pymammotion/data/state_manager.py,sha256=sP-y5jUFpL19sRwuaaSZcdGCn8jNOJhLg5sEkOTJhEw,4104
|
42
43
|
pymammotion/event/__init__.py,sha256=mgATR6vPHACNQ-0zH5fi7NdzeTCDV1CZyaWPmtUusi8,115
|
43
44
|
pymammotion/event/event.py,sha256=UzYnxV5DfvMDK3E06UvSzvzuBbaXOOUwO6xYt_zn9To,2034
|
44
45
|
pymammotion/http/_init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
|
-
pymammotion/http/http.py,sha256=
|
46
|
-
pymammotion/http/model/http.py,sha256=
|
46
|
+
pymammotion/http/http.py,sha256=gJU8w3j8NL_m5YnxfbRxxm4y9SW0UfKr1iAaMvLsfuM,3562
|
47
|
+
pymammotion/http/model/http.py,sha256=1NJP4hkBulcnpbD7Rcwltd8Ry9lnZzQVFJN8Oo7MKeM,1706
|
47
48
|
pymammotion/mammotion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
48
49
|
pymammotion/mammotion/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
50
|
pymammotion/mammotion/commands/abstract_message.py,sha256=nw6r7694yzl7iJKqRqhLmAPRjd_TL_Xo_-JXq2_a_ug,222
|
@@ -52,20 +53,20 @@ pymammotion/mammotion/commands/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
|
|
52
53
|
pymammotion/mammotion/commands/messages/driver.py,sha256=ANIOEtjF23k7W_R_Yfgzxmc7p1KSBCF-l-tijUtw0Yg,3709
|
53
54
|
pymammotion/mammotion/commands/messages/media.py,sha256=ps0l06CXy5Ej--gTNCsyKttwo7yHLVrJUpn-wNJYecs,1150
|
54
55
|
pymammotion/mammotion/commands/messages/navigation.py,sha256=4rXBL-mViWc38K6x1w5O-GjwV8UWS5xZXkf4aHYjs8A,23684
|
55
|
-
pymammotion/mammotion/commands/messages/network.py,sha256=
|
56
|
+
pymammotion/mammotion/commands/messages/network.py,sha256=61msRJWyXdrO8FbI_rhrO2K8R1qkGVUj5BFzNwm7lwg,8155
|
56
57
|
pymammotion/mammotion/commands/messages/ota.py,sha256=XkeuWBZtpYMMBze6r8UN7dJXbe2FxUNGNnjwBpXJKM0,1240
|
57
|
-
pymammotion/mammotion/commands/messages/system.py,sha256=
|
58
|
+
pymammotion/mammotion/commands/messages/system.py,sha256=XoDvnERAMXIj6oK6l-P7kVBmloaIOAf9flCq0Q6vryg,12399
|
58
59
|
pymammotion/mammotion/commands/messages/video.py,sha256=_8lJsU4sLm2CGnc7RDkueA0A51Ysui6x7SqFnhX8O2g,1007
|
59
60
|
pymammotion/mammotion/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
61
|
pymammotion/mammotion/control/joystick.py,sha256=QfBVxM_gxpWsZAGO90whtgxCI2tIZ3TTad9wHIPsU9s,5640
|
61
62
|
pymammotion/mammotion/devices/__init__.py,sha256=f2qQFPgLGmV85W2hSlMUh5BYuht9o_Ar_JEAAMD4fsE,102
|
62
|
-
pymammotion/mammotion/devices/base.py,sha256=
|
63
|
-
pymammotion/mammotion/devices/mammotion.py,sha256=
|
64
|
-
pymammotion/mammotion/devices/mammotion_bluetooth.py,sha256=
|
65
|
-
pymammotion/mammotion/devices/mammotion_cloud.py,sha256=
|
63
|
+
pymammotion/mammotion/devices/base.py,sha256=53of3422C6g0jiKX0X4uo-eRI1Du31NA4RWLdw2O5u8,11131
|
64
|
+
pymammotion/mammotion/devices/mammotion.py,sha256=od4crY_1hbBwlDt5St_IV_QT0jOT_tlKv_iplf_CTu4,11966
|
65
|
+
pymammotion/mammotion/devices/mammotion_bluetooth.py,sha256=Lu8-85fBa-rGzssARqiydfQbPuIeeTcV-1ROIoYBKKM,17512
|
66
|
+
pymammotion/mammotion/devices/mammotion_cloud.py,sha256=H_O3LrEOSlKm1JB8AoF5of7olyyiDhZAzD2cBXULUlo,11317
|
66
67
|
pymammotion/mqtt/__init__.py,sha256=Ocs5e-HLJvTuDpVXyECEsWIvwsUaxzj7lZ9mSYutNDY,105
|
67
68
|
pymammotion/mqtt/mammotion_future.py,sha256=_OWqKOlUGl2yT1xOsXFQYpGd-1zQ63OxqXgy7KRQgYc,710
|
68
|
-
pymammotion/mqtt/mammotion_mqtt.py,sha256
|
69
|
+
pymammotion/mqtt/mammotion_mqtt.py,sha256=-n_GryLoyl-ioQUQRnth4syToh8MwWOj-pt2p41J0xQ,8750
|
69
70
|
pymammotion/proto/__init__.py,sha256=v3_P1LOsYRBN9qCAhaWrWMX7GWHrox9XD3hdZQxcPZM,190
|
70
71
|
pymammotion/proto/basestation.proto,sha256=_x5gAz3FkZXS1jtq4GgZgaDCuRU-UV-7HTFdsfQ3zbo,1034
|
71
72
|
pymammotion/proto/basestation.py,sha256=js64_N2xQYRxWPRdVNEapO0qe7vBlfYnjW5sE8hi7hw,2026
|
@@ -117,7 +118,7 @@ pymammotion/utility/map.py,sha256=GYscVMg2cX3IPlNpCBNHDW0S55yS1WGRf1iHnNZ7TfQ,22
|
|
117
118
|
pymammotion/utility/movement.py,sha256=N75oAoAgFydqoaOedYIxGUHmuTCtPzAOtb-d_29tpfI,615
|
118
119
|
pymammotion/utility/periodic.py,sha256=MbeSb9cfhxzYmdT_RiE0dZe3H9IfbQW_zSqhmSX2RUc,3321
|
119
120
|
pymammotion/utility/rocker_util.py,sha256=6tX7sS87qoQC_tsxbx3NLL-HgS08wtzXiZkhDiz7uo0,7179
|
120
|
-
pymammotion-0.2.
|
121
|
-
pymammotion-0.2.
|
122
|
-
pymammotion-0.2.
|
123
|
-
pymammotion-0.2.
|
121
|
+
pymammotion-0.2.43.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
122
|
+
pymammotion-0.2.43.dist-info/METADATA,sha256=EG9s9oLqTmk_dzxkCpAAYxrCw9XP29gH_2N3L9yL_jM,4052
|
123
|
+
pymammotion-0.2.43.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
124
|
+
pymammotion-0.2.43.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|