pymammotion 0.2.35__py3-none-any.whl → 0.2.36__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 +1 -0
- pymammotion/data/state_manager.py +6 -3
- pymammotion/mammotion/commands/messages/system.py +53 -41
- pymammotion/mammotion/devices/base.py +4 -1
- pymammotion/mammotion/devices/mammotion.py +76 -34
- pymammotion/mammotion/devices/mammotion_bluetooth.py +1 -0
- pymammotion/mammotion/devices/mammotion_cloud.py +8 -2
- pymammotion/utility/device_type.py +0 -1
- {pymammotion-0.2.35.dist-info → pymammotion-0.2.36.dist-info}/METADATA +1 -1
- {pymammotion-0.2.35.dist-info → pymammotion-0.2.36.dist-info}/RECORD +12 -12
- {pymammotion-0.2.35.dist-info → pymammotion-0.2.36.dist-info}/LICENSE +0 -0
- {pymammotion-0.2.35.dist-info → pymammotion-0.2.36.dist-info}/WHEEL +0 -0
@@ -586,6 +586,7 @@ class CloudIOTGateway:
|
|
586
586
|
request=request,
|
587
587
|
version="1.0",
|
588
588
|
)
|
589
|
+
logger.debug(self.converter.printBase64Binary(command))
|
589
590
|
|
590
591
|
# send request
|
591
592
|
response = client.do_request("/thing/service/invoke", "https", "POST", None, body, RuntimeOptions())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Manage state from notifications into MowingDevice."""
|
2
2
|
|
3
|
-
from typing import Awaitable, Callable, Optional
|
3
|
+
from typing import Any, Awaitable, Callable, Optional
|
4
4
|
|
5
5
|
import betterproto
|
6
6
|
|
@@ -19,6 +19,7 @@ class StateManager:
|
|
19
19
|
self.gethash_ack_callback: Optional[Callable[[NavGetHashListAck], Awaitable[None]]] = None
|
20
20
|
self.get_commondata_ack_callback: Optional[Callable[[NavGetCommDataAck], Awaitable[None]]] = None
|
21
21
|
self.on_notification_callback: Optional[Callable[[], Awaitable[None]]] = None
|
22
|
+
self.queue_command_callback: Optional[Callable[[str, dict[str, Any]], Awaitable[bytes]]] = None
|
22
23
|
|
23
24
|
def get_device(self) -> MowingDevice:
|
24
25
|
"""Get device."""
|
@@ -36,7 +37,7 @@ class StateManager:
|
|
36
37
|
case "nav":
|
37
38
|
await self._update_nav_data(message)
|
38
39
|
case "sys":
|
39
|
-
self._update_sys_data(message)
|
40
|
+
await self._update_sys_data(message)
|
40
41
|
case "driver":
|
41
42
|
self._update_driver_data(message)
|
42
43
|
case "net":
|
@@ -66,7 +67,7 @@ class StateManager:
|
|
66
67
|
hash_names: AppGetAllAreaHashName = nav_msg[1]
|
67
68
|
self._device.map.area_name = hash_names.hashnames
|
68
69
|
|
69
|
-
def _update_sys_data(self, message) -> None:
|
70
|
+
async def _update_sys_data(self, message) -> None:
|
70
71
|
"""Update system."""
|
71
72
|
sys_msg = betterproto.which_one_of(message.sys, "SubSysMsg")
|
72
73
|
match sys_msg[0]:
|
@@ -74,6 +75,8 @@ class StateManager:
|
|
74
75
|
self._device.buffer(sys_msg[1])
|
75
76
|
case "toapp_report_data":
|
76
77
|
self._device.update_report_data(sys_msg[1])
|
78
|
+
if self.queue_command_callback:
|
79
|
+
await self.queue_command_callback("get_report_cfg", stop=True)
|
77
80
|
case "mow_to_app_info":
|
78
81
|
self._device.mow_info(sys_msg[1])
|
79
82
|
case "system_tard_state_tunnel":
|
@@ -4,8 +4,20 @@ from abc import ABC
|
|
4
4
|
|
5
5
|
from pymammotion.mammotion.commands.abstract_message import AbstractMessage
|
6
6
|
from pymammotion.mammotion.commands.messages.navigation import MessageNavigation
|
7
|
-
from pymammotion.proto import
|
8
|
-
from pymammotion.proto.mctrl_sys import
|
7
|
+
from pymammotion.proto.luba_msg import LubaMsg, MsgAttr, MsgCmdType, MsgDevice
|
8
|
+
from pymammotion.proto.mctrl_sys import (
|
9
|
+
DeviceProductTypeInfoT,
|
10
|
+
LoraCfgReq,
|
11
|
+
MctlSys,
|
12
|
+
MCtrlSimulationCmdData,
|
13
|
+
ReportInfoCfg,
|
14
|
+
RptAct,
|
15
|
+
RptInfoType,
|
16
|
+
SysCommCmd,
|
17
|
+
SysKnifeControl,
|
18
|
+
SysSetDateTime,
|
19
|
+
TimeCtrlLight,
|
20
|
+
)
|
9
21
|
from pymammotion.utility.device_type import DeviceType
|
10
22
|
|
11
23
|
|
@@ -13,37 +25,35 @@ class MessageSystem(AbstractMessage, ABC):
|
|
13
25
|
messageNavigation: MessageNavigation = MessageNavigation()
|
14
26
|
|
15
27
|
def send_order_msg_sys(self, sys):
|
16
|
-
luba_msg =
|
17
|
-
msgtype=
|
18
|
-
sender=
|
19
|
-
rcver=
|
28
|
+
luba_msg = LubaMsg(
|
29
|
+
msgtype=MsgCmdType.MSG_CMD_TYPE_EMBED_SYS,
|
30
|
+
sender=MsgDevice.DEV_MOBILEAPP,
|
31
|
+
rcver=MsgDevice.DEV_MAINCTL,
|
20
32
|
sys=sys,
|
21
33
|
)
|
22
34
|
|
23
35
|
return luba_msg.SerializeToString()
|
24
36
|
|
25
37
|
def reset_system(self):
|
26
|
-
build =
|
38
|
+
build = MctlSys(todev_reset_system=1)
|
27
39
|
print("Send command - send factory reset")
|
28
40
|
return self.send_order_msg_sys(build)
|
29
41
|
|
30
42
|
def set_blade_control(self, on_off: int):
|
31
|
-
mctlsys =
|
32
|
-
sys_knife_control =
|
43
|
+
mctlsys = MctlSys()
|
44
|
+
sys_knife_control = SysKnifeControl()
|
33
45
|
sys_knife_control.knife_status = on_off
|
34
|
-
mctlsys.todev_knife_ctrl
|
46
|
+
mctlsys.todev_knife_ctrl = sys_knife_control
|
35
47
|
|
36
48
|
return self.send_order_msg_sys(mctlsys)
|
37
49
|
|
38
50
|
def get_device_product_model(self):
|
39
|
-
return self.send_order_msg_sys(
|
40
|
-
mctrl_sys_pb2.MctlSys(device_product_type_info=mctrl_sys_pb2.device_product_type_info_t())
|
41
|
-
)
|
51
|
+
return self.send_order_msg_sys(MctlSys(device_product_type_info=DeviceProductTypeInfoT()))
|
42
52
|
|
43
53
|
def read_and_set_sidelight(self, is_sidelight: bool, operate: int):
|
44
54
|
"""Read state of sidelight as well as set it."""
|
45
55
|
if is_sidelight:
|
46
|
-
build =
|
56
|
+
build = TimeCtrlLight(
|
47
57
|
operate=operate,
|
48
58
|
enable=0,
|
49
59
|
action=0,
|
@@ -53,7 +63,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
53
63
|
end_min=0,
|
54
64
|
)
|
55
65
|
else:
|
56
|
-
build =
|
66
|
+
build = TimeCtrlLight(
|
57
67
|
operate=operate,
|
58
68
|
enable=1,
|
59
69
|
action=0,
|
@@ -64,16 +74,16 @@ class MessageSystem(AbstractMessage, ABC):
|
|
64
74
|
)
|
65
75
|
print(f"Send read and write sidelight command is_sidelight:{
|
66
76
|
is_sidelight}, operate:{operate}")
|
67
|
-
build2 =
|
77
|
+
build2 = MctlSys(todev_time_ctrl_light=build)
|
68
78
|
print(f"Send command - send read and write sidelight command is_sidelight:{
|
69
79
|
is_sidelight}, operate:{operate}, timeCtrlLight:{build}")
|
70
80
|
return self.send_order_msg_sys(build2)
|
71
81
|
|
72
82
|
def test_tool_order_to_sys(self, sub_cmd: int, param_id: int, param_value: list[int]):
|
73
|
-
build =
|
83
|
+
build = MCtrlSimulationCmdData(sub_cmd=sub_cmd, param_id=param_id, param_value=param_value)
|
74
84
|
print(f"Send tool test command: subCmd={sub_cmd}, param_id:{
|
75
85
|
param_id}, param_value={param_value}")
|
76
|
-
build2 =
|
86
|
+
build2 = MctlSys(simulation_cmd=build)
|
77
87
|
print(f"Send tool test command: subCmd={sub_cmd}, param_id:{
|
78
88
|
param_id}, param_value={param_value}")
|
79
89
|
return self.send_order_msg_sys(build2)
|
@@ -81,15 +91,13 @@ class MessageSystem(AbstractMessage, ABC):
|
|
81
91
|
def read_and_set_rtk_paring_code(self, op: int, cgf: str):
|
82
92
|
print(f"Send read and write base station configuration quality op:{
|
83
93
|
op}, cgf:{cgf}")
|
84
|
-
return self.send_order_msg_sys(
|
85
|
-
mctrl_sys_pb2.MctlSys(todev_lora_cfg_req=mctrl_sys_pb2.LoraCfgReq(op=op, cfg=cgf))
|
86
|
-
)
|
94
|
+
return self.send_order_msg_sys(MctlSys(todev_lora_cfg_req=LoraCfgReq(op=op, cfg=cgf)))
|
87
95
|
|
88
96
|
def allpowerfull_rw(self, id: int, context: int, rw: int):
|
89
97
|
if (id == 6 or id == 3 or id == 7) and DeviceType.is_luba_2(self.get_device_name()):
|
90
98
|
self.messageNavigation.allpowerfull_rw_adapter_x3(id, context, rw)
|
91
99
|
return
|
92
|
-
build =
|
100
|
+
build = MctlSys(bidire_comm_cmd=SysCommCmd(id=id, context=context, rw=rw))
|
93
101
|
print(f"Send command - 9 general read and write command id={id}, context={context}, rw={rw}")
|
94
102
|
if id == 5:
|
95
103
|
# This logic doesnt make snese, but its what they had so..
|
@@ -98,7 +106,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
98
106
|
|
99
107
|
# Commented out as not needed and too many refs to try fix up
|
100
108
|
# def factory_test_order(self, test_id: int, test_duration: int, expect: str):
|
101
|
-
# new_builder =
|
109
|
+
# new_builder = mow_to_app_qctools_info_t.Builder()
|
102
110
|
# print(f"Factory tool print, expect={expect}")
|
103
111
|
# if not expect:
|
104
112
|
# build = new_builder.set_type_value(
|
@@ -108,7 +116,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
108
116
|
# json_array = json.loads(expect)
|
109
117
|
# z2 = True
|
110
118
|
# for i in range(len(json_array)):
|
111
|
-
# new_builder2 =
|
119
|
+
# new_builder2 = QCAppTestExcept.Builder()
|
112
120
|
# json_object = json_array[i]
|
113
121
|
# if "except_type" in json_object:
|
114
122
|
# string = json_object["except_type"]
|
@@ -116,7 +124,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
116
124
|
# json_array2 = json_object["conditions"]
|
117
125
|
# for i2 in range(len(json_array2)):
|
118
126
|
# json_object2 = json_array2[i2]
|
119
|
-
# new_builder3 =
|
127
|
+
# new_builder3 = QCAppTestConditions.Builder()
|
120
128
|
# if "cond_type" in json_object2:
|
121
129
|
# new_builder3.set_cond_type(
|
122
130
|
# json_object2["cond_type"])
|
@@ -151,7 +159,7 @@ class MessageSystem(AbstractMessage, ABC):
|
|
151
159
|
# test_id).set_time_of_duration(test_duration).build()
|
152
160
|
# print(f"Factory tool print, mow_to_app_qctools_info_t={
|
153
161
|
# build.except_count}, mow_to_app_qctools_info_t22={build.except_list}")
|
154
|
-
# build2 =
|
162
|
+
# build2 = MctlSys(mow_to_app_qctools_info=build)
|
155
163
|
# print(f"Send command - factory tool test command testId={
|
156
164
|
# test_id}, testDuration={test_duration}", "Factory tool print222", True)
|
157
165
|
# return self.send_order_msg_sys(build2)
|
@@ -169,8 +177,8 @@ class MessageSystem(AbstractMessage, ABC):
|
|
169
177
|
i9 = 1 if calendar.dst() else 0
|
170
178
|
print(f"Print time zone, time zone={
|
171
179
|
i8}, daylight saving time={i9} week={i4}")
|
172
|
-
build =
|
173
|
-
todev_data_time=
|
180
|
+
build = MctlSys(
|
181
|
+
todev_data_time=SysSetDateTime(
|
174
182
|
year=i,
|
175
183
|
month=i2,
|
176
184
|
date=i3,
|
@@ -191,21 +199,21 @@ class MessageSystem(AbstractMessage, ABC):
|
|
191
199
|
return self.send_order_msg_sys(build)
|
192
200
|
|
193
201
|
def get_device_version_info(self):
|
194
|
-
return self.send_order_msg_sys(
|
202
|
+
return self.send_order_msg_sys(MctlSys(todev_get_dev_fw_info=1))
|
195
203
|
|
196
204
|
# === sendOrderMsg_Sys2 ===
|
197
205
|
|
198
206
|
def request_iot_sys(
|
199
207
|
self,
|
200
|
-
rpt_act:
|
208
|
+
rpt_act: RptAct,
|
201
209
|
rpt_info_type: list[RptInfoType | str] | None,
|
202
210
|
timeout: int,
|
203
211
|
period: int,
|
204
212
|
no_change_period: int,
|
205
213
|
count: int,
|
206
214
|
) -> bytes:
|
207
|
-
build =
|
208
|
-
todev_report_cfg=
|
215
|
+
build = MctlSys(
|
216
|
+
todev_report_cfg=ReportInfoCfg(
|
209
217
|
act=rpt_act,
|
210
218
|
sub=rpt_info_type,
|
211
219
|
timeout=timeout,
|
@@ -218,9 +226,12 @@ class MessageSystem(AbstractMessage, ABC):
|
|
218
226
|
build.todev_report_cfg.act} {build}")
|
219
227
|
return self.send_order_msg_sys(build)
|
220
228
|
|
221
|
-
def get_report_cfg(
|
222
|
-
|
223
|
-
|
229
|
+
def get_report_cfg(
|
230
|
+
self, timeout: int = 10000, period: int = 1000, no_change_period: int = 1000, stop: bool = False
|
231
|
+
):
|
232
|
+
mctlsys = MctlSys(
|
233
|
+
todev_report_cfg=ReportInfoCfg(
|
234
|
+
act=RptAct.RPT_STOP if stop else RptAct.RPT_START,
|
224
235
|
timeout=timeout,
|
225
236
|
period=period,
|
226
237
|
no_change_period=no_change_period,
|
@@ -233,17 +244,18 @@ class MessageSystem(AbstractMessage, ABC):
|
|
233
244
|
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_DEV_LOCAL.value)
|
234
245
|
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_WORK.value)
|
235
246
|
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_DEV_STA.value)
|
247
|
+
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_MAINTAIN.value)
|
236
248
|
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VISION_POINT.value)
|
237
249
|
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VIO.value)
|
238
250
|
mctlsys.todev_report_cfg.sub.append(RptInfoType.RIT_VISION_STATISTIC.value)
|
239
251
|
|
240
|
-
lubaMsg =
|
241
|
-
lubaMsg.msgtype =
|
242
|
-
lubaMsg.sender =
|
243
|
-
lubaMsg.rcver =
|
244
|
-
lubaMsg.msgattr =
|
252
|
+
lubaMsg = LubaMsg()
|
253
|
+
lubaMsg.msgtype = MsgCmdType.MSG_CMD_TYPE_EMBED_SYS
|
254
|
+
lubaMsg.sender = MsgDevice.DEV_MOBILEAPP
|
255
|
+
lubaMsg.rcver = MsgDevice.DEV_MAINCTL
|
256
|
+
lubaMsg.msgattr = MsgAttr.MSG_ATTR_REQ
|
245
257
|
lubaMsg.seqs = 1
|
246
258
|
lubaMsg.version = 1
|
247
259
|
lubaMsg.subtype = 1
|
248
|
-
lubaMsg.sys
|
260
|
+
lubaMsg.sys = mctlsys
|
249
261
|
return lubaMsg.SerializeToString()
|
@@ -18,7 +18,7 @@ from pymammotion.utility.movement import get_percent, transform_both_speeds
|
|
18
18
|
_LOGGER = logging.getLogger(__name__)
|
19
19
|
|
20
20
|
|
21
|
-
def find_next_integer(lst: list[int], current_hash:
|
21
|
+
def find_next_integer(lst: list[int], current_hash: int) -> int | None:
|
22
22
|
try:
|
23
23
|
# Find the index of the current integer
|
24
24
|
current_index = lst.index(current_hash)
|
@@ -54,6 +54,9 @@ class MammotionBaseDevice:
|
|
54
54
|
def set_notification_callback(self, func: Callable[[], Awaitable[None]]) -> None:
|
55
55
|
self._state_manager.on_notification_callback = func
|
56
56
|
|
57
|
+
def set_queue_callback(self, func: Callable[[str, dict[str, Any]], Awaitable[bytes]]) -> None:
|
58
|
+
self._state_manager.queue_command_callback = func
|
59
|
+
|
57
60
|
async def datahash_response(self, hash_ack: NavGetHashListAck) -> None:
|
58
61
|
"""Handle datahash responses."""
|
59
62
|
await self.queue_command("synchronize_hash_data", hash_num=hash_ack.data_couple[0])
|
@@ -37,7 +37,8 @@ class ConnectionPreference(Enum):
|
|
37
37
|
class MammotionMixedDeviceManager:
|
38
38
|
_ble_device: MammotionBaseBLEDevice | None = None
|
39
39
|
_cloud_device: MammotionBaseCloudDevice | None = None
|
40
|
-
_mowing_state: MowingDevice
|
40
|
+
_mowing_state: MowingDevice
|
41
|
+
preference: ConnectionPreference
|
41
42
|
|
42
43
|
def __init__(
|
43
44
|
self,
|
@@ -45,10 +46,13 @@ class MammotionMixedDeviceManager:
|
|
45
46
|
cloud_device: Device | None = None,
|
46
47
|
ble_device: BLEDevice | None = None,
|
47
48
|
mqtt: MammotionCloud | None = None,
|
49
|
+
preference: ConnectionPreference = ConnectionPreference.BLUETOOTH,
|
48
50
|
) -> None:
|
49
51
|
self.name = name
|
52
|
+
self._mowing_state = MowingDevice()
|
50
53
|
self.add_ble(ble_device)
|
51
54
|
self.add_cloud(cloud_device, mqtt)
|
55
|
+
self.preference = preference
|
52
56
|
|
53
57
|
def mower_state(self):
|
54
58
|
return self._mowing_state
|
@@ -101,19 +105,30 @@ class MammotionDevices:
|
|
101
105
|
def get_device(self, mammotion_device_name: str) -> MammotionMixedDeviceManager:
|
102
106
|
return self.devices.get(mammotion_device_name)
|
103
107
|
|
108
|
+
def remove_device(self, name) -> None:
|
109
|
+
device_for_removal = self.devices.pop(name)
|
110
|
+
if device_for_removal.has_cloud():
|
111
|
+
should_disconnect = {
|
112
|
+
device
|
113
|
+
for key, device in self.devices.items()
|
114
|
+
if device.cloud() is not None and device.cloud()._mqtt == device_for_removal.cloud()._mqtt
|
115
|
+
}
|
116
|
+
if len(should_disconnect) == 0:
|
117
|
+
device_for_removal.cloud()._mqtt.disconnect()
|
118
|
+
|
104
119
|
|
105
120
|
async def create_devices(
|
106
121
|
ble_device: BLEDevice,
|
107
122
|
cloud_credentials: Credentials | None = None,
|
108
123
|
preference: ConnectionPreference = ConnectionPreference.BLUETOOTH,
|
109
124
|
):
|
110
|
-
mammotion = Mammotion(
|
125
|
+
mammotion = Mammotion()
|
126
|
+
mammotion.add_ble_device(ble_device, preference)
|
111
127
|
|
112
128
|
if cloud_credentials and preference == ConnectionPreference.EITHER or preference == ConnectionPreference.WIFI:
|
113
|
-
|
129
|
+
await mammotion.login_and_initiate_cloud(
|
114
130
|
cloud_credentials.account_id or cloud_credentials.email, cloud_credentials.password
|
115
131
|
)
|
116
|
-
await mammotion.initiate_cloud_connection(cloud_client)
|
117
132
|
|
118
133
|
return mammotion
|
119
134
|
|
@@ -123,26 +138,44 @@ class Mammotion:
|
|
123
138
|
"""Represents a Mammotion account and its devices."""
|
124
139
|
|
125
140
|
devices = MammotionDevices()
|
126
|
-
|
127
|
-
mqtt: MammotionCloud | None = None
|
141
|
+
mqtt_list: dict[str, MammotionCloud] = dict()
|
128
142
|
|
129
|
-
|
130
|
-
|
131
|
-
|
143
|
+
_instance = None
|
144
|
+
|
145
|
+
def __new__(cls, *args, **kwargs):
|
146
|
+
if not cls._instance:
|
147
|
+
cls._instance = super().__new__(cls)
|
148
|
+
return cls._instance
|
149
|
+
|
150
|
+
def __init__(self) -> None:
|
132
151
|
"""Initialize MammotionDevice."""
|
133
|
-
|
134
|
-
self.devices.add_device(MammotionMixedDeviceManager(name=ble_device.name, ble_device=ble_device))
|
152
|
+
self._login_lock = asyncio.Lock()
|
135
153
|
|
136
|
-
|
137
|
-
|
154
|
+
def add_ble_device(self, ble_device: BLEDevice, preference: ConnectionPreference = ConnectionPreference.BLUETOOTH) -> None:
|
155
|
+
if ble_device:
|
156
|
+
self.devices.add_device(
|
157
|
+
MammotionMixedDeviceManager(name=ble_device.name, ble_device=ble_device, preference=preference)
|
158
|
+
)
|
138
159
|
|
139
|
-
async def
|
140
|
-
|
141
|
-
|
160
|
+
async def login_and_initiate_cloud(self, account, password, force: bool = False) -> None:
|
161
|
+
async with self._login_lock:
|
162
|
+
exists: MammotionCloud | None = self.mqtt_list.get(account)
|
163
|
+
if not exists or force:
|
164
|
+
cloud_client = await self.login(account, password)
|
165
|
+
else:
|
166
|
+
cloud_client = exists.cloud_client
|
167
|
+
|
168
|
+
await self.initiate_cloud_connection(account, cloud_client)
|
169
|
+
|
170
|
+
async def initiate_cloud_connection(self, account: str, cloud_client: CloudIOTGateway) -> None:
|
171
|
+
if self.mqtt_list.get(account) is not None:
|
172
|
+
if self.mqtt_list.get(account).is_connected:
|
173
|
+
# we might have removed a device so readd
|
174
|
+
self.add_cloud_devices(self.mqtt_list.get(account))
|
142
175
|
return
|
143
176
|
|
144
177
|
self.cloud_client = cloud_client
|
145
|
-
self.
|
178
|
+
self.mqtt_list[account] = MammotionCloud(
|
146
179
|
MammotionMQTT(
|
147
180
|
region_id=cloud_client.region_response.data.regionId,
|
148
181
|
product_key=cloud_client.aep_response.data.productKey,
|
@@ -151,23 +184,30 @@ class Mammotion:
|
|
151
184
|
iot_token=cloud_client.session_by_authcode_response.data.iotToken,
|
152
185
|
client_id=cloud_client.client_id,
|
153
186
|
cloud_client=cloud_client,
|
154
|
-
)
|
187
|
+
),
|
188
|
+
cloud_client,
|
155
189
|
)
|
156
190
|
|
157
191
|
loop = asyncio.get_running_loop()
|
158
|
-
await loop.run_in_executor(None, self.
|
192
|
+
await loop.run_in_executor(None, self.mqtt_list[account].connect_async)
|
159
193
|
|
160
|
-
|
194
|
+
def add_cloud_devices(self, mqtt_client: MammotionCloud) -> None:
|
195
|
+
for device in mqtt_client.cloud_client.devices_by_account_response.data.data:
|
161
196
|
mower_device = self.devices.get_device(device.deviceName)
|
162
197
|
if device.deviceName.startswith(("Luba-", "Yuka-")) and mower_device is None:
|
163
198
|
self.devices.add_device(
|
164
|
-
MammotionMixedDeviceManager(
|
199
|
+
MammotionMixedDeviceManager(
|
200
|
+
name=device.deviceName,
|
201
|
+
cloud_device=device,
|
202
|
+
mqtt=mqtt_client,
|
203
|
+
preference=ConnectionPreference.WIFI,
|
204
|
+
)
|
165
205
|
)
|
166
206
|
elif device.deviceName.startswith(("Luba-", "Yuka-")) and mower_device:
|
167
207
|
if mower_device.cloud() is None:
|
168
|
-
mower_device.add_cloud(cloud_device=device, mqtt=
|
169
|
-
|
170
|
-
|
208
|
+
mower_device.add_cloud(cloud_device=device, mqtt=mqtt_client)
|
209
|
+
elif mqtt_client != mower_device.cloud().mqtt:
|
210
|
+
mower_device.replace_mqtt(mqtt_client)
|
171
211
|
|
172
212
|
def set_disconnect_strategy(self, disconnect: bool) -> None:
|
173
213
|
for device_name, device in self.devices.devices:
|
@@ -175,8 +215,7 @@ class Mammotion:
|
|
175
215
|
ble_device: MammotionBaseBLEDevice = device.ble()
|
176
216
|
ble_device.set_disconnect_strategy(disconnect)
|
177
217
|
|
178
|
-
|
179
|
-
async def login(account: str, password: str) -> CloudIOTGateway:
|
218
|
+
async def login(self, account: str, password: str) -> CloudIOTGateway:
|
180
219
|
"""Login to mammotion cloud."""
|
181
220
|
cloud_client = CloudIOTGateway()
|
182
221
|
async with ClientSession(MAMMOTION_DOMAIN) as session:
|
@@ -197,6 +236,9 @@ class Mammotion:
|
|
197
236
|
await loop.run_in_executor(None, cloud_client.list_binding_by_account)
|
198
237
|
return cloud_client
|
199
238
|
|
239
|
+
def remove_device(self, name: str) -> None:
|
240
|
+
self.devices.remove_device(name)
|
241
|
+
|
200
242
|
def get_device_by_name(self, name: str) -> MammotionMixedDeviceManager:
|
201
243
|
return self.devices.get_device(name)
|
202
244
|
|
@@ -204,9 +246,9 @@ class Mammotion:
|
|
204
246
|
"""Send a command to the device."""
|
205
247
|
device = self.get_device_by_name(name)
|
206
248
|
if device:
|
207
|
-
if
|
249
|
+
if device.preference is ConnectionPreference.BLUETOOTH:
|
208
250
|
return await device.ble().command(key)
|
209
|
-
if
|
251
|
+
if device.preference is ConnectionPreference.WIFI:
|
210
252
|
return await device.cloud().command(key)
|
211
253
|
# TODO work with both with EITHER
|
212
254
|
|
@@ -214,27 +256,27 @@ class Mammotion:
|
|
214
256
|
"""Send a command with args to the device."""
|
215
257
|
device = self.get_device_by_name(name)
|
216
258
|
if device:
|
217
|
-
if
|
259
|
+
if device.preference is ConnectionPreference.BLUETOOTH:
|
218
260
|
return await device.ble().command(key, **kwargs)
|
219
|
-
if
|
261
|
+
if device.preference is ConnectionPreference.WIFI:
|
220
262
|
return await device.cloud().command(key, **kwargs)
|
221
263
|
# TODO work with both with EITHER
|
222
264
|
|
223
265
|
async def start_sync(self, name: str, retry: int):
|
224
266
|
device = self.get_device_by_name(name)
|
225
267
|
if device:
|
226
|
-
if
|
268
|
+
if device.preference is ConnectionPreference.BLUETOOTH:
|
227
269
|
return await device.ble().start_sync(retry)
|
228
|
-
if
|
270
|
+
if device.preference is ConnectionPreference.WIFI:
|
229
271
|
return await device.cloud().start_sync(retry)
|
230
272
|
# TODO work with both with EITHER
|
231
273
|
|
232
274
|
async def start_map_sync(self, name: str):
|
233
275
|
device = self.get_device_by_name(name)
|
234
276
|
if device:
|
235
|
-
if
|
277
|
+
if device.preference is ConnectionPreference.BLUETOOTH:
|
236
278
|
return await device.ble().start_map_sync()
|
237
|
-
if
|
279
|
+
if device.preference is ConnectionPreference.WIFI:
|
238
280
|
return await device.cloud().start_map_sync()
|
239
281
|
# TODO work with both with EITHER
|
240
282
|
|
@@ -88,6 +88,7 @@ class MammotionBaseBLEDevice(MammotionBaseDevice):
|
|
88
88
|
self._connect_lock = asyncio.Lock()
|
89
89
|
self._operation_lock = asyncio.Lock()
|
90
90
|
self._key: str | None = None
|
91
|
+
self.set_queue_callback(self.queue_command)
|
91
92
|
|
92
93
|
def update_device(self, device: BLEDevice) -> None:
|
93
94
|
"""Update the BLE device."""
|
@@ -7,7 +7,7 @@ from typing import Any, Awaitable, Callable, Optional, cast
|
|
7
7
|
|
8
8
|
import betterproto
|
9
9
|
|
10
|
-
from pymammotion import MammotionMQTT
|
10
|
+
from pymammotion import CloudIOTGateway, MammotionMQTT
|
11
11
|
from pymammotion.aliyun.dataclass.dev_by_account_response import Device
|
12
12
|
from pymammotion.data.model.device import MowingDevice
|
13
13
|
from pymammotion.data.mqtt.event import ThingEventMessage
|
@@ -24,7 +24,8 @@ _LOGGER = logging.getLogger(__name__)
|
|
24
24
|
class MammotionCloud:
|
25
25
|
"""Per account MQTT cloud."""
|
26
26
|
|
27
|
-
def __init__(self, mqtt_client: MammotionMQTT) -> None:
|
27
|
+
def __init__(self, mqtt_client: MammotionMQTT, cloud_client: CloudIOTGateway) -> None:
|
28
|
+
self.cloud_client = cloud_client
|
28
29
|
self.loop = asyncio.get_event_loop()
|
29
30
|
self._ble_sync_task = None
|
30
31
|
self.is_ready = False
|
@@ -155,6 +156,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
155
156
|
self.currentID = ""
|
156
157
|
self._mqtt.mqtt_message_event.add_subscribers(self._parse_message_for_device)
|
157
158
|
self._mqtt.on_ready_event.add_subscribers(self.on_ready)
|
159
|
+
self.set_queue_callback(self.queue_command)
|
158
160
|
|
159
161
|
if self._mqtt.is_ready:
|
160
162
|
self.run_periodic_sync_task()
|
@@ -244,3 +246,7 @@ class MammotionBaseCloudDevice(MammotionBaseDevice):
|
|
244
246
|
if not fut.fut.cancelled():
|
245
247
|
fut.resolve(cast(bytes, binary_data))
|
246
248
|
await self._state_manager.notification(new_msg)
|
249
|
+
|
250
|
+
@property
|
251
|
+
def mqtt(self):
|
252
|
+
return self._mqtt
|
@@ -95,7 +95,6 @@ class DeviceType(Enum):
|
|
95
95
|
elif DeviceType.LUBA.get_name() in substring2 or DeviceType.contain_luba_product_key(product_key):
|
96
96
|
return DeviceType.LUBA
|
97
97
|
else:
|
98
|
-
print("unknown device type")
|
99
98
|
return DeviceType.UNKNOWN
|
100
99
|
except Exception:
|
101
100
|
return DeviceType.UNKNOWN
|
@@ -1,6 +1,6 @@
|
|
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=X6Y9yyIXCwkPwgRqsBESUI5v6j-CKlJGJaEnMINyp8c,22780
|
4
4
|
pymammotion/aliyun/cloud_service.py,sha256=px7dUKow5Z7VyebjYzuKkzkm77XbUXYiFiYO_2e-UQ0,2207
|
5
5
|
pymammotion/aliyun/dataclass/aep_response.py,sha256=8f6GIP58ve8gd6AL3HBoXxsy0n2q4ygWvjELGnoOnVc,452
|
6
6
|
pymammotion/aliyun/dataclass/connect_response.py,sha256=Yz-fEbDzgGPTo5Of2oAjmFkSv08T7ze80pQU4k-gKIU,824
|
@@ -38,7 +38,7 @@ pymammotion/data/mqtt/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdr
|
|
38
38
|
pymammotion/data/mqtt/event.py,sha256=GKh4NdcaeeaG5pPktzy-eguFKK74Mfxpl0eIAkKU1Vc,4567
|
39
39
|
pymammotion/data/mqtt/properties.py,sha256=HkBPghr26L9_b4QaOi1DtPgb0UoPIOGSe9wb3kgnM6Y,2815
|
40
40
|
pymammotion/data/mqtt/status.py,sha256=zqnlo-MzejEQZszl0i0Wucoc3E76x6UtI9JLxoBnu54,1067
|
41
|
-
pymammotion/data/state_manager.py,sha256=
|
41
|
+
pymammotion/data/state_manager.py,sha256=ZIOB1Th7yDAgCJk17rd-kXjwD50GIqQMsNgf1iFldUs,3615
|
42
42
|
pymammotion/event/__init__.py,sha256=mgATR6vPHACNQ-0zH5fi7NdzeTCDV1CZyaWPmtUusi8,115
|
43
43
|
pymammotion/event/event.py,sha256=UzYnxV5DfvMDK3E06UvSzvzuBbaXOOUwO6xYt_zn9To,2034
|
44
44
|
pymammotion/http/_init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -54,15 +54,15 @@ pymammotion/mammotion/commands/messages/media.py,sha256=ps0l06CXy5Ej--gTNCsyKttw
|
|
54
54
|
pymammotion/mammotion/commands/messages/navigation.py,sha256=4rXBL-mViWc38K6x1w5O-GjwV8UWS5xZXkf4aHYjs8A,23684
|
55
55
|
pymammotion/mammotion/commands/messages/network.py,sha256=gD7NKVKg8U2KNbPvgOxvTJXbznWdpdPQo9jBsQSx4OI,8027
|
56
56
|
pymammotion/mammotion/commands/messages/ota.py,sha256=XkeuWBZtpYMMBze6r8UN7dJXbe2FxUNGNnjwBpXJKM0,1240
|
57
|
-
pymammotion/mammotion/commands/messages/system.py,sha256=
|
57
|
+
pymammotion/mammotion/commands/messages/system.py,sha256=4ijnn0VPHOIizgjqDGpeD3jVetEscnSDt2E4tIBwkoQ,10888
|
58
58
|
pymammotion/mammotion/commands/messages/video.py,sha256=_8lJsU4sLm2CGnc7RDkueA0A51Ysui6x7SqFnhX8O2g,1007
|
59
59
|
pymammotion/mammotion/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
60
|
pymammotion/mammotion/control/joystick.py,sha256=QfBVxM_gxpWsZAGO90whtgxCI2tIZ3TTad9wHIPsU9s,5640
|
61
61
|
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=
|
62
|
+
pymammotion/mammotion/devices/base.py,sha256=5k82KHwW2JqZXW-MBYe8SkedZw_bQ2gqShX5RQd_Yio,11504
|
63
|
+
pymammotion/mammotion/devices/mammotion.py,sha256=uiKtzLHfVPyk-cdm5gFraI9nkrIX3ZkdZPE7xEyuoTQ,11597
|
64
|
+
pymammotion/mammotion/devices/mammotion_bluetooth.py,sha256=sgGeyQeAeA3lQodcalRYS4nDNAzjfFs9SddIB1kadvw,17355
|
65
|
+
pymammotion/mammotion/devices/mammotion_cloud.py,sha256=dk49VY9yHO3d-Nb17y-D4YIURs2FTLMzWHUYrxBYhtw,10116
|
66
66
|
pymammotion/mqtt/__init__.py,sha256=Ocs5e-HLJvTuDpVXyECEsWIvwsUaxzj7lZ9mSYutNDY,105
|
67
67
|
pymammotion/mqtt/mammotion_future.py,sha256=_OWqKOlUGl2yT1xOsXFQYpGd-1zQ63OxqXgy7KRQgYc,710
|
68
68
|
pymammotion/mqtt/mammotion_mqtt.py,sha256=QZuqp2G5nywCFSwGNSayPI5JVL789yOxyr0UM0G7wzg,8295
|
@@ -112,12 +112,12 @@ pymammotion/utility/constant/__init__.py,sha256=tcY0LDeD-qDDHx2LKt55KOyv9ZI0UfCN
|
|
112
112
|
pymammotion/utility/constant/device_constant.py,sha256=rAEK60F52VyJL31uLnq0Y60D-0VK5gVm59yi9kBfndM,7123
|
113
113
|
pymammotion/utility/conversions.py,sha256=v3YICy0zZwwBBzrUZgabI7GRfiDBnkiAX2qdtk3NxOY,89
|
114
114
|
pymammotion/utility/datatype_converter.py,sha256=SPM_HuaaD_XOawlqEnA8qlRRZXGba3WjA8kGOZgeBlQ,4284
|
115
|
-
pymammotion/utility/device_type.py,sha256=
|
115
|
+
pymammotion/utility/device_type.py,sha256=xOgfIhOkzgcAtoKtlhlB1q8FpiKe1rVVV5BvN7K7zYc,9433
|
116
116
|
pymammotion/utility/map.py,sha256=GYscVMg2cX3IPlNpCBNHDW0S55yS1WGRf1iHnNZ7TfQ,2227
|
117
117
|
pymammotion/utility/movement.py,sha256=N75oAoAgFydqoaOedYIxGUHmuTCtPzAOtb-d_29tpfI,615
|
118
118
|
pymammotion/utility/periodic.py,sha256=MbeSb9cfhxzYmdT_RiE0dZe3H9IfbQW_zSqhmSX2RUc,3321
|
119
119
|
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.
|
120
|
+
pymammotion-0.2.36.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
121
|
+
pymammotion-0.2.36.dist-info/METADATA,sha256=oKsEdniFXVqz9dbL-obKVKoZWsKWlS0xDGjaob8n3Lc,4052
|
122
|
+
pymammotion-0.2.36.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
123
|
+
pymammotion-0.2.36.dist-info/RECORD,,
|
File without changes
|
File without changes
|