pymammotion 0.5.69__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.
Files changed (154) hide show
  1. pymammotion/__init__.py +53 -0
  2. pymammotion/agora/__init__.py +0 -0
  3. pymammotion/agora/agora_api.py +755 -0
  4. pymammotion/agora/agora_rtc_capabilities.py +748 -0
  5. pymammotion/agora/agora_websockets.py +1175 -0
  6. pymammotion/aliyun/__init__.py +1 -0
  7. pymammotion/aliyun/client.py +235 -0
  8. pymammotion/aliyun/cloud_gateway.py +982 -0
  9. pymammotion/aliyun/model/aep_response.py +21 -0
  10. pymammotion/aliyun/model/connect_response.py +51 -0
  11. pymammotion/aliyun/model/dev_by_account_response.py +195 -0
  12. pymammotion/aliyun/model/login_by_oauth_response.py +64 -0
  13. pymammotion/aliyun/model/regions_response.py +29 -0
  14. pymammotion/aliyun/model/session_by_authcode_response.py +19 -0
  15. pymammotion/aliyun/model/thing_response.py +12 -0
  16. pymammotion/aliyun/regions.py +62 -0
  17. pymammotion/aliyun/tea/core.py +297 -0
  18. pymammotion/aliyun/tmp_constant.py +171 -0
  19. pymammotion/bluetooth/__init__.py +1 -0
  20. pymammotion/bluetooth/ble.py +62 -0
  21. pymammotion/bluetooth/ble_message.py +676 -0
  22. pymammotion/bluetooth/const.py +27 -0
  23. pymammotion/bluetooth/data/__init__.py +0 -0
  24. pymammotion/bluetooth/data/convert.py +25 -0
  25. pymammotion/bluetooth/data/framectrldata.py +40 -0
  26. pymammotion/bluetooth/data/notifydata.py +62 -0
  27. pymammotion/bluetooth/model/__init__.py +0 -0
  28. pymammotion/bluetooth/model/atomic_integer.py +54 -0
  29. pymammotion/const.py +13 -0
  30. pymammotion/data/__init__.py +0 -0
  31. pymammotion/data/model/__init__.py +8 -0
  32. pymammotion/data/model/account.py +8 -0
  33. pymammotion/data/model/device.py +192 -0
  34. pymammotion/data/model/device_config.py +72 -0
  35. pymammotion/data/model/device_info.py +60 -0
  36. pymammotion/data/model/device_limits.py +49 -0
  37. pymammotion/data/model/enums.py +77 -0
  38. pymammotion/data/model/errors.py +12 -0
  39. pymammotion/data/model/events.py +14 -0
  40. pymammotion/data/model/generate_geojson.py +565 -0
  41. pymammotion/data/model/generate_route_information.py +26 -0
  42. pymammotion/data/model/hash_list.py +475 -0
  43. pymammotion/data/model/location.py +36 -0
  44. pymammotion/data/model/mowing_modes.py +77 -0
  45. pymammotion/data/model/rapid_state.py +45 -0
  46. pymammotion/data/model/raw_data.py +215 -0
  47. pymammotion/data/model/region_data.py +102 -0
  48. pymammotion/data/model/report_info.py +182 -0
  49. pymammotion/data/model/work.py +27 -0
  50. pymammotion/data/mower_state_manager.py +369 -0
  51. pymammotion/data/mqtt/__init__.py +1 -0
  52. pymammotion/data/mqtt/event.py +227 -0
  53. pymammotion/data/mqtt/mammotion_properties.py +276 -0
  54. pymammotion/data/mqtt/properties.py +203 -0
  55. pymammotion/data/mqtt/status.py +57 -0
  56. pymammotion/event/__init__.py +6 -0
  57. pymammotion/event/event.py +96 -0
  58. pymammotion/homeassistant/__init__.py +3 -0
  59. pymammotion/homeassistant/mower_api.py +514 -0
  60. pymammotion/homeassistant/rtk_api.py +54 -0
  61. pymammotion/http/__init__.py +0 -0
  62. pymammotion/http/encryption.py +220 -0
  63. pymammotion/http/http.py +673 -0
  64. pymammotion/http/model/__init__.py +0 -0
  65. pymammotion/http/model/camera_stream.py +31 -0
  66. pymammotion/http/model/http.py +249 -0
  67. pymammotion/http/model/response_factory.py +61 -0
  68. pymammotion/http/model/rtk.py +16 -0
  69. pymammotion/mammotion/__init__.py +0 -0
  70. pymammotion/mammotion/commands/__init__.py +0 -0
  71. pymammotion/mammotion/commands/abstract_message.py +24 -0
  72. pymammotion/mammotion/commands/mammotion_command.py +81 -0
  73. pymammotion/mammotion/commands/messages/__init__.py +0 -0
  74. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  75. pymammotion/mammotion/commands/messages/driver.py +122 -0
  76. pymammotion/mammotion/commands/messages/media.py +87 -0
  77. pymammotion/mammotion/commands/messages/navigation.py +564 -0
  78. pymammotion/mammotion/commands/messages/network.py +205 -0
  79. pymammotion/mammotion/commands/messages/ota.py +38 -0
  80. pymammotion/mammotion/commands/messages/system.py +330 -0
  81. pymammotion/mammotion/commands/messages/video.py +33 -0
  82. pymammotion/mammotion/control/__init__.py +0 -0
  83. pymammotion/mammotion/control/joystick.py +145 -0
  84. pymammotion/mammotion/devices/__init__.py +29 -0
  85. pymammotion/mammotion/devices/base.py +163 -0
  86. pymammotion/mammotion/devices/mammotion.py +571 -0
  87. pymammotion/mammotion/devices/mammotion_bluetooth.py +496 -0
  88. pymammotion/mammotion/devices/mammotion_cloud.py +355 -0
  89. pymammotion/mammotion/devices/mammotion_mower_ble.py +48 -0
  90. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  91. pymammotion/mammotion/devices/managers/managers.py +81 -0
  92. pymammotion/mammotion/devices/mower_device.py +120 -0
  93. pymammotion/mammotion/devices/mower_manager.py +107 -0
  94. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  95. pymammotion/mammotion/devices/rtk_cloud.py +115 -0
  96. pymammotion/mammotion/devices/rtk_device.py +50 -0
  97. pymammotion/mammotion/devices/rtk_manager.py +125 -0
  98. pymammotion/mqtt/__init__.py +6 -0
  99. pymammotion/mqtt/aliyun_mqtt.py +237 -0
  100. pymammotion/mqtt/linkkit/__init__.py +5 -0
  101. pymammotion/mqtt/linkkit/h2client.py +585 -0
  102. pymammotion/mqtt/linkkit/linkkit.py +3025 -0
  103. pymammotion/mqtt/mammotion_future.py +26 -0
  104. pymammotion/mqtt/mammotion_mqtt.py +214 -0
  105. pymammotion/mqtt/mqtt_models.py +66 -0
  106. pymammotion/proto/__init__.py +4841 -0
  107. pymammotion/proto/basestation.proto +51 -0
  108. pymammotion/proto/basestation_pb2.py +35 -0
  109. pymammotion/proto/basestation_pb2.pyi +89 -0
  110. pymammotion/proto/common.proto +7 -0
  111. pymammotion/proto/common_pb2.py +25 -0
  112. pymammotion/proto/common_pb2.pyi +13 -0
  113. pymammotion/proto/dev_net.proto +321 -0
  114. pymammotion/proto/dev_net_pb2.py +111 -0
  115. pymammotion/proto/dev_net_pb2.pyi +515 -0
  116. pymammotion/proto/luba_msg.proto +76 -0
  117. pymammotion/proto/luba_msg_pb2.py +41 -0
  118. pymammotion/proto/luba_msg_pb2.pyi +97 -0
  119. pymammotion/proto/luba_mul.proto +129 -0
  120. pymammotion/proto/luba_mul_pb2.py +61 -0
  121. pymammotion/proto/luba_mul_pb2.pyi +178 -0
  122. pymammotion/proto/mctrl_driver.proto +107 -0
  123. pymammotion/proto/mctrl_driver_pb2.py +57 -0
  124. pymammotion/proto/mctrl_driver_pb2.pyi +167 -0
  125. pymammotion/proto/mctrl_nav.proto +591 -0
  126. pymammotion/proto/mctrl_nav_pb2.py +136 -0
  127. pymammotion/proto/mctrl_nav_pb2.pyi +1067 -0
  128. pymammotion/proto/mctrl_ota.proto +80 -0
  129. pymammotion/proto/mctrl_ota_pb2.py +45 -0
  130. pymammotion/proto/mctrl_ota_pb2.pyi +128 -0
  131. pymammotion/proto/mctrl_pept.proto +34 -0
  132. pymammotion/proto/mctrl_pept_pb2.py +33 -0
  133. pymammotion/proto/mctrl_pept_pb2.pyi +58 -0
  134. pymammotion/proto/mctrl_sys.proto +741 -0
  135. pymammotion/proto/mctrl_sys_pb2.py +206 -0
  136. pymammotion/proto/mctrl_sys_pb2.pyi +1213 -0
  137. pymammotion/proto/message_pool.py +3 -0
  138. pymammotion/proto/py.typed +0 -0
  139. pymammotion/py.typed +0 -0
  140. pymammotion/utility/constant/__init__.py +3 -0
  141. pymammotion/utility/constant/device_constant.py +315 -0
  142. pymammotion/utility/conversions.py +5 -0
  143. pymammotion/utility/datatype_converter.py +124 -0
  144. pymammotion/utility/device_config.py +755 -0
  145. pymammotion/utility/device_type.py +489 -0
  146. pymammotion/utility/map.py +259 -0
  147. pymammotion/utility/movement.py +18 -0
  148. pymammotion/utility/mur_mur_hash.py +159 -0
  149. pymammotion/utility/periodic.py +106 -0
  150. pymammotion/utility/rocker_util.py +194 -0
  151. pymammotion-0.5.69.dist-info/METADATA +93 -0
  152. pymammotion-0.5.69.dist-info/RECORD +154 -0
  153. pymammotion-0.5.69.dist-info/WHEEL +4 -0
  154. pymammotion-0.5.69.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,475 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import IntEnum
