pymammotion 0.2.62__py3-none-any.whl → 0.5.51__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.

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