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.
- pymammotion/__init__.py +53 -0
- pymammotion/agora/__init__.py +0 -0
- pymammotion/agora/agora_api.py +755 -0
- pymammotion/agora/agora_rtc_capabilities.py +748 -0
- pymammotion/agora/agora_websockets.py +1175 -0
- pymammotion/aliyun/__init__.py +1 -0
- pymammotion/aliyun/client.py +235 -0
- pymammotion/aliyun/cloud_gateway.py +982 -0
- pymammotion/aliyun/model/aep_response.py +21 -0
- pymammotion/aliyun/model/connect_response.py +51 -0
- pymammotion/aliyun/model/dev_by_account_response.py +195 -0
- pymammotion/aliyun/model/login_by_oauth_response.py +64 -0
- pymammotion/aliyun/model/regions_response.py +29 -0
- pymammotion/aliyun/model/session_by_authcode_response.py +19 -0
- pymammotion/aliyun/model/thing_response.py +12 -0
- pymammotion/aliyun/regions.py +62 -0
- pymammotion/aliyun/tea/core.py +297 -0
- pymammotion/aliyun/tmp_constant.py +171 -0
- pymammotion/bluetooth/__init__.py +1 -0
- pymammotion/bluetooth/ble.py +62 -0
- pymammotion/bluetooth/ble_message.py +676 -0
- pymammotion/bluetooth/const.py +27 -0
- pymammotion/bluetooth/data/__init__.py +0 -0
- pymammotion/bluetooth/data/convert.py +25 -0
- pymammotion/bluetooth/data/framectrldata.py +40 -0
- pymammotion/bluetooth/data/notifydata.py +62 -0
- pymammotion/bluetooth/model/__init__.py +0 -0
- pymammotion/bluetooth/model/atomic_integer.py +54 -0
- pymammotion/const.py +13 -0
- pymammotion/data/__init__.py +0 -0
- pymammotion/data/model/__init__.py +8 -0
- pymammotion/data/model/account.py +8 -0
- pymammotion/data/model/device.py +192 -0
- pymammotion/data/model/device_config.py +72 -0
- pymammotion/data/model/device_info.py +60 -0
- pymammotion/data/model/device_limits.py +49 -0
- pymammotion/data/model/enums.py +77 -0
- pymammotion/data/model/errors.py +12 -0
- pymammotion/data/model/events.py +14 -0
- pymammotion/data/model/generate_geojson.py +565 -0
- pymammotion/data/model/generate_route_information.py +26 -0
- pymammotion/data/model/hash_list.py +475 -0
- pymammotion/data/model/location.py +36 -0
- pymammotion/data/model/mowing_modes.py +77 -0
- pymammotion/data/model/rapid_state.py +45 -0
- pymammotion/data/model/raw_data.py +215 -0
- pymammotion/data/model/region_data.py +102 -0
- pymammotion/data/model/report_info.py +182 -0
- pymammotion/data/model/work.py +27 -0
- pymammotion/data/mower_state_manager.py +369 -0
- pymammotion/data/mqtt/__init__.py +1 -0
- pymammotion/data/mqtt/event.py +227 -0
- pymammotion/data/mqtt/mammotion_properties.py +276 -0
- pymammotion/data/mqtt/properties.py +203 -0
- pymammotion/data/mqtt/status.py +57 -0
- pymammotion/event/__init__.py +6 -0
- pymammotion/event/event.py +96 -0
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +514 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/__init__.py +0 -0
- pymammotion/http/encryption.py +220 -0
- pymammotion/http/http.py +673 -0
- pymammotion/http/model/__init__.py +0 -0
- pymammotion/http/model/camera_stream.py +31 -0
- pymammotion/http/model/http.py +249 -0
- pymammotion/http/model/response_factory.py +61 -0
- pymammotion/http/model/rtk.py +16 -0
- pymammotion/mammotion/__init__.py +0 -0
- pymammotion/mammotion/commands/__init__.py +0 -0
- pymammotion/mammotion/commands/abstract_message.py +24 -0
- pymammotion/mammotion/commands/mammotion_command.py +81 -0
- pymammotion/mammotion/commands/messages/__init__.py +0 -0
- pymammotion/mammotion/commands/messages/basestation.py +43 -0
- pymammotion/mammotion/commands/messages/driver.py +122 -0
- pymammotion/mammotion/commands/messages/media.py +87 -0
- pymammotion/mammotion/commands/messages/navigation.py +564 -0
- pymammotion/mammotion/commands/messages/network.py +205 -0
- pymammotion/mammotion/commands/messages/ota.py +38 -0
- pymammotion/mammotion/commands/messages/system.py +330 -0
- pymammotion/mammotion/commands/messages/video.py +33 -0
- pymammotion/mammotion/control/__init__.py +0 -0
- pymammotion/mammotion/control/joystick.py +145 -0
- pymammotion/mammotion/devices/__init__.py +29 -0
- pymammotion/mammotion/devices/base.py +163 -0
- pymammotion/mammotion/devices/mammotion.py +571 -0
- pymammotion/mammotion/devices/mammotion_bluetooth.py +496 -0
- pymammotion/mammotion/devices/mammotion_cloud.py +355 -0
- pymammotion/mammotion/devices/mammotion_mower_ble.py +48 -0
- pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
- pymammotion/mammotion/devices/managers/managers.py +81 -0
- pymammotion/mammotion/devices/mower_device.py +120 -0
- pymammotion/mammotion/devices/mower_manager.py +107 -0
- pymammotion/mammotion/devices/rtk_ble.py +89 -0
- pymammotion/mammotion/devices/rtk_cloud.py +115 -0
- pymammotion/mammotion/devices/rtk_device.py +50 -0
- pymammotion/mammotion/devices/rtk_manager.py +125 -0
- pymammotion/mqtt/__init__.py +6 -0
- pymammotion/mqtt/aliyun_mqtt.py +237 -0
- pymammotion/mqtt/linkkit/__init__.py +5 -0
- pymammotion/mqtt/linkkit/h2client.py +585 -0
- pymammotion/mqtt/linkkit/linkkit.py +3025 -0
- pymammotion/mqtt/mammotion_future.py +26 -0
- pymammotion/mqtt/mammotion_mqtt.py +214 -0
- pymammotion/mqtt/mqtt_models.py +66 -0
- pymammotion/proto/__init__.py +4841 -0
- pymammotion/proto/basestation.proto +51 -0
- pymammotion/proto/basestation_pb2.py +35 -0
- pymammotion/proto/basestation_pb2.pyi +89 -0
- pymammotion/proto/common.proto +7 -0
- pymammotion/proto/common_pb2.py +25 -0
- pymammotion/proto/common_pb2.pyi +13 -0
- pymammotion/proto/dev_net.proto +321 -0
- pymammotion/proto/dev_net_pb2.py +111 -0
- pymammotion/proto/dev_net_pb2.pyi +515 -0
- pymammotion/proto/luba_msg.proto +76 -0
- pymammotion/proto/luba_msg_pb2.py +41 -0
- pymammotion/proto/luba_msg_pb2.pyi +97 -0
- pymammotion/proto/luba_mul.proto +129 -0
- pymammotion/proto/luba_mul_pb2.py +61 -0
- pymammotion/proto/luba_mul_pb2.pyi +178 -0
- pymammotion/proto/mctrl_driver.proto +107 -0
- pymammotion/proto/mctrl_driver_pb2.py +57 -0
- pymammotion/proto/mctrl_driver_pb2.pyi +167 -0
- pymammotion/proto/mctrl_nav.proto +591 -0
- pymammotion/proto/mctrl_nav_pb2.py +136 -0
- pymammotion/proto/mctrl_nav_pb2.pyi +1067 -0
- pymammotion/proto/mctrl_ota.proto +80 -0
- pymammotion/proto/mctrl_ota_pb2.py +45 -0
- pymammotion/proto/mctrl_ota_pb2.pyi +128 -0
- pymammotion/proto/mctrl_pept.proto +34 -0
- pymammotion/proto/mctrl_pept_pb2.py +33 -0
- pymammotion/proto/mctrl_pept_pb2.pyi +58 -0
- pymammotion/proto/mctrl_sys.proto +741 -0
- pymammotion/proto/mctrl_sys_pb2.py +206 -0
- pymammotion/proto/mctrl_sys_pb2.pyi +1213 -0
- pymammotion/proto/message_pool.py +3 -0
- pymammotion/proto/py.typed +0 -0
- pymammotion/py.typed +0 -0
- pymammotion/utility/constant/__init__.py +3 -0
- pymammotion/utility/constant/device_constant.py +315 -0
- pymammotion/utility/conversions.py +5 -0
- pymammotion/utility/datatype_converter.py +124 -0
- pymammotion/utility/device_config.py +755 -0
- pymammotion/utility/device_type.py +489 -0
- pymammotion/utility/map.py +259 -0
- pymammotion/utility/movement.py +18 -0
- pymammotion/utility/mur_mur_hash.py +159 -0
- pymammotion/utility/periodic.py +106 -0
- pymammotion/utility/rocker_util.py +194 -0
- pymammotion-0.5.69.dist-info/METADATA +93 -0
- pymammotion-0.5.69.dist-info/RECORD +154 -0
- pymammotion-0.5.69.dist-info/WHEEL +4 -0
- 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
|
+
)
|