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.
- pymammotion/__init__.py +9 -6
- pymammotion/aliyun/client.py +235 -0
- pymammotion/aliyun/cloud_gateway.py +320 -69
- pymammotion/aliyun/model/aep_response.py +1 -2
- pymammotion/aliyun/model/dev_by_account_response.py +170 -23
- pymammotion/aliyun/model/login_by_oauth_response.py +2 -3
- pymammotion/aliyun/model/regions_response.py +3 -3
- pymammotion/aliyun/model/session_by_authcode_response.py +2 -2
- pymammotion/aliyun/model/thing_response.py +12 -0
- pymammotion/aliyun/regions.py +62 -0
- pymammotion/aliyun/tea/core.py +297 -0
- pymammotion/bluetooth/ble.py +11 -15
- pymammotion/bluetooth/ble_message.py +389 -106
- pymammotion/bluetooth/model/atomic_integer.py +54 -0
- pymammotion/const.py +3 -0
- pymammotion/data/model/__init__.py +1 -2
- pymammotion/data/model/device.py +92 -240
- pymammotion/data/model/device_config.py +10 -24
- pymammotion/data/model/device_info.py +35 -0
- pymammotion/data/model/device_limits.py +49 -0
- pymammotion/data/model/enums.py +12 -2
- pymammotion/data/model/errors.py +12 -0
- pymammotion/data/model/events.py +14 -0
- pymammotion/data/model/generate_geojson.py +521 -0
- pymammotion/data/model/generate_route_information.py +3 -4
- pymammotion/data/model/hash_list.py +384 -48
- pymammotion/data/model/location.py +4 -4
- pymammotion/data/model/mowing_modes.py +24 -1
- pymammotion/data/model/raw_data.py +215 -0
- pymammotion/data/model/region_data.py +10 -11
- pymammotion/data/model/report_info.py +62 -6
- pymammotion/data/model/work.py +27 -0
- pymammotion/data/mower_state_manager.py +316 -0
- pymammotion/data/mqtt/event.py +73 -28
- pymammotion/data/mqtt/mammotion_properties.py +257 -0
- pymammotion/data/mqtt/properties.py +93 -78
- pymammotion/data/mqtt/status.py +18 -17
- pymammotion/event/event.py +32 -8
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +484 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/__init__.py +0 -0
- pymammotion/http/encryption.py +220 -0
- pymammotion/http/http.py +652 -44
- pymammotion/http/model/__init__.py +0 -0
- pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
- pymammotion/http/model/http.py +160 -9
- pymammotion/http/model/response_factory.py +61 -0
- pymammotion/http/model/rtk.py +16 -0
- pymammotion/mammotion/commands/abstract_message.py +7 -5
- pymammotion/mammotion/commands/mammotion_command.py +32 -3
- pymammotion/mammotion/commands/messages/basestation.py +43 -0
- pymammotion/mammotion/commands/messages/driver.py +61 -29
- pymammotion/mammotion/commands/messages/media.py +68 -15
- pymammotion/mammotion/commands/messages/navigation.py +61 -25
- pymammotion/mammotion/commands/messages/network.py +93 -100
- pymammotion/mammotion/commands/messages/ota.py +18 -18
- pymammotion/mammotion/commands/messages/system.py +97 -72
- pymammotion/mammotion/commands/messages/video.py +17 -12
- pymammotion/mammotion/devices/__init__.py +27 -3
- pymammotion/mammotion/devices/base.py +50 -127
- pymammotion/mammotion/devices/mammotion.py +447 -212
- pymammotion/mammotion/devices/mammotion_bluetooth.py +105 -60
- pymammotion/mammotion/devices/mammotion_cloud.py +157 -105
- 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 +124 -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/linkkit/__init__.py +5 -0
- pymammotion/mqtt/linkkit/h2client.py +585 -0
- pymammotion/mqtt/linkkit/linkkit.py +3023 -0
- pymammotion/mqtt/mammotion_mqtt.py +176 -169
- pymammotion/mqtt/mqtt_models.py +66 -0
- pymammotion/proto/__init__.py +4839 -4
- pymammotion/proto/basestation.proto +8 -0
- pymammotion/proto/basestation_pb2.py +11 -9
- pymammotion/proto/basestation_pb2.pyi +16 -2
- pymammotion/proto/dev_net.proto +79 -55
- pymammotion/proto/dev_net_pb2.py +60 -56
- pymammotion/proto/dev_net_pb2.pyi +49 -6
- pymammotion/proto/luba_msg.proto +2 -1
- pymammotion/proto/luba_msg_pb2.py +6 -6
- pymammotion/proto/luba_msg_pb2.pyi +1 -0
- pymammotion/proto/luba_mul.proto +62 -1
- pymammotion/proto/luba_mul_pb2.py +38 -22
- pymammotion/proto/luba_mul_pb2.pyi +94 -7
- pymammotion/proto/mctrl_driver.proto +44 -4
- pymammotion/proto/mctrl_driver_pb2.py +26 -14
- pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
- pymammotion/proto/mctrl_nav.proto +97 -51
- pymammotion/proto/mctrl_nav_pb2.py +75 -67
- pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
- pymammotion/proto/mctrl_ota.proto +40 -2
- pymammotion/proto/mctrl_ota_pb2.py +23 -13
- pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
- pymammotion/proto/mctrl_pept.proto +8 -3
- pymammotion/proto/mctrl_pept_pb2.py +8 -6
- pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
- pymammotion/proto/mctrl_sys.proto +325 -86
- pymammotion/proto/mctrl_sys_pb2.py +162 -98
- pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
- pymammotion/proto/message_pool.py +3 -0
- pymammotion/proto/py.typed +0 -0
- pymammotion/utility/constant/device_constant.py +65 -21
- pymammotion/utility/datatype_converter.py +13 -12
- pymammotion/utility/device_config.py +755 -0
- pymammotion/utility/device_type.py +218 -21
- pymammotion/utility/map.py +238 -51
- pymammotion/utility/mur_mur_hash.py +159 -0
- {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/METADATA +27 -31
- pymammotion-0.5.51.dist-info/RECORD +152 -0
- {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
- pymammotion/aliyun/cloud_service.py +0 -65
- pymammotion/data/model/plan.py +0 -58
- pymammotion/data/state_manager.py +0 -130
- pymammotion/proto/basestation.py +0 -59
- pymammotion/proto/common.py +0 -12
- pymammotion/proto/dev_net.py +0 -381
- pymammotion/proto/luba_msg.py +0 -81
- pymammotion/proto/luba_mul.py +0 -76
- pymammotion/proto/mctrl_driver.py +0 -100
- pymammotion/proto/mctrl_nav.py +0 -660
- pymammotion/proto/mctrl_ota.py +0 -48
- pymammotion/proto/mctrl_pept.py +0 -41
- pymammotion/proto/mctrl_sys.py +0 -574
- pymammotion-0.2.62.dist-info/RECORD +0 -125
- /pymammotion/{http/_init_.py → bluetooth/model/__init__.py} +0 -0
- {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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
242
|
+
if not self.root_hash_lists:
|
|
66
243
|
return []
|
|
67
|
-
|
|
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
|
|
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
|
|
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
|
|
257
|
+
if root_list.sub_cmd == 0
|
|
76
258
|
]
|
|
77
259
|
|
|
78
|
-
def
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
|
98
|
-
return self.
|
|
375
|
+
if target_dict is not None:
|
|
376
|
+
return self._add_hash_data(target_dict, hash_data)
|
|
99
377
|
|
|
100
|
-
|
|
101
|
-
return self._find_missing_frames(self.path.get(hash_data.hash))
|
|
378
|
+
return False
|
|
102
379
|
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
397
|
+
# Get current frame numbers from dictionary keys
|
|
398
|
+
current_frames = set(self.current_mow_path.keys())
|
|
116
399
|
|
|
117
|
-
|
|
118
|
-
|
|
400
|
+
# Return sorted list of missing frames
|
|
401
|
+
missing_frames = sorted(expected_frames - current_frames)
|
|
402
|
+
return missing_frames
|
|
119
403
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
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:
|
|
135
|
-
if
|
|
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
|
|
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(
|
|
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:
|
|
32
|
-
RTK:
|
|
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
|
|
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):
|