pymammotion 0.2.28__py3-none-any.whl → 0.2.30__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/__init__.py +12 -9
- pymammotion/aliyun/cloud_gateway.py +57 -39
- pymammotion/aliyun/cloud_service.py +3 -3
- pymammotion/aliyun/dataclass/dev_by_account_response.py +1 -2
- pymammotion/aliyun/dataclass/session_by_authcode_response.py +1 -0
- pymammotion/bluetooth/ble.py +6 -6
- pymammotion/bluetooth/ble_message.py +30 -16
- pymammotion/bluetooth/data/convert.py +1 -1
- pymammotion/bluetooth/data/framectrldata.py +1 -1
- pymammotion/bluetooth/data/notifydata.py +6 -6
- pymammotion/const.py +1 -0
- pymammotion/data/model/__init__.py +2 -0
- pymammotion/data/model/account.py +1 -1
- pymammotion/data/model/device.py +31 -24
- pymammotion/data/model/device_config.py +71 -0
- pymammotion/data/model/enums.py +4 -4
- pymammotion/data/model/excute_boarder_params.py +5 -5
- pymammotion/data/model/execute_boarder.py +4 -4
- pymammotion/data/model/generate_route_information.py +18 -124
- pymammotion/data/model/hash_list.py +4 -7
- pymammotion/data/model/location.py +3 -3
- pymammotion/data/model/mowing_modes.py +1 -1
- pymammotion/data/model/plan.py +4 -4
- pymammotion/data/model/region_data.py +4 -4
- pymammotion/data/model/report_info.py +1 -1
- pymammotion/data/mqtt/event.py +8 -3
- pymammotion/data/state_manager.py +13 -12
- pymammotion/event/event.py +14 -14
- pymammotion/http/http.py +33 -45
- pymammotion/http/model/http.py +75 -0
- pymammotion/mammotion/commands/messages/driver.py +20 -23
- pymammotion/mammotion/commands/messages/navigation.py +47 -48
- pymammotion/mammotion/commands/messages/network.py +17 -35
- pymammotion/mammotion/commands/messages/system.py +6 -7
- pymammotion/mammotion/control/joystick.py +11 -10
- pymammotion/mammotion/devices/__init__.py +2 -2
- pymammotion/mammotion/devices/base.py +248 -0
- pymammotion/mammotion/devices/mammotion.py +52 -1042
- pymammotion/mammotion/devices/mammotion_bluetooth.py +447 -0
- pymammotion/mammotion/devices/mammotion_cloud.py +244 -0
- pymammotion/mqtt/mammotion_future.py +3 -2
- pymammotion/mqtt/mammotion_mqtt.py +23 -23
- pymammotion/proto/__init__.py +6 -0
- pymammotion/utility/constant/__init__.py +3 -1
- pymammotion/utility/conversions.py +1 -1
- pymammotion/utility/datatype_converter.py +9 -9
- pymammotion/utility/device_type.py +47 -18
- pymammotion/utility/map.py +2 -2
- pymammotion/utility/movement.py +2 -1
- pymammotion/utility/periodic.py +5 -5
- pymammotion/utility/rocker_util.py +1 -1
- {pymammotion-0.2.28.dist-info → pymammotion-0.2.30.dist-info}/METADATA +3 -1
- {pymammotion-0.2.28.dist-info → pymammotion-0.2.30.dist-info}/RECORD +55 -51
- {pymammotion-0.2.28.dist-info → pymammotion-0.2.30.dist-info}/LICENSE +0 -0
- {pymammotion-0.2.28.dist-info → pymammotion-0.2.30.dist-info}/WHEEL +0 -0
@@ -0,0 +1,248 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
from abc import abstractmethod
|
4
|
+
from typing import Any, Awaitable, Callable
|
5
|
+
|
6
|
+
import betterproto
|
7
|
+
|
8
|
+
from pymammotion.aliyun.dataclass.connect_response import Device
|
9
|
+
from pymammotion.data.model import RegionData
|
10
|
+
from pymammotion.data.model.device import MowingDevice
|
11
|
+
from pymammotion.data.state_manager import StateManager
|
12
|
+
from pymammotion.proto import has_field
|
13
|
+
from pymammotion.proto.luba_msg import LubaMsg
|
14
|
+
from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck
|
15
|
+
from pymammotion.utility.movement import get_percent, transform_both_speeds
|
16
|
+
|
17
|
+
_LOGGER = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
def find_next_integer(lst: list[int], current_hash: float) -> int | None:
|
21
|
+
try:
|
22
|
+
# Find the index of the current integer
|
23
|
+
current_index = lst.index(current_hash)
|
24
|
+
|
25
|
+
# Check if there is a next integer in the list
|
26
|
+
if current_index + 1 < len(lst):
|
27
|
+
return lst[current_index + 1]
|
28
|
+
else:
|
29
|
+
return None # Or raise an exception or handle it in some other way
|
30
|
+
except ValueError:
|
31
|
+
# Handle the case where current_int is not in the list
|
32
|
+
return None # Or raise an exception or handle it in some other way
|
33
|
+
|
34
|
+
|
35
|
+
class MammotionBaseDevice:
|
36
|
+
"""Base class for Mammotion devices."""
|
37
|
+
|
38
|
+
_mower: MowingDevice
|
39
|
+
_state_manager: StateManager
|
40
|
+
_cloud_device: Device | None = None
|
41
|
+
|
42
|
+
def __init__(self, device: MowingDevice, cloud_device: Device | None = None) -> None:
|
43
|
+
"""Initialize MammotionBaseDevice."""
|
44
|
+
self.loop = asyncio.get_event_loop()
|
45
|
+
self._raw_data = LubaMsg().to_dict(casing=betterproto.Casing.SNAKE)
|
46
|
+
self._mower = device
|
47
|
+
self._state_manager = StateManager(self._mower)
|
48
|
+
self._state_manager.gethash_ack_callback = self.datahash_response
|
49
|
+
self._state_manager.get_commondata_ack_callback = self.commdata_response
|
50
|
+
self._notify_future: asyncio.Future[bytes] | None = None
|
51
|
+
self._cloud_device = cloud_device
|
52
|
+
|
53
|
+
def set_notification_callback(self, func: Callable[[], Awaitable[None]]) -> None:
|
54
|
+
self._state_manager.on_notification_callback = func
|
55
|
+
|
56
|
+
async def datahash_response(self, hash_ack: NavGetHashListAck) -> None:
|
57
|
+
"""Handle datahash responses."""
|
58
|
+
await self.queue_command("synchronize_hash_data", hash_num=hash_ack.data_couple[0])
|
59
|
+
|
60
|
+
async def commdata_response(self, common_data: NavGetCommDataAck) -> None:
|
61
|
+
"""Handle common data responses."""
|
62
|
+
total_frame = common_data.total_frame
|
63
|
+
current_frame = common_data.current_frame
|
64
|
+
|
65
|
+
missing_frames = self._mower.map.missing_frame(common_data)
|
66
|
+
if len(missing_frames) == 0:
|
67
|
+
# get next in hash ack list
|
68
|
+
|
69
|
+
data_hash = find_next_integer(self._mower.nav.toapp_gethash_ack.data_couple, common_data.hash)
|
70
|
+
if data_hash is None:
|
71
|
+
return
|
72
|
+
|
73
|
+
await self.queue_command("synchronize_hash_data", hash_num=data_hash)
|
74
|
+
else:
|
75
|
+
if current_frame != missing_frames[0] - 1:
|
76
|
+
current_frame = missing_frames[0] - 1
|
77
|
+
|
78
|
+
region_data = RegionData()
|
79
|
+
region_data.hash = common_data.hash
|
80
|
+
region_data.action = common_data.action
|
81
|
+
region_data.type = common_data.type
|
82
|
+
region_data.total_frame = total_frame
|
83
|
+
region_data.current_frame = current_frame
|
84
|
+
await self.queue_command("get_regional_data", regional_data=region_data)
|
85
|
+
|
86
|
+
def _update_raw_data(self, data: bytes) -> None:
|
87
|
+
"""Update raw and model data from notifications."""
|
88
|
+
tmp_msg = LubaMsg().parse(data)
|
89
|
+
res = betterproto.which_one_of(tmp_msg, "LubaSubMsg")
|
90
|
+
match res[0]:
|
91
|
+
case "nav":
|
92
|
+
self._update_nav_data(tmp_msg)
|
93
|
+
case "sys":
|
94
|
+
self._update_sys_data(tmp_msg)
|
95
|
+
case "driver":
|
96
|
+
self._update_driver_data(tmp_msg)
|
97
|
+
case "net":
|
98
|
+
self._update_net_data(tmp_msg)
|
99
|
+
case "mul":
|
100
|
+
self._update_mul_data(tmp_msg)
|
101
|
+
case "ota":
|
102
|
+
self._update_ota_data(tmp_msg)
|
103
|
+
|
104
|
+
self._mower.update_raw(self._raw_data)
|
105
|
+
|
106
|
+
def _update_nav_data(self, tmp_msg) -> None:
|
107
|
+
"""Update navigation data."""
|
108
|
+
nav_sub_msg = betterproto.which_one_of(tmp_msg.nav, "SubNavMsg")
|
109
|
+
if nav_sub_msg[1] is None:
|
110
|
+
_LOGGER.debug("Sub message was NoneType %s", nav_sub_msg[0])
|
111
|
+
return
|
112
|
+
nav = self._raw_data.get("nav", {})
|
113
|
+
if isinstance(nav_sub_msg[1], int):
|
114
|
+
nav[nav_sub_msg[0]] = nav_sub_msg[1]
|
115
|
+
else:
|
116
|
+
nav[nav_sub_msg[0]] = nav_sub_msg[1].to_dict(casing=betterproto.Casing.SNAKE)
|
117
|
+
self._raw_data["nav"] = nav
|
118
|
+
|
119
|
+
def _update_sys_data(self, tmp_msg) -> None:
|
120
|
+
"""Update system data."""
|
121
|
+
sys_sub_msg = betterproto.which_one_of(tmp_msg.sys, "SubSysMsg")
|
122
|
+
if sys_sub_msg[1] is None:
|
123
|
+
_LOGGER.debug("Sub message was NoneType %s", sys_sub_msg[0])
|
124
|
+
return
|
125
|
+
sys = self._raw_data.get("sys", {})
|
126
|
+
sys[sys_sub_msg[0]] = sys_sub_msg[1].to_dict(casing=betterproto.Casing.SNAKE)
|
127
|
+
self._raw_data["sys"] = sys
|
128
|
+
|
129
|
+
def _update_driver_data(self, tmp_msg) -> None:
|
130
|
+
"""Update driver data."""
|
131
|
+
drv_sub_msg = betterproto.which_one_of(tmp_msg.driver, "SubDrvMsg")
|
132
|
+
if drv_sub_msg[1] is None:
|
133
|
+
_LOGGER.debug("Sub message was NoneType %s", drv_sub_msg[0])
|
134
|
+
return
|
135
|
+
drv = self._raw_data.get("driver", {})
|
136
|
+
drv[drv_sub_msg[0]] = drv_sub_msg[1].to_dict(casing=betterproto.Casing.SNAKE)
|
137
|
+
self._raw_data["driver"] = drv
|
138
|
+
|
139
|
+
def _update_net_data(self, tmp_msg) -> None:
|
140
|
+
"""Update network data."""
|
141
|
+
net_sub_msg = betterproto.which_one_of(tmp_msg.net, "NetSubType")
|
142
|
+
if net_sub_msg[1] is None:
|
143
|
+
_LOGGER.debug("Sub message was NoneType %s", net_sub_msg[0])
|
144
|
+
return
|
145
|
+
net = self._raw_data.get("net", {})
|
146
|
+
if isinstance(net_sub_msg[1], int):
|
147
|
+
net[net_sub_msg[0]] = net_sub_msg[1]
|
148
|
+
else:
|
149
|
+
net[net_sub_msg[0]] = net_sub_msg[1].to_dict(casing=betterproto.Casing.SNAKE)
|
150
|
+
self._raw_data["net"] = net
|
151
|
+
|
152
|
+
def _update_mul_data(self, tmp_msg) -> None:
|
153
|
+
"""Update mul data."""
|
154
|
+
mul_sub_msg = betterproto.which_one_of(tmp_msg.mul, "SubMul")
|
155
|
+
if mul_sub_msg[1] is None:
|
156
|
+
_LOGGER.debug("Sub message was NoneType %s", mul_sub_msg[0])
|
157
|
+
return
|
158
|
+
mul = self._raw_data.get("mul", {})
|
159
|
+
mul[mul_sub_msg[0]] = mul_sub_msg[1].to_dict(casing=betterproto.Casing.SNAKE)
|
160
|
+
self._raw_data["mul"] = mul
|
161
|
+
|
162
|
+
def _update_ota_data(self, tmp_msg) -> None:
|
163
|
+
"""Update OTA data."""
|
164
|
+
ota_sub_msg = betterproto.which_one_of(tmp_msg.ota, "SubOtaMsg")
|
165
|
+
if ota_sub_msg[1] is None:
|
166
|
+
_LOGGER.debug("Sub message was NoneType %s", ota_sub_msg[0])
|
167
|
+
return
|
168
|
+
ota = self._raw_data.get("ota", {})
|
169
|
+
ota[ota_sub_msg[0]] = ota_sub_msg[1].to_dict(casing=betterproto.Casing.SNAKE)
|
170
|
+
self._raw_data["ota"] = ota
|
171
|
+
|
172
|
+
@property
|
173
|
+
def raw_data(self) -> dict[str, Any]:
|
174
|
+
"""Get the raw data of the device."""
|
175
|
+
return self._raw_data
|
176
|
+
|
177
|
+
@property
|
178
|
+
def mower(self) -> MowingDevice:
|
179
|
+
"""Get the LubaMsg of the device."""
|
180
|
+
return self._mower
|
181
|
+
|
182
|
+
@abstractmethod
|
183
|
+
async def queue_command(self, key: str, **kwargs: any) -> bytes | None:
|
184
|
+
"""Queue commands to mower."""
|
185
|
+
|
186
|
+
@abstractmethod
|
187
|
+
async def _ble_sync(self):
|
188
|
+
"""Send ble sync command every 3 seconds or sooner."""
|
189
|
+
|
190
|
+
async def start_sync(self, retry: int) -> None:
|
191
|
+
"""Start synchronization with the device."""
|
192
|
+
await self.queue_command("get_device_base_info")
|
193
|
+
await self.queue_command("get_device_product_model")
|
194
|
+
await self.queue_command("get_report_cfg")
|
195
|
+
"""RTK and dock location."""
|
196
|
+
await self.queue_command("allpowerfull_rw", id=5, rw=1, context=1)
|
197
|
+
|
198
|
+
async def start_map_sync(self) -> None:
|
199
|
+
"""Start sync of map data."""
|
200
|
+
await self.queue_command("read_plan", sub_cmd=2, plan_index=0)
|
201
|
+
|
202
|
+
await self.queue_command("get_all_boundary_hash_list", sub_cmd=0)
|
203
|
+
|
204
|
+
await self.queue_command("get_hash_response", total_frame=1, current_frame=1)
|
205
|
+
|
206
|
+
# work out why this crashes sometimes for better proto
|
207
|
+
if self._cloud_device:
|
208
|
+
await self.queue_command("get_area_name_list", device_id=self._cloud_device.deviceName)
|
209
|
+
if has_field(self._mower.net.toapp_wifi_iot_status):
|
210
|
+
await self.queue_command("get_area_name_list", device_id=self._mower.net.toapp_wifi_iot_status.devicename)
|
211
|
+
|
212
|
+
# sub_cmd 3 is job hashes??
|
213
|
+
# sub_cmd 4 is dump location (yuka)
|
214
|
+
# jobs list
|
215
|
+
# hash_list_result = await self._send_command_with_args("get_all_boundary_hash_list", sub_cmd=3)
|
216
|
+
|
217
|
+
async def async_get_errors(self) -> None:
|
218
|
+
"""Error codes."""
|
219
|
+
await self.queue_command("allpowerfull_rw", id=5, rw=1, context=2)
|
220
|
+
await self.queue_command("allpowerfull_rw", id=5, rw=1, context=3)
|
221
|
+
|
222
|
+
async def move_forward(self, linear: float) -> None:
|
223
|
+
"""Move forward. values 0.0 1.0."""
|
224
|
+
linear_percent = get_percent(abs(linear * 100))
|
225
|
+
(linear_speed, angular_speed) = transform_both_speeds(90.0, 0.0, linear_percent, 0.0)
|
226
|
+
await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
|
227
|
+
|
228
|
+
async def move_back(self, linear: float) -> None:
|
229
|
+
"""Move back. values 0.0 1.0."""
|
230
|
+
linear_percent = get_percent(abs(linear * 100))
|
231
|
+
(linear_speed, angular_speed) = transform_both_speeds(270.0, 0.0, linear_percent, 0.0)
|
232
|
+
await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
|
233
|
+
|
234
|
+
async def move_left(self, angulur: float) -> None:
|
235
|
+
"""Move forward. values 0.0 1.0."""
|
236
|
+
angular_percent = get_percent(abs(angulur * 100))
|
237
|
+
(linear_speed, angular_speed) = transform_both_speeds(0.0, 0.0, 0.0, angular_percent)
|
238
|
+
await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
|
239
|
+
|
240
|
+
async def move_right(self, angulur: float) -> None:
|
241
|
+
"""Move back. values 0.0 1.0."""
|
242
|
+
angular_percent = get_percent(abs(angulur * 100))
|
243
|
+
(linear_speed, angular_speed) = transform_both_speeds(0.0, 180.0, 0.0, angular_percent)
|
244
|
+
await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed)
|
245
|
+
|
246
|
+
async def command(self, key: str, **kwargs):
|
247
|
+
"""Send a command to the device."""
|
248
|
+
return await self.queue_command(key, **kwargs)
|