3
+ from typing import Any
4
+
5
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
6
+
7
+ from pymammotion.proto import NavGetCommDataAck, NavGetHashListAck, SvgMessageAckT
8
+ from pymammotion.utility.mur_mur_hash import MurMurHashUtil
9
+
10
+
11
+ class PathType(IntEnum):
12
+ """Path types for common data."""
13
+
14
+ AREA = 0
15
+ OBSTACLE = 1
16
+ PATH = 2
17
+ LINE = 10
18
+ DUMP = 12
19
+ SVG = 13
20
+ VISUAL_SAFETY_ZONE = 25
21
+
22
+
23
+ @dataclass
24
+ class CommDataCouple:
25
+ x: float = 0.0
26
+ y: float = 0.0
27
+
28
+
29
+ @dataclass
30
+ class AreaLabelName(DataClassORJSONMixin):
31
+ label: str = ""
32
+
33
+
34
+ @dataclass
35
+ class NavNameTime(DataClassORJSONMixin):
36
+ name: str = ""
37
+ create_time: int = 0
38
+ modify_time: int = 0
39
+
40
+
41
+ @dataclass
42
+ class NavGetCommData(DataClassORJSONMixin):
43
+ pver: int = 0
44
+ sub_cmd: int = 0
45
+ result: int = 0
46
+ action: int = 0
47
+ type: int = 0
48
+ hash: int = 0
49
+ paternal_hash_a: int = 0
50
+ paternal_hash_b: int = 0
51
+ total_frame: int = 0
52
+ current_frame: int = 0
53
+ data_hash: int = 0
54
+ data_len: int = 0
55
+ data_couple: list["CommDataCouple"] = field(default_factory=list)
56
+ reserved: str = ""
57
+ name_time: NavNameTime = field(default_factory=NavNameTime)
58
+
59
+
60
+ @dataclass
61
+ class MowPathPacket(DataClassORJSONMixin):
62
+ path_hash: int = 0
63
+ path_type: int = 0
64
+ path_total: int = 0
65
+ path_cur: int = 0
66
+ zone_hash: int = 0
67
+ data_couple: list["CommDataCouple"] = field(default_factory=list)
68
+
69
+
70
+ @dataclass
71
+ class MowPath(DataClassORJSONMixin):
72
+ pver: int = 0
73
+ sub_cmd: int = 0
74
+ result: int = 0
75
+ area: int = 0
76
+ time: int = 0
77
+ total_frame: int = 0
78
+ current_frame: int = 0
79
+ total_path_num: int = 0
80
+ valid_path_num: int = 0
81
+ data_hash: int = 0
82
+ transaction_id: int = 0
83
+ reserved: list[int] = field(default_factory=list)
84
+ data_len: int = 0
85
+ path_packets: list[MowPathPacket] = field(default_factory=list)
86
+
87
+
88
+ @dataclass
89
+ class SvgMessageData(DataClassORJSONMixin):
90
+ x_move: float = 0.0
91
+ y_move: float = 0.0
92
+ scale: float = 0.0
93
+ rotate: float = 0.0
94
+ base_width_m: float = 0.0
95
+ base_width_pix: int = 0
96
+ base_height_m: float = 0.0
97
+ base_height_pix: int = 0
98
+ name_count: int = 0
99
+ data_count: int = 0
100
+ hide_svg: bool = False
101
+ svg_file_name: str = ""
102
+ svg_file_data: str = ""
103
+
104
+
105
+ @dataclass
106
+ class SvgMessage(DataClassORJSONMixin):
107
+ pver: int = 0
108
+ sub_cmd: int = 0
109
+ total_frame: int = 0
110
+ current_frame: int = 0
111
+ data_hash: int = 0
112
+ paternal_hash_a: int = 0
113
+ type: int = 0
114
+ result: int = 0
115
+ svg_message: "SvgMessageData" = field(default_factory=SvgMessageData)
116
+
117
+
118
+ @dataclass
119
+ class FrameList(DataClassORJSONMixin):
120
+ total_frame: int = 0
121
+ sub_cmd: int = 0
122
+ data: list[NavGetCommData | SvgMessage] = field(default_factory=list)
123
+
124
+
125
+ @dataclass
126
+ class Plan(DataClassORJSONMixin):
127
+ pver: int = 0
128
+ sub_cmd: int = 2
129
+ area: int = 0
130
+ work_time: int = 0
131
+ version: str = ""
132
+ id: str = ""
133
+ user_id: str = ""
134
+ device_id: str = ""
135
+ plan_id: str = ""
136
+ task_id: str = ""
137
+ job_id: str = ""
138
+ start_time: str = ""
139
+ end_time: str = ""
140
+ week: int = 0
141
+ knife_height: int = 0
142
+ model: int = 0
143
+ edge_mode: int = 0
144
+ required_time: int = 0
145
+ route_angle: int = 0
146
+ route_model: int = 0
147
+ route_spacing: int = 0
148
+ ultrasonic_barrier: int = 0
149
+ total_plan_num: int = 0
150
+ plan_index: int = 0
151
+ result: int = 0
152
+ speed: float = 0.0
153
+ task_name: str = ""
154
+ job_name: str = ""
155
+ zone_hashs: list[int] = field(default_factory=list)
156
+ reserved: str = ""
157
+ start_date: str = ""
158
+ end_date: str = ""
159
+ trigger_type: int = 0
160
+ day: int = 0
161
+ weeks: list[int] = field(default_factory=list)
162
+ remained_seconds: int = 0
163
+ toward_mode: int = 0
164
+ toward_included_angle: int = 0
165
+
166
+
167
+ @dataclass(eq=False, repr=False)
168
+ class NavGetHashListData(DataClassORJSONMixin):
169
+ """Dataclass for NavGetHashListData."""
170
+
171
+ pver: int = 0
172
+ sub_cmd: int = 0
173
+ total_frame: int = 0
174
+ current_frame: int = 0
175
+ data_hash: int = 0
176
+ hash_len: int = 0
177
+ reserved: str = ""
178
+ result: int = 0
179
+ data_couple: list[int] = field(default_factory=list)
180
+
181
+
182
+ @dataclass
183
+ class RootHashList(DataClassORJSONMixin):
184
+ total_frame: int = 0
185
+ sub_cmd: int = 0
186
+ data: list[NavGetHashListData] = field(default_factory=list)
187
+
188
+
189
+ @dataclass
190
+ class AreaHashNameList(DataClassORJSONMixin):
191
+ """Wrapper so we can serialize to and from dict."""
192
+
193
+ name: str
194
+ hash: int
195
+
196
+
197
+ @dataclass
198
+ class HashList(DataClassORJSONMixin):
199
+ """stores our map data.
200
+ [hashID, FrameList].
201
+ hashlist for all our hashIDs for verification
202
+ """
203
+
204
+ root_hash_lists: list[RootHashList] = field(default_factory=list)
205
+ area: dict[int, FrameList] = field(default_factory=dict) # type 0
206
+ path: dict[int, FrameList] = field(default_factory=dict) # type 2
207
+ obstacle: dict[int, FrameList] = field(default_factory=dict) # type 1
208
+ dump: dict[int, FrameList] = field(default_factory=dict) # type 12? / sub cmd 4
209
+ svg: dict[int, FrameList] = field(default_factory=dict) # type 13
210
+ line: dict[int, FrameList] = field(default_factory=dict) # type 10 possibly breakpoint? / sub cmd 3
211
+ visual_safety_zone: dict[int, FrameList] = field(default_factory=dict) # type 25
212
+ plan: dict[str, Plan] = field(default_factory=dict)
213
+ area_name: list[AreaHashNameList] = field(default_factory=list)
214
+ current_mow_path: dict[int, dict[int, MowPath]] = field(default_factory=dict)
215
+ generated_geojson: dict[str, Any] = field(default_factory=dict)
216
+ generated_mow_path_geojson: dict[str, Any] = field(default_factory=dict)
217
+
218
+ def update_hash_lists(self, hashlist: list[int], bol_hash: int | None = None) -> None:
219
+ if bol_hash:
220
+ self.invalidate_maps(bol_hash)
221
+ self.area = {hash_id: frames for hash_id, frames in self.area.items() if hash_id in hashlist}
222
+ self.path = {hash_id: frames for hash_id, frames in self.path.items() if hash_id in hashlist}
223
+ self.obstacle = {hash_id: frames for hash_id, frames in self.obstacle.items() if hash_id in hashlist}
224
+ self.dump = {hash_id: frames for hash_id, frames in self.dump.items() if hash_id in hashlist}
225
+ self.svg = {hash_id: frames for hash_id, frames in self.svg.items() if hash_id in hashlist}
226
+ self.visual_safety_zone = {
227
+ hash_id: frames for hash_id, frames in self.visual_safety_zone.items() if hash_id in hashlist
228
+ }
229
+
230
+ area_hashes = list(self.area.keys())
231
+ for hash_id, plan_task in self.plan.copy().items():
232
+ for item in plan_task.zone_hashs:
233
+ if item not in area_hashes:
234
+ self.plan.pop(hash_id)
235
+ break
236
+
237
+ self.area_name = [
238
+ area_item
239
+ for area_item in self.area_name
240
+ if area_item.hash in self.area.keys() or area_item.hash in hashlist
241
+ ]
242
+
243
+ @property
244
+ def hashlist(self) -> list[int]:
245
+ if not self.root_hash_lists:
246
+ return []
247
+ # Combine data_couple from all RootHashLists
248
+ return [i for root_list in self.root_hash_lists for obj in root_list.data for i in obj.data_couple]
249
+
250
+ @property
251
+ def area_root_hashlist(self) -> list[int]:
252
+ if not self.root_hash_lists:
253
+ return []
254
+ # Combine data_couple from all RootHashLists
255
+ return [
256
+ i
257
+ for root_list in self.root_hash_lists
258
+ for obj in root_list.data
259
+ for i in obj.data_couple
260
+ if root_list.sub_cmd == 0
261
+ ]
262
+
263
+ def missing_hashlist(self, sub_cmd: int = 0) -> list[int]:
264
+ """Return missing hashlist."""
265
+ all_hash_ids = set(self.area.keys()).union(
266
+ self.path.keys(), self.obstacle.keys(), self.dump.keys(), self.svg.keys(), self.visual_safety_zone.keys()
267
+ )
268
+ if sub_cmd == 3:
269
+ all_hash_ids = set(self.line.keys())
270
+ return [
271
+ i
272
+ for root_list in self.root_hash_lists
273
+ for obj in root_list.data
274
+ if root_list.sub_cmd == sub_cmd
275
+ for i in obj.data_couple
276
+ if i not in all_hash_ids
277
+ ]
278
+
279
+ def missing_root_hash_frame(self, hash_list: NavGetHashListAck) -> list[int]:
280
+ """Return missing root hash frame."""
281
+ target_root_list = next(
282
+ (
283
+ rhl
284
+ for rhl in self.root_hash_lists
285
+ if rhl.total_frame == hash_list.total_frame and rhl.sub_cmd == hash_list.sub_cmd
286
+ ),
287
+ None,
288
+ )
289
+ if target_root_list is None:
290
+ return []
291
+
292
+ return self.find_missing_frames(target_root_list)
293
+
294
+ def update_root_hash_list(self, hash_list: NavGetHashListData) -> None:
295
+ target_root_list = next(
296
+ (
297
+ rhl
298
+ for rhl in self.root_hash_lists
299
+ if rhl.total_frame == hash_list.total_frame and rhl.sub_cmd == hash_list.sub_cmd
300
+ ),
301
+ None,
302
+ )
303
+
304
+ if target_root_list is None:
305
+ # Create new RootHashList if none exists for this total_frame
306
+ new_root_list = RootHashList(total_frame=hash_list.total_frame, sub_cmd=hash_list.sub_cmd, data=[hash_list])
307
+ self.root_hash_lists.append(new_root_list)
308
+ return
309
+
310
+ for index, obj in enumerate(target_root_list.data):
311
+ if obj.current_frame == hash_list.current_frame:
312
+ # Replace the item if current_frame matches
313
+ target_root_list.data[index] = hash_list
314
+ return
315
+
316
+ # If no match was found, append the new item
317
+ target_root_list.data.append(hash_list)
318
+
319
+ def missing_hash_frame(self, hash_ack: NavGetHashListAck) -> list[int]:
320
+ """Returns a combined list of all missing frames across all RootHashLists."""
321
+ missing_frames = []
322
+ filtered_lists = [rl for rl in self.root_hash_lists if rl.sub_cmd == hash_ack.sub_cmd]
323
+ for root_list in filtered_lists:
324
+ missing = self.find_missing_frames(root_list)
325
+ if missing:
326
+ missing_frames.extend(missing)
327
+ return missing_frames
328
+
329
+ def missing_frame(self, hash_data: NavGetCommDataAck | SvgMessageAckT) -> list[int]:
330
+ frame_list = self._get_frame_list_by_type_and_hash(hash_data)
331
+ return self.find_missing_frames(frame_list)
332
+
333
+ def _get_frame_list_by_type_and_hash(self, hash_data: NavGetCommDataAck | SvgMessageAckT) -> FrameList | None:
334
+ """Get the appropriate FrameList based on hash_data type and hash."""
335
+ path_type_mapping = self._get_path_type_mapping()
336
+ target_dict = path_type_mapping.get(hash_data.type)
337
+
338
+ if target_dict is None:
339
+ return None
340
+
341
+ # Handle SvgMessage with data_hash attribute
342
+ if isinstance(hash_data, SvgMessageAckT):
343
+ return target_dict.get(hash_data.data_hash)
344
+
345
+ # Handle NavGetCommDataAck with hash attribute
346
+ return target_dict.get(hash_data.hash)
347
+
348
+ def update_plan(self, plan: Plan) -> None:
349
+ if plan.total_plan_num != 0:
350
+ self.plan[plan.plan_id] = plan
351
+
352
+ def _get_path_type_mapping(self) -> dict[int, dict[int, FrameList]]:
353
+ """Return mapping of PathType to corresponding hash dictionary."""
354
+ return {
355
+ PathType.AREA: self.area,
356
+ PathType.OBSTACLE: self.obstacle,
357
+ PathType.PATH: self.path,
358
+ PathType.LINE: self.line,
359
+ PathType.DUMP: self.dump,
360
+ PathType.SVG: self.svg,
361
+ PathType.VISUAL_SAFETY_ZONE: self.visual_safety_zone,
362
+ }
363
+
364
+ def update(self, hash_data: NavGetCommData | SvgMessage) -> bool:
365
+ """Update the map data."""
366
+
367
+ if hash_data.type == PathType.AREA and isinstance(hash_data, NavGetCommData):
368
+ existing_name = next((area for area in self.area_name if area.hash == hash_data.hash), None)
369
+ if not existing_name:
370
+ name = f"area {len(self.area_name)+1}"
371
+ self.area_name.append(AreaHashNameList(name=name, hash=hash_data.hash))
372
+ result = self._add_hash_data(self.area, hash_data)
373
+ self.update_hash_lists(self.hashlist)
374
+ return result
375
+
376
+ path_type_mapping = self._get_path_type_mapping()
377
+ target_dict = path_type_mapping.get(hash_data.type)
378
+
379
+ if target_dict is not None:
380
+ return self._add_hash_data(target_dict, hash_data)
381
+
382
+ return False
383
+
384
+ def find_missing_mow_path_frames(self) -> dict[int, list[int]]:
385
+ """Find missing frames in current_mow_path grouped by transaction_id.
386
+
387
+ Returns a mapping of transaction_id -> list of missing frame numbers.
388
+ Only transaction_ids with at least one missing frame are included.
389
+ """
390
+ missing_frames: dict[int, list[int]] = {}
391
+
392
+ if not self.current_mow_path:
393
+ return missing_frames
394
+
395
+ for transaction_id, frames_by_index in self.current_mow_path.items():
396
+ if not frames_by_index:
397
+ continue
398
+
399
+ # Get total_frame from any MowPath object for this transaction_id
400
+ any_mow_path = next(iter(frames_by_index.values()))
401
+ total_frame = any_mow_path.total_frame
402
+
403
+ if total_frame == 0:
404
+ continue
405
+
406
+ expected_frames = set(range(1, total_frame + 1))
407
+ current_frames = set(frames_by_index.keys())
408
+ missing_for_transaction = sorted(expected_frames - current_frames)
409
+
410
+ if missing_for_transaction:
411
+ missing_frames[transaction_id] = missing_for_transaction
412
+
413
+ return missing_frames
414
+
415
+ def update_mow_path(self, path: MowPath) -> None:
416
+ """Update the current_mow_path with the latest MowPath data."""
417
+ # TODO check if we need to clear the current_mow_path first
418
+ transaction_id = path.transaction_id
419
+ if transaction_id not in self.current_mow_path:
420
+ self.current_mow_path[transaction_id] = {}
421
+ self.current_mow_path[transaction_id][path.current_frame] = path
422
+
423
+ @staticmethod
424
+ def find_missing_frames(frame_list: FrameList | RootHashList | None) -> list[int]:
425
+ if frame_list is None:
426
+ return []
427
+
428
+ if frame_list.total_frame == len(frame_list.data):
429
+ return []
430
+ number_list = list(range(1, frame_list.total_frame + 1))
431
+
432
+ current_frames = {frame.current_frame for frame in frame_list.data}
433
+ missing_numbers = [num for num in number_list if num not in current_frames]
434
+ return missing_numbers
435
+
436
+ @staticmethod
437
+ def _add_hash_data(hash_dict: dict[int, FrameList], hash_data: NavGetCommData | SvgMessage) -> bool:
438
+ if isinstance(hash_data, SvgMessage):
439
+ if hash_dict.get(hash_data.data_hash, None) is None:
440
+ hash_dict[hash_data.data_hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
441
+ return True
442
+
443
+ if hash_data not in hash_dict[hash_data.data_hash].data:
444
+ exists = next(
445
+ (
446
+ rhl
447
+ for rhl in hash_dict[hash_data.data_hash].data
448
+ if rhl.current_frame == hash_data.current_frame
449
+ ),
450
+ None,
451
+ )
452
+ if exists:
453
+ return True
454
+ hash_dict[hash_data.data_hash].data.append(hash_data)
455
+ return True
456
+ return False
457
+
458
+ if hash_dict.get(hash_data.hash, None) is None:
459
+ hash_dict[hash_data.hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
460
+ return True
461
+
462
+ if hash_data not in hash_dict[hash_data.hash].data:
463
+ exists = next(
464
+ (rhl for rhl in hash_dict[hash_data.hash].data if rhl.current_frame == hash_data.current_frame),
465
+ None,
466
+ )
467
+ if exists:
468
+ return True
469
+ hash_dict[hash_data.hash].data.append(hash_data)
470
+ return True
471
+ return False
472
+
473
+ def invalidate_maps(self, bol_hash: int) -> None:
474
+ if MurMurHashUtil.hash_unsigned_list(self.area_root_hashlist) != bol_hash:
475
+ self.root_hash_lists = []
@@ -0,0 +1,36 @@
1
+ """Contains RTK models for robot location and RTK positions."""
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
6
+
7
+
8
+ @dataclass
9
+ class LocationPoint(DataClassORJSONMixin):
10
+ """Returns a lat long."""
11
+
12
+ latitude: float = 0.0
13
+ longitude: float = 0.0
14
+
15
+ def __init__(self, latitude: float = 0.0, longitude: float = 0.0) -> None:
16
+ self.latitude = latitude
17
+ self.longitude = longitude
18
+
19
+
20
+ @dataclass
21
+ class Dock(LocationPoint):
22
+ """Stores robot dock position."""
23
+
24
+ rotation: int = 0
25
+
26
+
27
+ @dataclass
28
+ class Location(DataClassORJSONMixin):
29
+ """Stores/retrieves RTK GPS data."""
30
+
31
+ device: LocationPoint = field(default_factory=LocationPoint)
32
+ RTK: LocationPoint = field(default_factory=LocationPoint)
33
+ dock: Dock = field(default_factory=Dock)
34
+ position_type: int = 0
35
+ orientation: int = 0 # 360 degree rotation +-
36
+ work_zone: int = 0
@@ -0,0 +1,77 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class CuttingMode(IntEnum):
5
+ """job_mode"""
6
+
7
+ single_grid = 0
8
+ double_grid = 1
9
+ segment_grid = 2
10
+ no_grid = 3
11
+
12
+
13
+ class CuttingSpeedMode(IntEnum):
14
+ """speed"""
15
+
16
+ normal = 0
17
+ slow = 1
18
+ fast = 2
19
+
20
+
21
+ class BorderPatrolMode(IntEnum):
22
+ """"""
23
+
24
+ none = 0
25
+ one = 1
26
+ two = 2
27
+ three = 3
28
+ four = 4
29
+
30
+
31
+ class ObstacleLapsMode(IntEnum):
32
+ """mowingLaps"""
33
+
34
+ none = 0
35
+ one = 1
36
+ two = 2
37
+ three = 3
38
+ four = 4
39
+
40
+
41
+ class MowOrder(IntEnum):
42
+ """path_order"""
43
+
44
+ border_first = 0
45
+ grid_first = 1
46
+
47
+
48
+ class TraversalMode(IntEnum):
49
+ """Traversal mode when returning."""
50
+
51
+ direct = 0
52
+ follow_perimeter = 1
53
+
54
+
55
+ class TurningMode(IntEnum):
56
+ """Turning mode on corners."""
57
+
58
+ zero_turn = 0
59
+ multipoint = 1
60
+
61
+
62
+ class DetectionStrategy(IntEnum):
63
+ """Matches up with ultra_wave."""
64
+
65
+ direct_touch = 0
66
+ slow_touch = 1
67
+ less_touch = 2
68
+ no_touch = 10 # luba 2 yuka only or possibly value of 10
69
+ sensitive = 11 # x series
70
+
71
+
72
+ class PathAngleSetting(IntEnum):
73
+ """Path Angle type."""
74
+
75
+ relative_angle = 0
76
+ absolute_angle = 1
77
+ random_angle = 2 # Luba Pro / Luba 2 Yuka only
@@ -0,0 +1,45 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+
4
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
5
+
6
+ from pymammotion.utility.conversions import parse_double
7
+
8
+
9
+ class RTKStatus(Enum):
10
+ NONE = 0
11
+ BAD = 1
12
+ FINE = 4
13
+
14
+
15
+ @dataclass
16
+ class RapidState(DataClassORJSONMixin):
17
+ pos_x: float = 0
18
+ pos_y: float = 0
19
+ rtk_status: RTKStatus = RTKStatus.NONE
20
+ toward: float = 0
21
+ satellites_total: int = 0
22
+ satellites_l2: int = 0
23
+ rtk_age: float = 0
24
+ lat_std: float = 0
25
+ lon_std: float = 0
26
+ pos_type: int = 0
27
+ zone_hash: int = 0
28
+ pos_level: int = 0
29
+
30
+ @classmethod
31
+ def from_raw(cls, raw: list[int]) -> "RapidState":
32
+ return RapidState(
33
+ rtk_status=RTKStatus.FINE if raw[0] == 4 else RTKStatus.BAD if raw[0] in (1, 5) else RTKStatus.NONE,
34
+ pos_level=raw[1],
35
+ satellites_total=raw[2],
36
+ rtk_age=parse_double(raw[3], 4.0),
37
+ lat_std=parse_double(raw[4], 4.0),
38
+ lon_std=parse_double(raw[5], 4.0),
39
+ satellites_l2=raw[6],
40
+ pos_x=parse_double(raw[7], 4.0),
41
+ pos_y=parse_double(raw[8], 4.0),
42
+ toward=parse_double(raw[9], 4.0),
43
+ pos_type=raw[10],
44
+ zone_hash=raw[11],
45
+ )