pymammotion 0.4.0a2__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 (133) hide show
  1. pymammotion/__init__.py +5 -4
  2. pymammotion/aliyun/client.py +235 -0
  3. pymammotion/aliyun/cloud_gateway.py +312 -64
  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 +7 -9
  13. pymammotion/bluetooth/ble_message.py +10 -14
  14. pymammotion/const.py +3 -0
  15. pymammotion/data/model/__init__.py +1 -2
  16. pymammotion/data/model/device.py +95 -27
  17. pymammotion/data/model/device_config.py +4 -4
  18. pymammotion/data/model/device_info.py +35 -0
  19. pymammotion/data/model/device_limits.py +10 -10
  20. pymammotion/data/model/enums.py +12 -2
  21. pymammotion/data/model/errors.py +12 -0
  22. pymammotion/data/model/events.py +14 -0
  23. pymammotion/data/model/generate_geojson.py +521 -0
  24. pymammotion/data/model/generate_route_information.py +2 -2
  25. pymammotion/data/model/hash_list.py +370 -57
  26. pymammotion/data/model/location.py +4 -4
  27. pymammotion/data/model/mowing_modes.py +17 -1
  28. pymammotion/data/model/raw_data.py +2 -10
  29. pymammotion/data/model/region_data.py +10 -11
  30. pymammotion/data/model/report_info.py +31 -5
  31. pymammotion/data/model/work.py +27 -0
  32. pymammotion/data/mower_state_manager.py +316 -0
  33. pymammotion/data/mqtt/event.py +73 -28
  34. pymammotion/data/mqtt/mammotion_properties.py +257 -0
  35. pymammotion/data/mqtt/properties.py +93 -78
  36. pymammotion/data/mqtt/status.py +18 -17
  37. pymammotion/event/event.py +27 -6
  38. pymammotion/homeassistant/__init__.py +3 -0
  39. pymammotion/homeassistant/mower_api.py +484 -0
  40. pymammotion/homeassistant/rtk_api.py +54 -0
  41. pymammotion/http/encryption.py +5 -6
  42. pymammotion/http/http.py +574 -28
  43. pymammotion/http/model/__init__.py +0 -0
  44. pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
  45. pymammotion/http/model/http.py +129 -4
  46. pymammotion/http/model/response_factory.py +61 -0
  47. pymammotion/http/model/rtk.py +16 -0
  48. pymammotion/mammotion/commands/abstract_message.py +7 -5
  49. pymammotion/mammotion/commands/mammotion_command.py +30 -1
  50. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  51. pymammotion/mammotion/commands/messages/driver.py +61 -29
  52. pymammotion/mammotion/commands/messages/media.py +68 -15
  53. pymammotion/mammotion/commands/messages/navigation.py +61 -25
  54. pymammotion/mammotion/commands/messages/network.py +17 -23
  55. pymammotion/mammotion/commands/messages/ota.py +18 -18
  56. pymammotion/mammotion/commands/messages/system.py +32 -49
  57. pymammotion/mammotion/commands/messages/video.py +15 -16
  58. pymammotion/mammotion/devices/__init__.py +27 -3
  59. pymammotion/mammotion/devices/base.py +40 -131
  60. pymammotion/mammotion/devices/mammotion.py +436 -201
  61. pymammotion/mammotion/devices/mammotion_bluetooth.py +57 -47
  62. pymammotion/mammotion/devices/mammotion_cloud.py +134 -105
  63. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  64. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  65. pymammotion/mammotion/devices/managers/managers.py +81 -0
  66. pymammotion/mammotion/devices/mower_device.py +124 -0
  67. pymammotion/mammotion/devices/mower_manager.py +107 -0
  68. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  69. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  70. pymammotion/mammotion/devices/rtk_device.py +50 -0
  71. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  72. pymammotion/mqtt/__init__.py +2 -1
  73. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  74. pymammotion/mqtt/linkkit/__init__.py +5 -0
  75. pymammotion/mqtt/linkkit/h2client.py +585 -0
  76. pymammotion/mqtt/linkkit/linkkit.py +3023 -0
  77. pymammotion/mqtt/mammotion_mqtt.py +176 -169
  78. pymammotion/mqtt/mqtt_models.py +66 -0
  79. pymammotion/proto/__init__.py +4839 -4
  80. pymammotion/proto/basestation.proto +8 -0
  81. pymammotion/proto/basestation_pb2.py +11 -9
  82. pymammotion/proto/basestation_pb2.pyi +16 -2
  83. pymammotion/proto/dev_net.proto +79 -55
  84. pymammotion/proto/dev_net_pb2.py +60 -56
  85. pymammotion/proto/dev_net_pb2.pyi +49 -6
  86. pymammotion/proto/luba_msg.proto +2 -1
  87. pymammotion/proto/luba_msg_pb2.py +6 -6
  88. pymammotion/proto/luba_msg_pb2.pyi +1 -0
  89. pymammotion/proto/luba_mul.proto +62 -1
  90. pymammotion/proto/luba_mul_pb2.py +38 -22
  91. pymammotion/proto/luba_mul_pb2.pyi +94 -7
  92. pymammotion/proto/mctrl_driver.proto +44 -4
  93. pymammotion/proto/mctrl_driver_pb2.py +26 -14
  94. pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
  95. pymammotion/proto/mctrl_nav.proto +93 -52
  96. pymammotion/proto/mctrl_nav_pb2.py +75 -67
  97. pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
  98. pymammotion/proto/mctrl_ota.proto +40 -2
  99. pymammotion/proto/mctrl_ota_pb2.py +23 -13
  100. pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
  101. pymammotion/proto/mctrl_pept.proto +8 -3
  102. pymammotion/proto/mctrl_pept_pb2.py +8 -6
  103. pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
  104. pymammotion/proto/mctrl_sys.proto +325 -86
  105. pymammotion/proto/mctrl_sys_pb2.py +162 -98
  106. pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
  107. pymammotion/proto/message_pool.py +3 -0
  108. pymammotion/proto/py.typed +0 -0
  109. pymammotion/utility/constant/device_constant.py +29 -5
  110. pymammotion/utility/datatype_converter.py +13 -12
  111. pymammotion/utility/device_config.py +522 -130
  112. pymammotion/utility/device_type.py +218 -21
  113. pymammotion/utility/map.py +238 -51
  114. pymammotion/utility/mur_mur_hash.py +159 -0
  115. {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info}/METADATA +26 -31
  116. pymammotion-0.5.51.dist-info/RECORD +152 -0
  117. {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
  118. pymammotion/aliyun/cloud_service.py +0 -65
  119. pymammotion/data/model/plan.py +0 -58
  120. pymammotion/data/state_manager.py +0 -129
  121. pymammotion/proto/basestation.py +0 -59
  122. pymammotion/proto/common.py +0 -12
  123. pymammotion/proto/dev_net.py +0 -381
  124. pymammotion/proto/luba_msg.py +0 -81
  125. pymammotion/proto/luba_mul.py +0 -76
  126. pymammotion/proto/mctrl_driver.py +0 -100
  127. pymammotion/proto/mctrl_nav.py +0 -664
  128. pymammotion/proto/mctrl_ota.py +0 -48
  129. pymammotion/proto/mctrl_pept.py +0 -41
  130. pymammotion/proto/mctrl_sys.py +0 -574
  131. pymammotion-0.4.0a2.dist-info/RECORD +0 -131
  132. /pymammotion/http/{_init_.py → __init__.py} +0 -0
  133. {pymammotion-0.4.0a2.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, SvgMessageAckT
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,25 +17,175 @@ class PathType(IntEnum):
12
17
  AREA = 0
13
18
  OBSTACLE = 1
14
19
  PATH = 2
20
+ LINE = 10
15
21
  DUMP = 12
16
22
  SVG = 13
17
23
 
18
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)
118
+
119
+
19
120
  @dataclass
20
121
  class FrameList(DataClassORJSONMixin):
21
- total_frame: int
22
- data: list[NavGetCommDataAck | SvgMessageAckT]
122
+ total_frame: int = 0
123
+ sub_cmd: int = 0
124
+ data: list[NavGetCommData | SvgMessage] = field(default_factory=list)
23
125
 
24
126
 
25
127
  @dataclass
26
- 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):
27
171
  """Dataclass for NavGetHashListData."""
