mrtautopilot 5__tar.gz → 6__tar.gz
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.
- {mrtautopilot-5/mrtautopilot.egg-info → mrtautopilot-6}/PKG-INFO +32 -5
- mrtautopilot-5/PKG-INFO → mrtautopilot-6/README.md +27 -13
- mrtautopilot-6/mrtautopilot/__init__.py +4 -0
- {mrtautopilot-5 → mrtautopilot-6}/mrtautopilot/autopilot.py +251 -27
- mrtautopilot-6/mrtautopilot/mission.py +564 -0
- mrtautopilot-5/README.md → mrtautopilot-6/mrtautopilot.egg-info/PKG-INFO +40 -4
- {mrtautopilot-5 → mrtautopilot-6}/mrtautopilot.egg-info/SOURCES.txt +3 -1
- mrtautopilot-6/mrtautopilot.egg-info/requires.txt +3 -0
- {mrtautopilot-5 → mrtautopilot-6}/pyproject.toml +5 -1
- mrtautopilot-6/tests/test_mission.py +645 -0
- mrtautopilot-5/mrtautopilot/__init__.py +0 -3
- mrtautopilot-5/mrtautopilot.egg-info/requires.txt +0 -1
- {mrtautopilot-5 → mrtautopilot-6}/mrtautopilot.egg-info/dependency_links.txt +0 -0
- {mrtautopilot-5 → mrtautopilot-6}/mrtautopilot.egg-info/top_level.txt +0 -0
- {mrtautopilot-5 → mrtautopilot-6}/setup.cfg +0 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mrtautopilot
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6
|
|
4
4
|
Summary: Communicate with the MRT Autopilot Mavlink API
|
|
5
5
|
Maintainer-email: Nathan Knotts <nathan@magothyrt.com>
|
|
6
|
+
Project-URL: Homepage, https://www.magothyrt.com/
|
|
7
|
+
Project-URL: Repository, https://github.com/magothy/mrtautopilot-py3.git
|
|
6
8
|
Requires-Python: >=3.8
|
|
7
9
|
Description-Content-Type: text/markdown
|
|
8
10
|
Requires-Dist: mrtmavlink>=26.0
|
|
11
|
+
Requires-Dist: mrtproto>=7.0
|
|
12
|
+
Requires-Dist: zstandard>=0.23.0
|
|
9
13
|
|
|
10
14
|
# MRT Autopilot Python3 API
|
|
11
15
|
|
|
@@ -14,7 +18,7 @@ This repository contains the Python3 API for the MRT Autopilot.
|
|
|
14
18
|
## Installation
|
|
15
19
|
|
|
16
20
|
```
|
|
17
|
-
pip3 install
|
|
21
|
+
pip3 install mrtautopilot
|
|
18
22
|
```
|
|
19
23
|
|
|
20
24
|
## Usage
|
|
@@ -30,14 +34,14 @@ import selectors
|
|
|
30
34
|
import mrtautopilot
|
|
31
35
|
|
|
32
36
|
# example for send_protobuf_proxy
|
|
33
|
-
from mrtproto import
|
|
37
|
+
from mrtproto import autopilot_pb2
|
|
34
38
|
PROTO_ID = 0x5433 # defined by protocol
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
def main():
|
|
38
42
|
mavlink = mrtautopilot.MavlinkThread()
|
|
39
43
|
|
|
40
|
-
proto =
|
|
44
|
+
proto = autopilot_pb2.VehicleData()
|
|
41
45
|
proto.position.latitude_deg = 38.3
|
|
42
46
|
proto.position.longitude_deg = -77.1
|
|
43
47
|
|
|
@@ -63,6 +67,29 @@ def main():
|
|
|
63
67
|
if count == 5:
|
|
64
68
|
mavlink.send_protobuf_proxy(PROTO_ID, proto.SerializeToString())
|
|
65
69
|
|
|
70
|
+
if count == 6:
|
|
71
|
+
m = mrtautopilot.mission
|
|
72
|
+
mavlink.send_mission(
|
|
73
|
+
m.Mission(
|
|
74
|
+
fault_config=m.FaultConfig(
|
|
75
|
+
fault_responses={},
|
|
76
|
+
loiter_radius_m=30,
|
|
77
|
+
loiter_duration_s=365 * 24 * 60 * 60,
|
|
78
|
+
response_speed_mps=3,
|
|
79
|
+
),
|
|
80
|
+
rally_points=[],
|
|
81
|
+
fence=[],
|
|
82
|
+
mission_items=[
|
|
83
|
+
m.Waypoint(lat_deg=38.31, lon_deg=-77.02, speed_mps=6),
|
|
84
|
+
m.Waypoint(lat_deg=38.31, lon_deg=-77.00, speed_mps=6),
|
|
85
|
+
m.Waypoint(lat_deg=38.34, lon_deg=-77.00, speed_mps=7),
|
|
86
|
+
m.DriftLoiter(
|
|
87
|
+
lat_deg=38.34, lon_deg=-77.00, speed_mps=3, radius_m=50
|
|
88
|
+
),
|
|
89
|
+
],
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
|
|
66
93
|
if count == 10:
|
|
67
94
|
mavlink.set_motor_enablement(True)
|
|
68
95
|
mavlink.send_waypoint(38.3, -77.1, 3.0)
|
|
@@ -80,7 +107,7 @@ def main():
|
|
|
80
107
|
|
|
81
108
|
def setup_logging():
|
|
82
109
|
logging.basicConfig(
|
|
83
|
-
format="%(asctime)s.%(msecs)03d | %(levelname)8s | %(message)s",
|
|
110
|
+
format="%(asctime)s.%(msecs)03d | %(threadName)s | %(levelname)8s | %(message)s",
|
|
84
111
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
85
112
|
)
|
|
86
113
|
logger = logging.getLogger()
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: mrtautopilot
|
|
3
|
-
Version: 5
|
|
4
|
-
Summary: Communicate with the MRT Autopilot Mavlink API
|
|
5
|
-
Maintainer-email: Nathan Knotts <nathan@magothyrt.com>
|
|
6
|
-
Requires-Python: >=3.8
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: mrtmavlink>=26.0
|
|
9
|
-
|
|
10
1
|
# MRT Autopilot Python3 API
|
|
11
2
|
|
|
12
3
|
This repository contains the Python3 API for the MRT Autopilot.
|
|
@@ -14,7 +5,7 @@ This repository contains the Python3 API for the MRT Autopilot.
|
|
|
14
5
|
## Installation
|
|
15
6
|
|
|
16
7
|
```
|
|
17
|
-
pip3 install
|
|
8
|
+
pip3 install mrtautopilot
|
|
18
9
|
```
|
|
19
10
|
|
|
20
11
|
## Usage
|
|
@@ -30,14 +21,14 @@ import selectors
|
|
|
30
21
|
import mrtautopilot
|
|
31
22
|
|
|
32
23
|
# example for send_protobuf_proxy
|
|
33
|
-
from mrtproto import
|
|
24
|
+
from mrtproto import autopilot_pb2
|
|
34
25
|
PROTO_ID = 0x5433 # defined by protocol
|
|
35
26
|
|
|
36
27
|
|
|
37
28
|
def main():
|
|
38
29
|
mavlink = mrtautopilot.MavlinkThread()
|
|
39
30
|
|
|
40
|
-
proto =
|
|
31
|
+
proto = autopilot_pb2.VehicleData()
|
|
41
32
|
proto.position.latitude_deg = 38.3
|
|
42
33
|
proto.position.longitude_deg = -77.1
|
|
43
34
|
|
|
@@ -63,6 +54,29 @@ def main():
|
|
|
63
54
|
if count == 5:
|
|
64
55
|
mavlink.send_protobuf_proxy(PROTO_ID, proto.SerializeToString())
|
|
65
56
|
|
|
57
|
+
if count == 6:
|
|
58
|
+
m = mrtautopilot.mission
|
|
59
|
+
mavlink.send_mission(
|
|
60
|
+
m.Mission(
|
|
61
|
+
fault_config=m.FaultConfig(
|
|
62
|
+
fault_responses={},
|
|
63
|
+
loiter_radius_m=30,
|
|
64
|
+
loiter_duration_s=365 * 24 * 60 * 60,
|
|
65
|
+
response_speed_mps=3,
|
|
66
|
+
),
|
|
67
|
+
rally_points=[],
|
|
68
|
+
fence=[],
|
|
69
|
+
mission_items=[
|
|
70
|
+
m.Waypoint(lat_deg=38.31, lon_deg=-77.02, speed_mps=6),
|
|
71
|
+
m.Waypoint(lat_deg=38.31, lon_deg=-77.00, speed_mps=6),
|
|
72
|
+
m.Waypoint(lat_deg=38.34, lon_deg=-77.00, speed_mps=7),
|
|
73
|
+
m.DriftLoiter(
|
|
74
|
+
lat_deg=38.34, lon_deg=-77.00, speed_mps=3, radius_m=50
|
|
75
|
+
),
|
|
76
|
+
],
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
66
80
|
if count == 10:
|
|
67
81
|
mavlink.set_motor_enablement(True)
|
|
68
82
|
mavlink.send_waypoint(38.3, -77.1, 3.0)
|
|
@@ -80,7 +94,7 @@ def main():
|
|
|
80
94
|
|
|
81
95
|
def setup_logging():
|
|
82
96
|
logging.basicConfig(
|
|
83
|
-
format="%(asctime)s.%(msecs)03d | %(levelname)8s | %(message)s",
|
|
97
|
+
format="%(asctime)s.%(msecs)03d | %(threadName)s | %(levelname)8s | %(message)s",
|
|
84
98
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
85
99
|
)
|
|
86
100
|
logger = logging.getLogger()
|
|
@@ -5,11 +5,18 @@ import socket
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
7
|
import struct
|
|
8
|
+
import uuid
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from enum import Enum
|
|
10
11
|
from typing import List, Union, Tuple
|
|
11
12
|
|
|
12
13
|
import mrtmavlink
|
|
14
|
+
from . import mission
|
|
15
|
+
|
|
16
|
+
import zstandard
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
SYSTEM_ID = 254 # system id for the mavlink thread
|
|
13
20
|
|
|
14
21
|
|
|
15
22
|
@dataclass
|
|
@@ -22,68 +29,74 @@ class HealthItem:
|
|
|
22
29
|
HEALTH_ITEMS = [
|
|
23
30
|
HealthItem(
|
|
24
31
|
health_id="health-monitor-status",
|
|
25
|
-
status_id=
|
|
32
|
+
status_id=mission.HealthId.HealthMonitor.value,
|
|
26
33
|
description="Health Monitor is not properly configured",
|
|
27
34
|
),
|
|
28
35
|
HealthItem(
|
|
29
|
-
health_id="geo-fence",
|
|
36
|
+
health_id="geo-fence",
|
|
37
|
+
status_id=mission.HealthId.GeoFence.value,
|
|
38
|
+
description="Geofence is breached",
|
|
30
39
|
),
|
|
31
40
|
HealthItem(
|
|
32
41
|
health_id="mcu-timeout",
|
|
33
|
-
status_id=
|
|
42
|
+
status_id=mission.HealthId.McuTimeout.value,
|
|
34
43
|
description="MCU Data Is Missing or Stale",
|
|
35
44
|
),
|
|
36
45
|
HealthItem(
|
|
37
46
|
health_id="gps-timeout",
|
|
38
|
-
status_id=
|
|
47
|
+
status_id=mission.HealthId.GpsTimeout.value,
|
|
39
48
|
description="GPS Data Is Missing or Stale",
|
|
40
49
|
),
|
|
41
50
|
HealthItem(
|
|
42
51
|
health_id="ahrs-timeout",
|
|
43
|
-
status_id=
|
|
52
|
+
status_id=mission.HealthId.AhrsTimeout.value,
|
|
44
53
|
description="AHRS Data Is Missing or Stale",
|
|
45
54
|
),
|
|
46
55
|
HealthItem(
|
|
47
56
|
health_id="depth-timeout",
|
|
48
|
-
status_id=
|
|
57
|
+
status_id=mission.HealthId.DepthTimeout.value,
|
|
49
58
|
description="Altimeter Data Is Missing or Stale",
|
|
50
59
|
),
|
|
51
60
|
HealthItem(
|
|
52
61
|
health_id="joystick-timeout",
|
|
53
|
-
status_id=
|
|
62
|
+
status_id=mission.HealthId.JoystickTimeout.value,
|
|
54
63
|
description="Joystick Data Is Missing or Stale",
|
|
55
64
|
),
|
|
56
65
|
HealthItem(
|
|
57
66
|
health_id="low-fuel",
|
|
58
|
-
status_id=
|
|
67
|
+
status_id=mission.HealthId.LowFuel.value,
|
|
59
68
|
description="Low Fuel or Battery",
|
|
60
69
|
),
|
|
61
70
|
HealthItem(
|
|
62
71
|
health_id="oc-timeout",
|
|
63
|
-
status_id=
|
|
72
|
+
status_id=mission.HealthId.OcTimeout.value,
|
|
64
73
|
description="Operator Console Communication Timeout",
|
|
65
74
|
),
|
|
66
75
|
HealthItem(
|
|
67
|
-
health_id="low-disk-space",
|
|
76
|
+
health_id="low-disk-space",
|
|
77
|
+
status_id=mission.HealthId.LowDiskSpace.value,
|
|
78
|
+
description="Low Disk Space",
|
|
68
79
|
),
|
|
69
80
|
]
|
|
70
81
|
|
|
71
82
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
# https://stackoverflow.com/a/30357446
|
|
84
|
+
def crc16(data: bytes) -> int:
|
|
85
|
+
crc: int = 0xFFFF
|
|
86
|
+
msb = crc >> 8
|
|
87
|
+
lsb = crc & 255
|
|
88
|
+
for c in data:
|
|
89
|
+
x = c ^ msb
|
|
90
|
+
x ^= x >> 4
|
|
91
|
+
msb = (lsb ^ (x >> 3) ^ (x << 4)) & 255
|
|
92
|
+
lsb = (x ^ (x << 5)) & 255
|
|
93
|
+
return (msb << 8) + lsb
|
|
81
94
|
|
|
82
95
|
|
|
83
96
|
@dataclass
|
|
84
97
|
class HealthResponse:
|
|
85
98
|
item: HealthItem
|
|
86
|
-
response:
|
|
99
|
+
response: mission.FaultResponseType
|
|
87
100
|
|
|
88
101
|
|
|
89
102
|
class MagothyCustomMainMode(Enum):
|
|
@@ -124,6 +137,36 @@ class AutopilotMode(Enum):
|
|
|
124
137
|
UnhealthyMissionPlanning = 7
|
|
125
138
|
|
|
126
139
|
|
|
140
|
+
class ExpectedMissionAck:
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
xfr: Union[mrtmavlink.MAVLink_magothy_mission_transfer_message, None] = None,
|
|
144
|
+
):
|
|
145
|
+
self.target_system = SYSTEM_ID
|
|
146
|
+
if xfr is None:
|
|
147
|
+
self.session_id = 0
|
|
148
|
+
self.chunk_index = 0
|
|
149
|
+
self.num_chunk = 0
|
|
150
|
+
self.crc16 = 0
|
|
151
|
+
else:
|
|
152
|
+
self.session_id = xfr.session_id
|
|
153
|
+
self.chunk_index = xfr.chunk_index
|
|
154
|
+
self.num_chunk = xfr.num_chunk
|
|
155
|
+
self.crc16 = xfr.crc16
|
|
156
|
+
|
|
157
|
+
def equals(self, ack: mrtmavlink.MAVLink_magothy_mission_ack_message) -> bool:
|
|
158
|
+
return (
|
|
159
|
+
self.target_system == ack.target_system
|
|
160
|
+
and self.session_id == ack.session_id
|
|
161
|
+
and self.chunk_index == ack.chunk_index
|
|
162
|
+
and self.num_chunk == ack.num_chunk
|
|
163
|
+
and self.crc16 == ack.crc16
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def __repr__(self):
|
|
167
|
+
return f"ExpectedMissionAck(sys_id={self.target_system}, session={self.session_id}, chunk={self.chunk_index}, num_chunk={self.num_chunk}, crc16={self.crc16})"
|
|
168
|
+
|
|
169
|
+
|
|
127
170
|
@dataclass
|
|
128
171
|
class LowBandwidth:
|
|
129
172
|
def __init__(
|
|
@@ -193,7 +236,7 @@ class LowBandwidth:
|
|
|
193
236
|
]:
|
|
194
237
|
details = (msg.custom_mode >> 8) & 0xFF
|
|
195
238
|
|
|
196
|
-
response_type =
|
|
239
|
+
response_type = mission.FaultResponseType((details >> 5) & 0x07)
|
|
197
240
|
status_id = 1 << (details & 0x1F)
|
|
198
241
|
|
|
199
242
|
for item in health_items:
|
|
@@ -260,7 +303,9 @@ class MavlinkThread:
|
|
|
260
303
|
|
|
261
304
|
self.is_started = False
|
|
262
305
|
self.is_started_cv = threading.Condition()
|
|
263
|
-
self.conn = mrtmavlink.MAVLink(
|
|
306
|
+
self.conn = mrtmavlink.MAVLink(
|
|
307
|
+
self, SYSTEM_ID, mrtmavlink.MAV_COMP_ID_SYSTEM_CONTROL
|
|
308
|
+
)
|
|
264
309
|
|
|
265
310
|
self.is_done = False
|
|
266
311
|
self.thread = threading.Thread(target=self._thread, name="MrtMavlink")
|
|
@@ -273,6 +318,12 @@ class MavlinkThread:
|
|
|
273
318
|
self.low_bandwidth_queue._reader.fileno() # type: ignore
|
|
274
319
|
)
|
|
275
320
|
|
|
321
|
+
self.is_sending_mission = False
|
|
322
|
+
self.mission_session_id = 0
|
|
323
|
+
self.expected_mission_ack = ExpectedMissionAck()
|
|
324
|
+
self.expected_cmd_uuid = 0
|
|
325
|
+
self.ack_result: Union[int, None] = None
|
|
326
|
+
|
|
276
327
|
def write(self, data: bytes):
|
|
277
328
|
self.sock.sendto(data, (self.remote_address, self.remote_port))
|
|
278
329
|
|
|
@@ -312,6 +363,11 @@ class MavlinkThread:
|
|
|
312
363
|
for msg in messages:
|
|
313
364
|
self._msg_callback(msg)
|
|
314
365
|
|
|
366
|
+
async def _notify_ack_result(self, result: int):
|
|
367
|
+
async with self.ack_cond:
|
|
368
|
+
self.ack_result = result
|
|
369
|
+
self.ack_cond.notify()
|
|
370
|
+
|
|
315
371
|
def _msg_callback(self, msg: mrtmavlink.MAVLink_message):
|
|
316
372
|
logging.debug(f"Received: {msg.msgname}, addr")
|
|
317
373
|
|
|
@@ -320,11 +376,41 @@ class MavlinkThread:
|
|
|
320
376
|
self.system_id = msg.get_srcSystem()
|
|
321
377
|
logging.info(f"Setting System ID to {sys_id}")
|
|
322
378
|
|
|
323
|
-
if msg
|
|
324
|
-
data = LowBandwidth(msg, HEALTH_ITEMS)
|
|
379
|
+
if type(msg) is mrtmavlink.MAVLink_magothy_low_bandwidth_message:
|
|
380
|
+
data = LowBandwidth(msg, HEALTH_ITEMS)
|
|
325
381
|
|
|
326
382
|
if not self.low_bandwidth_queue.full():
|
|
327
383
|
self.low_bandwidth_queue.put_nowait(data)
|
|
384
|
+
elif type(msg) is mrtmavlink.MAVLink_statustext_message:
|
|
385
|
+
if (
|
|
386
|
+
msg.severity == mrtmavlink.MAV_SEVERITY_CRITICAL
|
|
387
|
+
or msg.severity == mrtmavlink.MAV_SEVERITY_EMERGENCY
|
|
388
|
+
or msg.severity == mrtmavlink.MAV_SEVERITY_ALERT
|
|
389
|
+
):
|
|
390
|
+
logging.critical(f"Vehicle Status Text: {msg.text}")
|
|
391
|
+
elif msg.severity == mrtmavlink.MAV_SEVERITY_ERROR:
|
|
392
|
+
logging.error(f"Vehicle Status Text: {msg.text}")
|
|
393
|
+
elif (
|
|
394
|
+
msg.severity == mrtmavlink.MAV_SEVERITY_WARNING
|
|
395
|
+
or msg.severity == mrtmavlink.MAV_SEVERITY_NOTICE
|
|
396
|
+
):
|
|
397
|
+
logging.warning(f"Vehicle Status Text: {msg.text}")
|
|
398
|
+
elif msg.severity == mrtmavlink.MAV_SEVERITY_INFO:
|
|
399
|
+
logging.info(f"Vehicle Status Text: {msg.text}")
|
|
400
|
+
else:
|
|
401
|
+
logging.debug(f"Vehicle Status Text: {msg.text}")
|
|
402
|
+
elif type(msg) is mrtmavlink.MAVLink_magothy_mission_ack_message:
|
|
403
|
+
if self.expected_mission_ack.equals(msg):
|
|
404
|
+
logging.debug(f"Received Mission Ack: {msg}")
|
|
405
|
+
self.loop.create_task(self._notify_ack_result(msg.result))
|
|
406
|
+
elif type(msg) is mrtmavlink.MAVLink_command_ack_message:
|
|
407
|
+
if (
|
|
408
|
+
msg.command == mrtmavlink.MAV_CMD_DO_SET_MISSION_CURRENT
|
|
409
|
+
and msg.target_system == self.system_id
|
|
410
|
+
and msg.result_param2 == self.expected_cmd_uuid
|
|
411
|
+
):
|
|
412
|
+
logging.debug(f"Received Command Ack: {msg}")
|
|
413
|
+
self.loop.create_task(self._notify_ack_result(msg.result))
|
|
328
414
|
|
|
329
415
|
def send_heartbeat(self):
|
|
330
416
|
self.loop.call_soon_threadsafe(lambda: self._send_heartbeat())
|
|
@@ -391,6 +477,7 @@ class MavlinkThread:
|
|
|
391
477
|
self.is_started_cv.notify()
|
|
392
478
|
|
|
393
479
|
self.loop = asyncio.get_running_loop()
|
|
480
|
+
self.ack_cond = asyncio.Condition()
|
|
394
481
|
await self.loop.create_datagram_endpoint(lambda: self, sock=self.sock) # type: ignore
|
|
395
482
|
|
|
396
483
|
while not self.is_done:
|
|
@@ -409,7 +496,7 @@ class MavlinkThread:
|
|
|
409
496
|
|
|
410
497
|
def _send_autopilot_stop(self):
|
|
411
498
|
if not self.system_id:
|
|
412
|
-
logging.
|
|
499
|
+
logging.warning("System ID not set, not sending STOP")
|
|
413
500
|
return
|
|
414
501
|
logging.info("Sending STOP")
|
|
415
502
|
|
|
@@ -446,7 +533,7 @@ class MavlinkThread:
|
|
|
446
533
|
|
|
447
534
|
def _send_waypoint(self, lat_deg: float, lon_deg: float, speed_mps: float):
|
|
448
535
|
if not self.system_id:
|
|
449
|
-
logging.
|
|
536
|
+
logging.warning("System ID not set, not sending waypoint")
|
|
450
537
|
return
|
|
451
538
|
logging.info(
|
|
452
539
|
f"Sending Waypoint to lat {lat_deg}°, lon {lon_deg}°, speed {speed_mps} m/s"
|
|
@@ -473,7 +560,7 @@ class MavlinkThread:
|
|
|
473
560
|
|
|
474
561
|
def _send_loiter(self, speed_mps: float, radius_m: float, duration_s: float):
|
|
475
562
|
if not self.system_id:
|
|
476
|
-
logging.
|
|
563
|
+
logging.warning("System ID not set, not sending loiter")
|
|
477
564
|
return
|
|
478
565
|
logging.info(
|
|
479
566
|
f"Sending Loiter: speed {speed_mps} m/s, radius {radius_m} m, duration {duration_s} s"
|
|
@@ -508,3 +595,140 @@ class MavlinkThread:
|
|
|
508
595
|
assert len(buf) == MAX_BUF_LEN
|
|
509
596
|
|
|
510
597
|
self.conn.magothy_protobuf_proxy_send(proto_id, False, buf_len, buf)
|
|
598
|
+
|
|
599
|
+
def send_mission(self, mission: mission.Mission):
|
|
600
|
+
self.loop.create_task(self._send_mission(mission))
|
|
601
|
+
|
|
602
|
+
async def _send_mission(self, mission: mission.Mission):
|
|
603
|
+
if self.is_sending_mission:
|
|
604
|
+
logging.warning("Already uploading mission")
|
|
605
|
+
return
|
|
606
|
+
|
|
607
|
+
if self.system_id is None:
|
|
608
|
+
logging.warning("System ID not set, not uploading mission")
|
|
609
|
+
return
|
|
610
|
+
if len(mission.mission_items) == 0:
|
|
611
|
+
logging.warning("Mission is empty, not uploading")
|
|
612
|
+
return
|
|
613
|
+
|
|
614
|
+
logging.info(f"Uploading Mission with {len(mission.mission_items)} items")
|
|
615
|
+
|
|
616
|
+
try:
|
|
617
|
+
self.is_sending_mission = True
|
|
618
|
+
self.mission_session_id += 1
|
|
619
|
+
|
|
620
|
+
mission_buf = mission.to_proto().SerializeToString()
|
|
621
|
+
buf = zstandard.compress(mission_buf)
|
|
622
|
+
|
|
623
|
+
UPLOAD_TIMEOUT_S = 0.5
|
|
624
|
+
RETRY_PERIOD_S = 0.1
|
|
625
|
+
max_retries = int(round(UPLOAD_TIMEOUT_S / RETRY_PERIOD_S))
|
|
626
|
+
|
|
627
|
+
xfr = mrtmavlink.MAVLink_magothy_mission_transfer_message(
|
|
628
|
+
target_system=self.system_id,
|
|
629
|
+
session_id=self.mission_session_id,
|
|
630
|
+
filename_index=0xFF,
|
|
631
|
+
chunk_index=0,
|
|
632
|
+
num_chunk=0,
|
|
633
|
+
crc16=crc16(buf),
|
|
634
|
+
payload_len=0,
|
|
635
|
+
payload=bytes(),
|
|
636
|
+
)
|
|
637
|
+
max_payload_len = xfr.lengths[xfr.fieldnames.index("payload")]
|
|
638
|
+
xfr.num_chunk = len(buf) // max_payload_len + (
|
|
639
|
+
1 if len(buf) % max_payload_len > 0 else 0
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
for chunk_index in range(xfr.num_chunk):
|
|
643
|
+
xfr.chunk_index = chunk_index
|
|
644
|
+
xfr.payload_len = min(
|
|
645
|
+
max_payload_len, len(buf) - chunk_index * max_payload_len
|
|
646
|
+
)
|
|
647
|
+
begin = chunk_index * max_payload_len
|
|
648
|
+
end = begin + xfr.payload_len
|
|
649
|
+
xfr.payload = buf[begin:end]
|
|
650
|
+
xfr.payload_len = len(xfr.payload)
|
|
651
|
+
xfr.payload = xfr.payload + bytes(
|
|
652
|
+
max_payload_len - len(xfr.payload)
|
|
653
|
+
) # must pad to max_payload_len with zeros
|
|
654
|
+
|
|
655
|
+
self.expected_mission_ack = ExpectedMissionAck(xfr)
|
|
656
|
+
for retry_num in range(max_retries):
|
|
657
|
+
logging.debug(
|
|
658
|
+
f"Uploading mission chunk {chunk_index}/{xfr.num_chunk} ({retry_num}/{max_retries})"
|
|
659
|
+
)
|
|
660
|
+
self.ack_result = None
|
|
661
|
+
self.conn.send(xfr)
|
|
662
|
+
|
|
663
|
+
try:
|
|
664
|
+
async with self.ack_cond:
|
|
665
|
+
await asyncio.wait_for(self.ack_cond.wait(), RETRY_PERIOD_S)
|
|
666
|
+
except asyncio.TimeoutError:
|
|
667
|
+
continue
|
|
668
|
+
|
|
669
|
+
if self.ack_result is None:
|
|
670
|
+
continue
|
|
671
|
+
|
|
672
|
+
if self.ack_result == mrtmavlink.MAV_RESULT_ACCEPTED:
|
|
673
|
+
logging.debug(
|
|
674
|
+
f"Received ack for chunk {chunk_index + 1}/{xfr.num_chunk}"
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
break
|
|
678
|
+
else:
|
|
679
|
+
logging.error(
|
|
680
|
+
f"Received nack for chunk {chunk_index + 1}/{xfr.num_chunk} - code ({self.ack_result})"
|
|
681
|
+
)
|
|
682
|
+
return
|
|
683
|
+
|
|
684
|
+
if self.ack_result is None:
|
|
685
|
+
logging.error("Mission upload timed out")
|
|
686
|
+
return
|
|
687
|
+
|
|
688
|
+
logging.info("Mission upload successful, starting mission")
|
|
689
|
+
|
|
690
|
+
uid = uuid.uuid4()
|
|
691
|
+
uuid_params = struct.unpack("<ffff", uid.bytes)
|
|
692
|
+
self.expected_cmd_uuid = struct.unpack("<iiii", uid.bytes)[0]
|
|
693
|
+
|
|
694
|
+
# we have successfully sent the mission, now start it
|
|
695
|
+
cmd = mrtmavlink.MAVLink_command_long_message(
|
|
696
|
+
target_system=self.system_id,
|
|
697
|
+
target_component=mrtmavlink.MAV_COMP_ID_AUTOPILOT1,
|
|
698
|
+
command=mrtmavlink.MAV_CMD_DO_SET_MISSION_CURRENT,
|
|
699
|
+
confirmation=1,
|
|
700
|
+
param1=0, # current index - always 0
|
|
701
|
+
param2=0,
|
|
702
|
+
param3=0,
|
|
703
|
+
param4=uuid_params[0],
|
|
704
|
+
param5=uuid_params[1],
|
|
705
|
+
param6=uuid_params[2],
|
|
706
|
+
param7=uuid_params[3],
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
for retry_num in range(max_retries):
|
|
710
|
+
logging.debug(f"Set mission index: ({retry_num+1}/{max_retries})")
|
|
711
|
+
|
|
712
|
+
self.ack_result = None
|
|
713
|
+
self.conn.send(cmd)
|
|
714
|
+
|
|
715
|
+
try:
|
|
716
|
+
async with self.ack_cond:
|
|
717
|
+
await asyncio.wait_for(self.ack_cond.wait(), RETRY_PERIOD_S)
|
|
718
|
+
except asyncio.TimeoutError:
|
|
719
|
+
continue
|
|
720
|
+
|
|
721
|
+
if self.ack_result is None:
|
|
722
|
+
continue
|
|
723
|
+
|
|
724
|
+
if self.ack_result == mrtmavlink.MAV_RESULT_ACCEPTED:
|
|
725
|
+
logging.info("Successfully set mission index")
|
|
726
|
+
break
|
|
727
|
+
else:
|
|
728
|
+
logging.error(
|
|
729
|
+
f"Received nack for mission index - code ({self.ack_result})"
|
|
730
|
+
)
|
|
731
|
+
return
|
|
732
|
+
|
|
733
|
+
finally:
|
|
734
|
+
self.is_sending_mission = False
|