pymammotion 0.5.34__py3-none-any.whl → 0.5.44__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymammotion might be problematic. Click here for more details.
- pymammotion/__init__.py +3 -3
- pymammotion/aliyun/cloud_gateway.py +106 -18
- pymammotion/aliyun/model/dev_by_account_response.py +198 -20
- pymammotion/const.py +3 -0
- pymammotion/data/model/device.py +1 -0
- pymammotion/data/model/device_config.py +1 -1
- pymammotion/data/model/enums.py +5 -3
- pymammotion/data/model/generate_route_information.py +2 -2
- pymammotion/data/model/hash_list.py +113 -33
- pymammotion/data/model/region_data.py +4 -4
- pymammotion/data/{state_manager.py → mower_state_manager.py} +17 -7
- pymammotion/data/mqtt/event.py +47 -22
- pymammotion/data/mqtt/mammotion_properties.py +257 -0
- pymammotion/data/mqtt/properties.py +32 -29
- pymammotion/data/mqtt/status.py +17 -16
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +446 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/http.py +387 -13
- pymammotion/http/model/http.py +82 -2
- pymammotion/http/model/response_factory.py +10 -4
- pymammotion/mammotion/commands/mammotion_command.py +6 -0
- pymammotion/mammotion/commands/messages/navigation.py +10 -6
- pymammotion/mammotion/devices/__init__.py +27 -3
- pymammotion/mammotion/devices/base.py +16 -138
- pymammotion/mammotion/devices/mammotion.py +364 -204
- pymammotion/mammotion/devices/mammotion_bluetooth.py +7 -5
- pymammotion/mammotion/devices/mammotion_cloud.py +42 -83
- pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
- pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
- pymammotion/mammotion/devices/managers/managers.py +81 -0
- pymammotion/mammotion/devices/mower_device.py +121 -0
- pymammotion/mammotion/devices/mower_manager.py +107 -0
- pymammotion/mammotion/devices/rtk_ble.py +89 -0
- pymammotion/mammotion/devices/rtk_cloud.py +113 -0
- pymammotion/mammotion/devices/rtk_device.py +50 -0
- pymammotion/mammotion/devices/rtk_manager.py +122 -0
- pymammotion/mqtt/__init__.py +2 -1
- pymammotion/mqtt/aliyun_mqtt.py +232 -0
- pymammotion/mqtt/mammotion_mqtt.py +174 -192
- pymammotion/mqtt/mqtt_models.py +66 -0
- pymammotion/proto/__init__.py +1 -1
- pymammotion/proto/mctrl_nav.proto +1 -1
- pymammotion/proto/mctrl_nav_pb2.py +1 -1
- pymammotion/proto/mctrl_nav_pb2.pyi +4 -4
- pymammotion/proto/mctrl_sys.proto +1 -1
- pymammotion/utility/datatype_converter.py +13 -12
- pymammotion/utility/device_type.py +88 -3
- pymammotion/utility/mur_mur_hash.py +132 -87
- {pymammotion-0.5.34.dist-info → pymammotion-0.5.44.dist-info}/METADATA +25 -31
- {pymammotion-0.5.34.dist-info → pymammotion-0.5.44.dist-info}/RECORD +59 -45
- {pymammotion-0.5.34.dist-info → pymammotion-0.5.44.dist-info}/WHEEL +1 -1
- pymammotion/http/_init_.py +0 -0
- {pymammotion-0.5.34.dist-info → pymammotion-0.5.44.dist-info/licenses}/LICENSE +0 -0
|
@@ -4,6 +4,7 @@ from enum import IntEnum
|
|
|
4
4
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
5
5
|
|
|
6
6
|
from pymammotion.proto import NavGetCommDataAck, NavGetHashListAck, SvgMessageAckT
|
|
7
|
+
from pymammotion.utility.mur_mur_hash import MurMurHashUtil
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class PathType(IntEnum):
|
|
@@ -28,6 +29,13 @@ class AreaLabelName(DataClassORJSONMixin):
|
|
|
28
29
|
label: str = ""
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
@dataclass
|
|
33
|
+
class NavNameTime(DataClassORJSONMixin):
|
|
34
|
+
name: str = ""
|
|
35
|
+
create_time: int = 0
|
|
36
|
+
modify_time: int = 0
|
|
37
|
+
|
|
38
|
+
|
|
31
39
|
@dataclass
|
|
32
40
|
class NavGetCommData(DataClassORJSONMixin):
|
|
33
41
|
pver: int = 0
|
|
@@ -44,7 +52,35 @@ class NavGetCommData(DataClassORJSONMixin):
|
|
|
44
52
|
data_len: int = 0
|
|
45
53
|
data_couple: list["CommDataCouple"] = field(default_factory=list)
|
|
46
54
|
reserved: str = ""
|
|
47
|
-
|
|
55
|
+
name_time: NavNameTime = field(default_factory=NavNameTime)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class MowPathPacket(DataClassORJSONMixin):
|
|
60
|
+
path_hash: int = 0
|
|
61
|
+
path_type: int = 0
|
|
62
|
+
path_total: int = 0
|
|
63
|
+
path_cur: int = 0
|
|
64
|
+
zone_hash: int = 0
|
|
65
|
+
data_couple: list["CommDataCouple"] = field(default_factory=list)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class MowPath(DataClassORJSONMixin):
|
|
70
|
+
pver: int = 0
|
|
71
|
+
sub_cmd: int = 0
|
|
72
|
+
result: int = 0
|
|
73
|
+
area: int = 0
|
|
74
|
+
time: int = 0
|
|
75
|
+
total_frame: int = 0
|
|
76
|
+
current_frame: int = 0
|
|
77
|
+
total_path_num: int = 0
|
|
78
|
+
valid_path_num: int = 0
|
|
79
|
+
data_hash: int = 0
|
|
80
|
+
transaction_id: int = 0
|
|
81
|
+
reserved: list[int] = field(default_factory=list)
|
|
82
|
+
data_len: int = 0
|
|
83
|
+
path_packets: list[MowPathPacket] = field(default_factory=list)
|
|
48
84
|
|
|
49
85
|
|
|
50
86
|
@dataclass
|
|
@@ -172,13 +208,24 @@ class HashList(DataClassORJSONMixin):
|
|
|
172
208
|
line: dict[int, FrameList] = field(default_factory=dict) # type 10 possibly breakpoint? / sub cmd 3
|
|
173
209
|
plan: dict[str, Plan] = field(default_factory=dict)
|
|
174
210
|
area_name: list[AreaHashNameList] = field(default_factory=list)
|
|
211
|
+
current_mow_path: dict[int, MowPath] = field(default_factory=dict)
|
|
175
212
|
|
|
176
|
-
def update_hash_lists(self, hashlist: list[int]) -> None:
|
|
213
|
+
def update_hash_lists(self, hashlist: list[int], bol_hash: str | None = None) -> None:
|
|
214
|
+
if bol_hash:
|
|
215
|
+
self.invalidate_maps(int(bol_hash))
|
|
177
216
|
self.area = {hash_id: frames for hash_id, frames in self.area.items() if hash_id in hashlist}
|
|
178
217
|
self.path = {hash_id: frames for hash_id, frames in self.path.items() if hash_id in hashlist}
|
|
179
218
|
self.obstacle = {hash_id: frames for hash_id, frames in self.obstacle.items() if hash_id in hashlist}
|
|
180
219
|
self.dump = {hash_id: frames for hash_id, frames in self.dump.items() if hash_id in hashlist}
|
|
181
220
|
self.svg = {hash_id: frames for hash_id, frames in self.svg.items() if hash_id in hashlist}
|
|
221
|
+
|
|
222
|
+
area_hashes = list(self.area.keys())
|
|
223
|
+
for hash_id, plan_task in self.plan.copy().items():
|
|
224
|
+
for item in plan_task.zone_hashs:
|
|
225
|
+
if item not in area_hashes:
|
|
226
|
+
self.plan.pop(hash_id)
|
|
227
|
+
break
|
|
228
|
+
|
|
182
229
|
self.area_name = [
|
|
183
230
|
area_item
|
|
184
231
|
for area_item in self.area_name
|
|
@@ -259,61 +306,90 @@ class HashList(DataClassORJSONMixin):
|
|
|
259
306
|
return missing_frames
|
|
260
307
|
|
|
261
308
|
def missing_frame(self, hash_data: NavGetCommDataAck | SvgMessageAckT) -> list[int]:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if hash_data.type == PathType.OBSTACLE:
|
|
266
|
-
return self.find_missing_frames(self.obstacle.get(hash_data.hash))
|
|
309
|
+
frame_list = self._get_frame_list_by_type_and_hash(hash_data)
|
|
310
|
+
return self.find_missing_frames(frame_list)
|
|
267
311
|
|
|
268
|
-
|
|
269
|
-
|
|
312
|
+
def _get_frame_list_by_type_and_hash(self, hash_data: NavGetCommDataAck | SvgMessageAckT) -> FrameList | None:
|
|
313
|
+
"""Get the appropriate FrameList based on hash_data type and hash."""
|
|
314
|
+
path_type_mapping = self._get_path_type_mapping()
|
|
315
|
+
target_dict = path_type_mapping.get(hash_data.type)
|
|
270
316
|
|
|
271
|
-
if
|
|
272
|
-
return
|
|
317
|
+
if target_dict is None:
|
|
318
|
+
return None
|
|
273
319
|
|
|
274
|
-
|
|
275
|
-
|
|
320
|
+
# Handle SvgMessage with data_hash attribute
|
|
321
|
+
if isinstance(hash_data, SvgMessageAckT):
|
|
322
|
+
return target_dict.get(hash_data.data_hash)
|
|
276
323
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return []
|
|
324
|
+
# Handle NavGetCommDataAck with hash attribute
|
|
325
|
+
return target_dict.get(hash_data.hash)
|
|
281
326
|
|
|
282
327
|
def update_plan(self, plan: Plan) -> None:
|
|
283
328
|
if plan.total_plan_num != 0:
|
|
284
329
|
self.plan[plan.plan_id] = plan
|
|
285
330
|
|
|
331
|
+
def _get_path_type_mapping(self) -> dict[int, dict[int, FrameList]]:
|
|
332
|
+
"""Return mapping of PathType to corresponding hash dictionary."""
|
|
333
|
+
return {
|
|
334
|
+
PathType.AREA: self.area,
|
|
335
|
+
PathType.OBSTACLE: self.obstacle,
|
|
336
|
+
PathType.PATH: self.path,
|
|
337
|
+
PathType.LINE: self.line,
|
|
338
|
+
PathType.DUMP: self.dump,
|
|
339
|
+
PathType.SVG: self.svg,
|
|
340
|
+
}
|
|
341
|
+
|
|
286
342
|
def update(self, hash_data: NavGetCommData | SvgMessage) -> bool:
|
|
287
343
|
"""Update the map data."""
|
|
288
344
|
|
|
289
|
-
if hash_data.type == PathType.AREA:
|
|
345
|
+
if hash_data.type == PathType.AREA and isinstance(hash_data, NavGetCommData):
|
|
290
346
|
existing_name = next((area for area in self.area_name if area.hash == hash_data.hash), None)
|
|
291
347
|
if not existing_name:
|
|
292
|
-
name = f"area {len(self.area_name)+1}"
|
|
348
|
+
name = f"area {len(self.area_name)+1}"
|
|
293
349
|
self.area_name.append(AreaHashNameList(name=name, hash=hash_data.hash))
|
|
294
350
|
result = self._add_hash_data(self.area, hash_data)
|
|
295
351
|
self.update_hash_lists(self.hashlist)
|
|
296
352
|
return result
|
|
297
353
|
|
|
298
|
-
|
|
299
|
-
|
|
354
|
+
path_type_mapping = self._get_path_type_mapping()
|
|
355
|
+
target_dict = path_type_mapping.get(hash_data.type)
|
|
300
356
|
|
|
301
|
-
if
|
|
302
|
-
return self._add_hash_data(
|
|
357
|
+
if target_dict is not None:
|
|
358
|
+
return self._add_hash_data(target_dict, hash_data)
|
|
303
359
|
|
|
304
|
-
|
|
305
|
-
return self._add_hash_data(self.line, hash_data)
|
|
360
|
+
return False
|
|
306
361
|
|
|
307
|
-
|
|
308
|
-
|
|
362
|
+
def find_missing_mow_path_frames(self) -> list[int]:
|
|
363
|
+
"""Find missing frames in current_mow_path based on total_frame."""
|
|
364
|
+
if not self.current_mow_path:
|
|
365
|
+
return []
|
|
309
366
|
|
|
310
|
-
|
|
311
|
-
|
|
367
|
+
# Get total_frame from any MowPath object (they should all have the same total_frame)
|
|
368
|
+
total_frame = next(iter(self.current_mow_path.values())).total_frame
|
|
312
369
|
|
|
313
|
-
|
|
370
|
+
if total_frame == 0:
|
|
371
|
+
return []
|
|
372
|
+
|
|
373
|
+
if total_frame == len(self.current_mow_path):
|
|
374
|
+
return []
|
|
375
|
+
|
|
376
|
+
# Generate list of expected frame numbers (1 to total_frame)
|
|
377
|
+
expected_frames = set(range(1, total_frame + 1))
|
|
378
|
+
|
|
379
|
+
# Get current frame numbers from dictionary keys
|
|
380
|
+
current_frames = set(self.current_mow_path.keys())
|
|
381
|
+
|
|
382
|
+
# Return sorted list of missing frames
|
|
383
|
+
missing_frames = sorted(expected_frames - current_frames)
|
|
384
|
+
return missing_frames
|
|
385
|
+
|
|
386
|
+
def update_mow_path(self, path: MowPath) -> None:
|
|
387
|
+
"""Update the current_mow_path with the latest MowPath data."""
|
|
388
|
+
# TODO check if we need to clear the current_mow_path first
|
|
389
|
+
self.current_mow_path[path.current_frame] = path
|
|
314
390
|
|
|
315
391
|
@staticmethod
|
|
316
|
-
def find_missing_frames(frame_list: FrameList | RootHashList) -> list[int]:
|
|
392
|
+
def find_missing_frames(frame_list: FrameList | RootHashList | None) -> list[int]:
|
|
317
393
|
if frame_list is None:
|
|
318
394
|
return []
|
|
319
395
|
|
|
@@ -328,7 +404,7 @@ class HashList(DataClassORJSONMixin):
|
|
|
328
404
|
@staticmethod
|
|
329
405
|
def _add_hash_data(hash_dict: dict[int, FrameList], hash_data: NavGetCommData | SvgMessage) -> bool:
|
|
330
406
|
if isinstance(hash_data, SvgMessage):
|
|
331
|
-
if hash_dict.get(hash_data.data_hash) is None:
|
|
407
|
+
if hash_dict.get(hash_data.data_hash, None) is None:
|
|
332
408
|
hash_dict[hash_data.data_hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
|
|
333
409
|
return True
|
|
334
410
|
|
|
@@ -347,7 +423,7 @@ class HashList(DataClassORJSONMixin):
|
|
|
347
423
|
return True
|
|
348
424
|
return False
|
|
349
425
|
|
|
350
|
-
if hash_dict.get(hash_data.hash) is None:
|
|
426
|
+
if hash_dict.get(hash_data.hash, None) is None:
|
|
351
427
|
hash_dict[hash_data.hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
|
|
352
428
|
return True
|
|
353
429
|
|
|
@@ -361,3 +437,7 @@ class HashList(DataClassORJSONMixin):
|
|
|
361
437
|
hash_dict[hash_data.hash].data.append(hash_data)
|
|
362
438
|
return True
|
|
363
439
|
return False
|
|
440
|
+
|
|
441
|
+
def invalidate_maps(self, bol_hash: int) -> None:
|
|
442
|
+
if MurMurHashUtil.hash_unsigned_list(self.hashlist) != bol_hash:
|
|
443
|
+
self.root_hash_lists = []
|
|
@@ -6,13 +6,13 @@ from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
|
6
6
|
@dataclass
|
|
7
7
|
class RegionData(DataClassORJSONMixin):
|
|
8
8
|
def __init__(self) -> None:
|
|
9
|
-
self.hash: int
|
|
9
|
+
self.hash: int = 0
|
|
10
10
|
self.action: int = 0
|
|
11
11
|
self.current_frame: int = 0
|
|
12
|
-
self.data_hash: int
|
|
12
|
+
self.data_hash: int = 0
|
|
13
13
|
self.data_len: int = 0
|
|
14
|
-
self.p_hash_a: int
|
|
15
|
-
self.p_hash_b: int
|
|
14
|
+
self.p_hash_a: int = 0
|
|
15
|
+
self.p_hash_b: int = 0
|
|
16
16
|
self.path: list[list[float]] | None = None
|
|
17
17
|
self.pver: int = 0
|
|
18
18
|
self.result: int = 0
|
|
@@ -9,8 +9,14 @@ import betterproto2
|
|
|
9
9
|
|
|
10
10
|
from pymammotion.data.model.device import MowingDevice
|
|
11
11
|
from pymammotion.data.model.device_info import SideLight
|
|
12
|
-
from pymammotion.data.model.
|
|
13
|
-
|
|
12
|
+
from pymammotion.data.model.hash_list import (
|
|
13
|
+
AreaHashNameList,
|
|
14
|
+
MowPath,
|
|
15
|
+
NavGetCommData,
|
|
16
|
+
NavGetHashListData,
|
|
17
|
+
Plan,
|
|
18
|
+
SvgMessage,
|
|
19
|
+
)
|
|
14
20
|
from pymammotion.data.model.work import CurrentTaskSettings
|
|
15
21
|
from pymammotion.data.mqtt.event import ThingEventMessage
|
|
16
22
|
from pymammotion.data.mqtt.properties import ThingPropertiesMessage
|
|
@@ -20,6 +26,7 @@ from pymammotion.proto import (
|
|
|
20
26
|
AppGetAllAreaHashName,
|
|
21
27
|
AppGetCutterWorkMode,
|
|
22
28
|
AppSetCutterWorkMode,
|
|
29
|
+
CoverPathUploadT,
|
|
23
30
|
DeviceFwInfo,
|
|
24
31
|
DeviceProductTypeInfoT,
|
|
25
32
|
DrvDevInfoResp,
|
|
@@ -41,14 +48,13 @@ from pymammotion.proto import (
|
|
|
41
48
|
logger = logging.getLogger(__name__)
|
|
42
49
|
|
|
43
50
|
|
|
44
|
-
class
|
|
51
|
+
class MowerStateManager:
|
|
45
52
|
"""Manage state."""
|
|
46
53
|
|
|
47
54
|
def __init__(self, device: MowingDevice) -> None:
|
|
48
55
|
"""Initialize state manager with a device."""
|
|
49
56
|
self._device: MowingDevice = device
|
|
50
57
|
self.last_updated_at = datetime.now(UTC)
|
|
51
|
-
self.preference = ConnectionPreference.WIFI
|
|
52
58
|
self.cloud_gethash_ack_callback: Callable[[NavGetHashListAck], Awaitable[None]] | None = None
|
|
53
59
|
self.cloud_get_commondata_ack_callback: (
|
|
54
60
|
Callable[[NavGetCommDataAck | SvgMessageAckT], Awaitable[None]] | None
|
|
@@ -132,7 +138,7 @@ class StateManager:
|
|
|
132
138
|
await self.status_callback.data_event(thing_status)
|
|
133
139
|
|
|
134
140
|
async def on_device_event_callback(self, device_event: ThingEventMessage) -> None:
|
|
135
|
-
"""Executes the
|
|
141
|
+
"""Executes the event callback if it is set."""
|
|
136
142
|
if self.device_event_callback:
|
|
137
143
|
await self.device_event_callback.data_event(device_event)
|
|
138
144
|
|
|
@@ -191,6 +197,10 @@ class StateManager:
|
|
|
191
197
|
)
|
|
192
198
|
if updated:
|
|
193
199
|
await self.get_commondata_ack_callback(common_data)
|
|
200
|
+
case "cover_path_upload":
|
|
201
|
+
mow_path: CoverPathUploadT = nav_msg[1]
|
|
202
|
+
self._device.map.update_mow_path(MowPath.from_dict(mow_path.to_dict(casing=betterproto2.Casing.SNAKE)))
|
|
203
|
+
|
|
194
204
|
case "todev_planjob_set":
|
|
195
205
|
planjob: NavPlanJobSet = nav_msg[1]
|
|
196
206
|
self._device.map.update_plan(Plan.from_dict(planjob.to_dict(casing=betterproto2.Casing.SNAKE)))
|
|
@@ -263,8 +273,8 @@ class StateManager:
|
|
|
263
273
|
self._device.mower_state.cutter_mode = cutter_work_mode.current_cutter_mode
|
|
264
274
|
self._device.mower_state.cutter_rpm = cutter_work_mode.current_cutter_rpm
|
|
265
275
|
case "cutter_mode_ctrl_by_hand":
|
|
266
|
-
|
|
267
|
-
self._device.mower_state.cutter_mode =
|
|
276
|
+
cutter_work_mode_set: AppSetCutterWorkMode = driver_msg[1]
|
|
277
|
+
self._device.mower_state.cutter_mode = cutter_work_mode_set.cutter_mode
|
|
268
278
|
|
|
269
279
|
def _update_net_data(self, message) -> None:
|
|
270
280
|
"""Update network data."""
|
pymammotion/data/mqtt/event.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from base64 import b64decode
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import Any, Literal
|
|
3
|
+
from typing import Annotated, Any, Literal
|
|
4
4
|
|
|
5
5
|
from google.protobuf import json_format
|
|
6
6
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
7
|
-
from mashumaro.types import SerializableType
|
|
7
|
+
from mashumaro.types import Alias, SerializableType
|
|
8
8
|
|
|
9
9
|
from pymammotion.proto import luba_msg_pb2
|
|
10
10
|
|
|
@@ -73,34 +73,35 @@ class DeviceBizReqEventValue(DataClassORJSONMixin):
|
|
|
73
73
|
|
|
74
74
|
@dataclass
|
|
75
75
|
class GeneralParams(DataClassORJSONMixin):
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
group_id_list: Annotated[list[str], Alias("groupIdList")]
|
|
77
|
+
group_id: Annotated[str, Alias("groupId")]
|
|
78
|
+
category_key: Annotated[Literal["LawnMower", "Tracker"], Alias("categoryKey")]
|
|
79
|
+
batch_id: Annotated[str, Alias("batchId")]
|
|
80
|
+
gmt_create: Annotated[int, Alias("gmtCreate")]
|
|
81
|
+
product_key: Annotated[str, Alias("productKey")]
|
|
82
82
|
type: str
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
device_name: Annotated[str, Alias("deviceName")]
|
|
84
|
+
iot_id: Annotated[str, Alias("iotId")]
|
|
85
|
+
check_level: Annotated[int, Alias("checkLevel")]
|
|
86
86
|
namespace: str
|
|
87
|
-
|
|
87
|
+
tenant_id: Annotated[str, Alias("tenantId")]
|
|
88
88
|
name: str
|
|
89
|
-
|
|
89
|
+
thing_type: Annotated[Literal["DEVICE"], Alias("thingType")]
|
|
90
90
|
time: int
|
|
91
|
-
|
|
91
|
+
tenant_instance_id: Annotated[str, Alias("tenantInstanceId")]
|
|
92
92
|
value: Any
|
|
93
93
|
|
|
94
|
+
# Optional fields
|
|
94
95
|
identifier: str | None = None
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
check_failed_data: Annotated[dict | None, Alias("checkFailedData")] = None
|
|
97
|
+
_tenant_id: Annotated[str | None, Alias("_tenantId")] = None
|
|
98
|
+
generate_time: Annotated[int | None, Alias("generateTime")] = None
|
|
99
|
+
jmsx_delivery_count: Annotated[int | None, Alias("JMSXDeliveryCount")] = None
|
|
99
100
|
qos: int | None = None
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
request_id: Annotated[str | None, Alias("requestId")] = None
|
|
102
|
+
_category_key: Annotated[str | None, Alias("_categoryKey")] = None
|
|
103
|
+
device_type: Annotated[str | None, Alias("deviceType")] = None
|
|
104
|
+
_trace_id: Annotated[str | None, Alias("_traceId")] = None
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
@dataclass
|
|
@@ -196,3 +197,27 @@ class ThingEventMessage(DataClassORJSONMixin):
|
|
|
196
197
|
raise ValueError(f"Unknown identifier: {identifier} {params_dict}")
|
|
197
198
|
|
|
198
199
|
return cls(method=method, id=event_id, params=params_obj, version=version)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass
|
|
203
|
+
class MammotionProtoMsgParams(DataClassORJSONMixin, SerializableType):
|
|
204
|
+
value: DeviceProtobufMsgEventValue
|
|
205
|
+
iot_id: str = ""
|
|
206
|
+
product_key: str = ""
|
|
207
|
+
device_name: str = ""
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def _deserialize(cls, d: dict[str, Any]) -> "MammotionProtoMsgParams":
|
|
211
|
+
"""Override from_dict to allow dict manipulation before conversion."""
|
|
212
|
+
proto: str = d["content"]
|
|
213
|
+
|
|
214
|
+
return cls(value=DeviceProtobufMsgEventValue(content=proto))
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass
|
|
218
|
+
class MammotionEventMessage(DataClassORJSONMixin):
|
|
219
|
+
id: str
|
|
220
|
+
version: str
|
|
221
|
+
sys: dict
|
|
222
|
+
params: MammotionProtoMsgParams
|
|
223
|
+
method: str
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
from mashumaro.config import BaseConfig
|
|
5
|
+
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
6
|
+
from mashumaro.types import Alias
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class FirmwareInfo(DataClassORJSONMixin):
|
|
11
|
+
t: str
|
|
12
|
+
c: str
|
|
13
|
+
v: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DeviceVersionInfo(DataClassORJSONMixin):
|
|
18
|
+
dev_ver: Annotated[str, Alias("devVer")]
|
|
19
|
+
whole: int
|
|
20
|
+
fw_info: Annotated[list[FirmwareInfo], Alias("fwInfo")]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Coordinate(DataClassORJSONMixin):
|
|
25
|
+
lon: float
|
|
26
|
+
lat: float
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class InternalNavigation(DataClassORJSONMixin):
|
|
31
|
+
nav: Annotated[str, Alias("NAV")]
|
|
32
|
+
pau: Annotated[str, Alias("Pau")]
|
|
33
|
+
r_pau: Annotated[str, Alias("rPau")]
|
|
34
|
+
mcu: Annotated[str, Alias("MCU")]
|
|
35
|
+
app: Annotated[str, Alias("APP")]
|
|
36
|
+
w_slp: Annotated[str, Alias("wSlp")]
|
|
37
|
+
i_slp: Annotated[str, Alias("iSlp")]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class BandwidthTraffic(DataClassORJSONMixin):
|
|
42
|
+
iot: Annotated[str, Alias("IoT")]
|
|
43
|
+
roi: Annotated[str, Alias("RoI")]
|
|
44
|
+
fpv: Annotated[str, Alias("FPV")]
|
|
45
|
+
inav: InternalNavigation
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class TrafficPeriod(DataClassORJSONMixin):
|
|
50
|
+
r: str
|
|
51
|
+
t: str
|
|
52
|
+
s: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class TrafficData(DataClassORJSONMixin):
|
|
57
|
+
upt: str
|
|
58
|
+
hour: Annotated[dict[str, TrafficPeriod], Alias("Hour")]
|
|
59
|
+
day: Annotated[dict[str, TrafficPeriod], Alias("Day")]
|
|
60
|
+
mon: Annotated[dict[str, TrafficPeriod], Alias("Mon")]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class NetworkInfo(DataClassORJSONMixin):
|
|
65
|
+
ssid: str
|
|
66
|
+
ip: str
|
|
67
|
+
wifi_sta_mac: str
|
|
68
|
+
wifi_rssi: int
|
|
69
|
+
wifi_available: int
|
|
70
|
+
bt_mac: str
|
|
71
|
+
mnet_model: str
|
|
72
|
+
imei: str
|
|
73
|
+
fw_ver: str
|
|
74
|
+
sim: str
|
|
75
|
+
imsi: str
|
|
76
|
+
iccid: str
|
|
77
|
+
sim_source: str
|
|
78
|
+
mnet_rssi: int
|
|
79
|
+
signal: int
|
|
80
|
+
mnet_link: int
|
|
81
|
+
mnet_option: str
|
|
82
|
+
mnet_ip: str
|
|
83
|
+
mnet_reg: str
|
|
84
|
+
mnet_rsrp: str
|
|
85
|
+
mnet_snr: str
|
|
86
|
+
mnet_enable: int
|
|
87
|
+
apn_num: int
|
|
88
|
+
apn_info: str
|
|
89
|
+
apn_cid: int
|
|
90
|
+
used_net: int
|
|
91
|
+
hub_reset: int
|
|
92
|
+
mnet_dis: int
|
|
93
|
+
airplane_times: int
|
|
94
|
+
lsusb_num: int
|
|
95
|
+
b_tra: Annotated[BandwidthTraffic, Alias("bTra")]
|
|
96
|
+
bw_tra: Annotated[BandwidthTraffic, Alias("bwTra")]
|
|
97
|
+
mnet_rx: str
|
|
98
|
+
mnet_tx: str
|
|
99
|
+
m_tra: Annotated[TrafficData, Alias("mTra")]
|
|
100
|
+
mnet_uniot: int
|
|
101
|
+
mnet_un_getiot: int
|
|
102
|
+
ssh_flag: str
|
|
103
|
+
mileage: str
|
|
104
|
+
work_time: str
|
|
105
|
+
wt_sec: int
|
|
106
|
+
bat_cycles: str
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class DeviceOtherInfo(DataClassORJSONMixin):
|
|
111
|
+
soc_up_time: Annotated[int, Alias("socUpTime")]
|
|
112
|
+
mcu_up_time: Annotated[int, Alias("mcuUpTime")]
|
|
113
|
+
soc_loads: Annotated[str, Alias("socLoads")]
|
|
114
|
+
soc_mem_free: Annotated[int, Alias("socMemFree")]
|
|
115
|
+
soc_mem_total: Annotated[int, Alias("socMemTotal")]
|
|
116
|
+
soc_mmc_life_time: Annotated[int, Alias("socMmcLifeTime")]
|
|
117
|
+
usb_dis_cnt: Annotated[int, Alias("usbDisCnt")]
|
|
118
|
+
soc_pstore: Annotated[int, Alias("socPstore")]
|
|
119
|
+
soc_coredump: Annotated[int, Alias("socCoredump")]
|
|
120
|
+
soc_tmp: Annotated[int, Alias("socTmp")]
|
|
121
|
+
mc_mcu: Annotated[str, Alias("mcMcu")]
|
|
122
|
+
tilt_degree: str
|
|
123
|
+
i_msg_free: Annotated[int, Alias("iMsgFree")]
|
|
124
|
+
i_msg_limit: Annotated[int, Alias("iMsgLimit")]
|
|
125
|
+
i_msg_raw: Annotated[int, Alias("iMsgRaw")]
|
|
126
|
+
i_msg_prop: Annotated[int, Alias("iMsgprop")]
|
|
127
|
+
i_msg_serv: Annotated[int, Alias("iMsgServ")]
|
|
128
|
+
i_msg_info: Annotated[int, Alias("iMsgInfo")]
|
|
129
|
+
i_msg_warn: Annotated[int, Alias("iMsgWarn")]
|
|
130
|
+
i_msg_fault: Annotated[int, Alias("iMsgFault")]
|
|
131
|
+
i_msg_ota_stage: Annotated[int, Alias("iMsgOtaStage")]
|
|
132
|
+
i_msg_protobuf: Annotated[int, Alias("iMsgProtobuf")]
|
|
133
|
+
i_msg_notify: Annotated[int, Alias("iMsgNotify")]
|
|
134
|
+
i_msg_log_prog: Annotated[int, Alias("iMsgLogProg")]
|
|
135
|
+
i_msg_biz_req: Annotated[int, Alias("iMsgBizReq")]
|
|
136
|
+
i_msg_cfg_req: Annotated[int, Alias("iMsgCfgReq")]
|
|
137
|
+
i_msg_voice: Annotated[int, Alias("iMsgVoice")]
|
|
138
|
+
i_msg_warn_code: Annotated[int, Alias("iMsgWarnCode")]
|
|
139
|
+
pb_net: Annotated[int, Alias("pbNet")]
|
|
140
|
+
pb_sys: Annotated[int, Alias("pbSys")]
|
|
141
|
+
pb_nav: Annotated[int, Alias("pbNav")]
|
|
142
|
+
pb_local: Annotated[int, Alias("pbLocal")]
|
|
143
|
+
pb_plan: Annotated[int, Alias("pbPlan")]
|
|
144
|
+
pb_e_drv: Annotated[int, Alias("pbEDrv")]
|
|
145
|
+
pb_e_sys: Annotated[int, Alias("pbESys")]
|
|
146
|
+
pb_midware: Annotated[int, Alias("pbMidware")]
|
|
147
|
+
pb_ota: Annotated[int, Alias("pbOta")]
|
|
148
|
+
pb_appl: Annotated[int, Alias("pbAppl")]
|
|
149
|
+
pb_mul: Annotated[int, Alias("pbMul")]
|
|
150
|
+
pb_other: Annotated[int, Alias("pbOther")]
|
|
151
|
+
lora_connect: Annotated[int, Alias("loraConnect")]
|
|
152
|
+
base_status: Annotated[int, Alias("Basestatus")]
|
|
153
|
+
mqtt_rtk_switch: int
|
|
154
|
+
mqtt_rtk_channel: int
|
|
155
|
+
mqtt_rtk_status: int
|
|
156
|
+
mqtt_rtcm_cnt: int
|
|
157
|
+
mqtt_conn_cnt: int
|
|
158
|
+
mqtt_disconn_cnt: int
|
|
159
|
+
mqtt_rtk_hb_flag: int
|
|
160
|
+
mqtt_rtk_hb_count: int
|
|
161
|
+
mqtt_start_cnt: int
|
|
162
|
+
mqtt_close_cnt: int
|
|
163
|
+
mqtt_rtk_ssl_fail: int
|
|
164
|
+
mqtt_rtk_wifi_config: int
|
|
165
|
+
nrtk_svc_prov: int
|
|
166
|
+
nrtk_svc_err: int
|
|
167
|
+
base_stn_id: int
|
|
168
|
+
rtk_status: int
|
|
169
|
+
charge_status: int
|
|
170
|
+
chassis_state: int
|
|
171
|
+
nav: str
|
|
172
|
+
ins_fusion: str
|
|
173
|
+
perception: str
|
|
174
|
+
vision_proxy: str
|
|
175
|
+
vslam_vio: str
|
|
176
|
+
iot_con_timeout: int
|
|
177
|
+
iot_con: int
|
|
178
|
+
iot_con_fail_max: str
|
|
179
|
+
iot_con_fail_min: Annotated[str, Alias("iot_con__fail_min")]
|
|
180
|
+
iot_url_count: int
|
|
181
|
+
iot_url_max: str
|
|
182
|
+
iot_url_min: str
|
|
183
|
+
iot_cn: int
|
|
184
|
+
iot_ap: int
|
|
185
|
+
iot_us: int
|
|
186
|
+
iot_eu: int
|
|
187
|
+
task_area: float
|
|
188
|
+
task_count: int
|
|
189
|
+
task_hash: str
|
|
190
|
+
systemio_boot_time: Annotated[str, Alias("systemioBootTime")]
|
|
191
|
+
dds_no_gdc: int
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@dataclass
|
|
195
|
+
class CheckData(DataClassORJSONMixin):
|
|
196
|
+
result: str
|
|
197
|
+
error: Annotated[list[int], Alias("Error")]
|
|
198
|
+
warn: Annotated[list[int], Alias("Warn")]
|
|
199
|
+
ok: Annotated[list[int], Alias("OK")]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass
|
|
203
|
+
class DeviceProperties(DataClassORJSONMixin):
|
|
204
|
+
device_state: Annotated[int, Alias("deviceState")]
|
|
205
|
+
battery_percentage: Annotated[int, Alias("batteryPercentage")]
|
|
206
|
+
device_version: Annotated[str, Alias("deviceVersion")]
|
|
207
|
+
knife_height: Annotated[int, Alias("knifeHeight")]
|
|
208
|
+
lora_general_config: Annotated[str, Alias("loraGeneralConfig")]
|
|
209
|
+
ext_mod: Annotated[str, Alias("extMod")]
|
|
210
|
+
int_mod: Annotated[str, Alias("intMod")]
|
|
211
|
+
iot_state: Annotated[int, Alias("iotState")]
|
|
212
|
+
iot_msg_total: Annotated[int, Alias("iotMsgTotal")]
|
|
213
|
+
iot_msg_hz: Annotated[int, Alias("iotMsgHz")]
|
|
214
|
+
lt_mr_mod: Annotated[str, Alias("ltMrMod")]
|
|
215
|
+
rt_mr_mod: Annotated[str, Alias("rtMrMod")]
|
|
216
|
+
bms_hardware_version: Annotated[str, Alias("bmsHardwareVersion")]
|
|
217
|
+
stm32_h7_version: Annotated[str, Alias("stm32H7Version")]
|
|
218
|
+
left_motor_version: Annotated[str, Alias("leftMotorVersion")]
|
|
219
|
+
right_motor_version: Annotated[str, Alias("rightMotorVersion")]
|
|
220
|
+
rtk_version: Annotated[str, Alias("rtkVersion")]
|
|
221
|
+
bms_version: Annotated[str, Alias("bmsVersion")]
|
|
222
|
+
mc_boot_version: Annotated[str, Alias("mcBootVersion")]
|
|
223
|
+
left_motor_boot_version: Annotated[str, Alias("leftMotorBootVersion")]
|
|
224
|
+
right_motor_boot_version: Annotated[str, Alias("rightMotorBootVersion")]
|
|
225
|
+
|
|
226
|
+
# Nested JSON objects
|
|
227
|
+
device_version_info: Annotated[DeviceVersionInfo, Alias("deviceVersionInfo")]
|
|
228
|
+
coordinate: Coordinate
|
|
229
|
+
device_other_info: Annotated[DeviceOtherInfo, Alias("deviceOtherInfo")]
|
|
230
|
+
network_info: Annotated[NetworkInfo, Alias("networkInfo")]
|
|
231
|
+
check_data: Annotated[CheckData, Alias("checkData")]
|
|
232
|
+
iot_id: str = ""
|
|
233
|
+
|
|
234
|
+
class Config(BaseConfig):
|
|
235
|
+
# Custom deserializer for nested JSON strings
|
|
236
|
+
serialization_strategy = {
|
|
237
|
+
DeviceVersionInfo: {
|
|
238
|
+
"deserialize": lambda x: DeviceVersionInfo.from_json(x) if isinstance(x, str) else x,
|
|
239
|
+
"serialize": lambda x: x.to_json() if hasattr(x, "to_json") else x,
|
|
240
|
+
},
|
|
241
|
+
Coordinate: {
|
|
242
|
+
"deserialize": lambda x: Coordinate.from_json(x) if isinstance(x, str) else x,
|
|
243
|
+
"serialize": lambda x: x.to_json() if hasattr(x, "to_json") else x,
|
|
244
|
+
},
|
|
245
|
+
DeviceOtherInfo: {
|
|
246
|
+
"deserialize": lambda x: DeviceOtherInfo.from_json(x) if isinstance(x, str) else x,
|
|
247
|
+
"serialize": lambda x: x.to_json() if hasattr(x, "to_json") else x,
|
|
248
|
+
},
|
|
249
|
+
NetworkInfo: {
|
|
250
|
+
"deserialize": lambda x: NetworkInfo.from_json(x) if isinstance(x, str) else x,
|
|
251
|
+
"serialize": lambda x: x.to_json() if hasattr(x, "to_json") else x,
|
|
252
|
+
},
|
|
253
|
+
CheckData: {
|
|
254
|
+
"deserialize": lambda x: CheckData.from_json(x) if isinstance(x, str) else x,
|
|
255
|
+
"serialize": lambda x: x.to_json() if hasattr(x, "to_json") else x,
|
|
256
|
+
},
|
|
257
|
+
}
|