28
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
+
29
183
 
30
184
  @dataclass
31
185
  class RootHashList(DataClassORJSONMixin):
32
186
  total_frame: int = 0
33
- data: list[NavGetHashListAck] = field(default_factory=list)
187
+ sub_cmd: int = 0
188
+ data: list[NavGetHashListData] = field(default_factory=list)
34
189
 
35
190
 
36
191
  @dataclass
@@ -48,93 +203,214 @@ class HashList(DataClassORJSONMixin):
48
203
  hashlist for all our hashIDs for verification
49
204
  """
50
205
 
51
- root_hash_list: RootHashList = field(default_factory=RootHashList)
52
- area: dict = field(default_factory=dict) # type 0
53
- path: dict = field(default_factory=dict) # type 2
54
- obstacle: dict = field(default_factory=dict) # type 1
55
- dump: dict = field(default_factory=dict) # type 12?
56
- svg: dict = field(default_factory=dict) # type 13
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)
57
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)
58
217
 
59
- 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))
60
221
  self.area = {hash_id: frames for hash_id, frames in self.area.items() if hash_id in hashlist}
61
222
  self.path = {hash_id: frames for hash_id, frames in self.path.items() if hash_id in hashlist}
62
223
  self.obstacle = {hash_id: frames for hash_id, frames in self.obstacle.items() if hash_id in hashlist}
63
224
  self.dump = {hash_id: frames for hash_id, frames in self.dump.items() if hash_id in hashlist}
64
225
  self.svg = {hash_id: frames for hash_id, frames in self.svg.items() if hash_id in hashlist}
65
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
+ ]
239
+
66
240
  @property
67
241
  def hashlist(self) -> list[int]:
68
- if len(self.root_hash_list.data) == 0:
242
+ if not self.root_hash_lists:
69
243
  return []
70
- 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]
71
246
 
72
247
  @property
73
- 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
74
252
  return [
75
253
  i
76
- for obj in self.root_hash_list.data
254
+ for root_list in self.root_hash_lists
255
+ for obj in root_list.data
77
256
  for i in obj.data_couple
78
- if i
79
- not in set(self.area.keys()).union(
80
- self.path.keys(), self.obstacle.keys(), self.dump.keys(), self.svg.keys()
81
- )
257
+ if root_list.sub_cmd == 0
82
258
  ]
83
259
 
84
- def update_root_hash_list(self, hash_list: NavGetHashListAck) -> None:
85
- 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
+ ]
86
275
 
87
- 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):
88
308
  if obj.current_frame == hash_list.current_frame:
89
309
  # Replace the item if current_frame matches
90
- self.root_hash_list.data[index] = hash_list
310
+ target_root_list.data[index] = hash_list
91
311
  return
92
312
 
93
313
  # If no match was found, append the new item
94
- self.root_hash_list.data.append(hash_list)
95
-
96
- def missing_hash_frame(self):
97
- return self._find_missing_frames(self.root_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
98
325
 
99
326
  def missing_frame(self, hash_data: NavGetCommDataAck | SvgMessageAckT) -> list[int]:
100
- if hash_data.type == PathType.AREA:
101
- return self._find_missing_frames(self.area.get(hash_data.hash))
102
-
103
- if hash_data.type == PathType.OBSTACLE:
104
- return self._find_missing_frames(self.obstacle.get(hash_data.hash))
327
+ frame_list = self._get_frame_list_by_type_and_hash(hash_data)
328
+ return self.find_missing_frames(frame_list)
105
329
 
106
- if hash_data.type == PathType.PATH:
107
- return self._find_missing_frames(self.path.get(hash_data.hash))
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)
108
334
 
109
- if hash_data.type == PathType.DUMP:
110
- return self._find_missing_frames(self.dump.get(hash_data.hash))
335
+ if target_dict is None:
336
+ return None
111
337
 
112
- if hash_data.type == PathType.SVG:
113
- return self._find_missing_frames(self.svg.get(hash_data.data_hash))
114
-
115
- def update(self, hash_data: NavGetCommDataAck | SvgMessageAckT) -> bool:
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:
116
361
  """Update the map data."""
117
- if hash_data.type == PathType.AREA:
362
+
363
+ if hash_data.type == PathType.AREA and isinstance(hash_data, NavGetCommData):
118
364
  existing_name = next((area for area in self.area_name if area.hash == hash_data.hash), None)
119
365
  if not existing_name:
120
- name = f"area {len(self.area_name)+1}" if hash_data.area_label is None else hash_data.area_label.label
366
+ name = f"area {len(self.area_name)+1}"
121
367
  self.area_name.append(AreaHashNameList(name=name, hash=hash_data.hash))
122
- return self._add_hash_data(self.area, hash_data)
368
+ result = self._add_hash_data(self.area, hash_data)
369
+ self.update_hash_lists(self.hashlist)
370
+ return result
371
+
372
+ path_type_mapping = self._get_path_type_mapping()
373
+ target_dict = path_type_mapping.get(hash_data.type)
123
374
 
124
- if hash_data.type == PathType.OBSTACLE:
125
- return self._add_hash_data(self.obstacle, hash_data)
375
+ if target_dict is not None:
376
+ return self._add_hash_data(target_dict, hash_data)
126
377
 
127
- if hash_data.type == PathType.PATH:
128
- return self._add_hash_data(self.path, hash_data)
378
+ return False
379
+
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 []
129
384
 
130
- if hash_data.type == PathType.DUMP:
131
- return self._add_hash_data(self.dump, 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
132
387
 
133
- if hash_data.type == PathType.SVG:
134
- return self._add_hash_data(self.svg, hash_data)
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))
396
+
397
+ # Get current frame numbers from dictionary keys
398
+ current_frames = set(self.current_mow_path.keys())
399
+
400
+ # Return sorted list of missing frames
401
+ missing_frames = sorted(expected_frames - current_frames)
402
+ return missing_frames
403
+
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
135
408
 
136
409
  @staticmethod
137
- 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
+
138
414
  if frame_list.total_frame == len(frame_list.data):
139
415
  return []
140
416
  number_list = list(range(1, frame_list.total_frame + 1))
@@ -144,22 +420,59 @@ class HashList(DataClassORJSONMixin):
144
420
  return missing_numbers
145
421
 
146
422
  @staticmethod
147
- def _add_hash_data(hash_dict: dict, hash_data: NavGetCommDataAck | SvgMessageAckT) -> bool:
148
- if isinstance(hash_data, SvgMessageAckT):
149
- if hash_dict.get(hash_data.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:
150
426
  hash_dict[hash_data.data_hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
151
427
  return True
152
428
 
153
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
154
440
  hash_dict[hash_data.data_hash].data.append(hash_data)
155
441
  return True
156
442
  return False
157
443
 
158
- if hash_dict.get(hash_data.hash) is None:
444
+ if hash_dict.get(hash_data.hash, None) is None:
159
445
  hash_dict[hash_data.hash] = FrameList(total_frame=hash_data.total_frame, data=[hash_data])
160
446
  return True
161
447
 
162
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
163
455
  hash_dict[hash_data.hash].data.append(hash_data)
164
456
  return True
165
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
 
@@ -44,13 +52,21 @@ class TraversalMode(IntEnum):
44
52
  follow_perimeter = 1
45
53
 
46
54
 
47
- class BypassStrategy(IntEnum):
55
+ class TurningMode(IntEnum):
56
+ """Turning mode on corners."""
57
+
58
+ zero_turn = 0
59
+ multipoint = 1
60
+
61
+
62
+ class DetectionStrategy(IntEnum):
48
63
  """Matches up with ultra_wave."""
49
64
 
50
65
  direct_touch = 0
51
66
  slow_touch = 1
52
67
  less_touch = 2
53
68
  no_touch = 10 # luba 2 yuka only or possibly value of 10
69
+ sensitive = 11 # x series
54
70
 
55
71
 
56
72
  class PathAngleSetting(IntEnum):
@@ -1,21 +1,13 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Optional
3
2
 
4
3
  from mashumaro.mixins.orjson import DataClassORJSONMixin
5
4
 
6
- from pymammotion.proto.dev_net import DevNet
7
- from pymammotion.proto.luba_msg import LubaMsg
8
- from pymammotion.proto.luba_mul import SocMul
9
- from pymammotion.proto.mctrl_driver import MctlDriver
10
- from pymammotion.proto.mctrl_nav import MctlNav
11
- from pymammotion.proto.mctrl_ota import MctlOta
12
- from pymammotion.proto.mctrl_pept import MctlPept
13
- from pymammotion.proto.mctrl_sys import MctlSys
5
+ from pymammotion.proto import DevNet, LubaMsg, MctlDriver, MctlNav, MctlOta, MctlPept, MctlSys, SocMul
14
6
 
15
7
 
16
8
  @dataclass
17
9
  class RawMowerData:
18
- raw: Optional[LubaMsg] = field(default_factory=LubaMsg)
10
+ raw: LubaMsg | None = field(default_factory=LubaMsg)
19
11
 
20
12
  @classmethod
21
13
  def from_raw(cls, raw: dict) -> "RawMowerData":
@@ -1,5 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
3
2
 
4
3
  from mashumaro.mixins.orjson import DataClassORJSONMixin
5
4
 
@@ -7,14 +6,14 @@ from mashumaro.mixins.orjson import DataClassORJSONMixin
7
6
  @dataclass
8
7
  class RegionData(DataClassORJSONMixin):
9
8
  def __init__(self) -> None:
10
- self.hash: Optional[int] = None
9
+ self.hash: int = 0
11
10
  self.action: int = 0
12
11
  self.current_frame: int = 0
13
- self.data_hash: Optional[int] = None
12
+ self.data_hash: int = 0
14
13
  self.data_len: int = 0
15
- self.p_hash_a: Optional[int] = None
16
- self.p_hash_b: Optional[int] = None
17
- self.path: Optional[list[list[float]]] = None
14
+ self.p_hash_a: int = 0
15
+ self.p_hash_b: int = 0
16
+ self.path: list[list[float]] | None = None
18
17
  self.pver: int = 0
19
18
  self.result: int = 0
20
19
  self.sub_cmd: int = 0
@@ -72,31 +71,31 @@ class RegionData(DataClassORJSONMixin):
72
71
  def set_current_frame(self, current_frame: int) -> None:
73
72
  self.current_frame = current_frame
74
73
 
75
- def get_path(self) -> Optional[list[list[float]]]:
74
+ def get_path(self) -> list[list[float]] | None:
76
75
  return self.path
77
76
 
78
77
  def set_path(self, path: list[list[float]]) -> None:
79
78
  self.path = path
80
79
 
81
- def get_hash(self) -> Optional[int]:
80
+ def get_hash(self) -> int | None:
82
81
  return self.hash
83
82
 
84
83
  def set_data_hash(self, data_hash: int) -> None:
85
84
  self.data_hash = data_hash
86
85
 
87
- def get_data_hash(self) -> Optional[int]:
86
+ def get_data_hash(self) -> int | None:
88
87
  return self.data_hash
89
88
 
90
89
  def set_p_hash_a(self, p_hash_a: int) -> None:
91
90
  self.p_hash_a = p_hash_a
92
91
 
93
- def get_p_hash_a(self) -> Optional[int]:
92
+ def get_p_hash_a(self) -> int | None:
94
93
  return self.p_hash_a
95
94
 
96
95
  def set_p_hash_b(self, p_hash_b: int) -> None:
97
96
  self.p_hash_b = p_hash_b
98
97
 
99
- def get_p_hash_b(self) -> Optional[int]:
98
+ def get_p_hash_b(self) -> int | None:
100
99
  return self.p_hash_b
101
100
 
102
101
  def __str__(self) -> str: