mrtautopilot 5__tar.gz → 7__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.
@@ -1,11 +1,15 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mrtautopilot
3
- Version: 5
3
+ Version: 7
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 git+https://github.com/magothy/mrtautopilot-py3@v5
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 Magothy_pb2
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 = Magothy_pb2.VehicleData()
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 git+https://github.com/magothy/mrtautopilot-py3@v5
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 Magothy_pb2
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 = Magothy_pb2.VehicleData()
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()
@@ -0,0 +1,4 @@
1
+ __version__ = "7"
2
+
3
+ from .autopilot import *
4
+ from .mission import *
@@ -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=0x10000000,
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", status_id=0x100000, description="Geofence is breached"
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=0x8000,
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=0x20,
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=0x200000,
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=0x400000,
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=0x10000,
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=0x2000000,
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=0x8000000,
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", status_id=0x1000000, description="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
- class HealthResponseType(Enum):
73
- Ignore = 0
74
- Halt = 1
75
- Loiter = 2
76
- GoRally = 3
77
- GoFirst = 4
78
- GoLast = 5
79
- GoLaunch = 6
80
- Custom = 7
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: HealthResponseType
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 = HealthResponseType((details >> 5) & 0x07)
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(self, 254, mrtmavlink.MAV_COMP_ID_SYSTEM_CONTROL)
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.get_msgId() == mrtmavlink.MAVLINK_MSG_ID_MAGOTHY_LOW_BANDWIDTH:
324
- data = LowBandwidth(msg, HEALTH_ITEMS) # type: ignore
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.warn("System ID not set, not sending STOP")
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.warn("System ID not set, not sending waypoint")
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.warn("System ID not set, not sending loiter")
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