Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/autotest/arducopter.py
9693 views
1
'''
2
Fly Copter in SITL
3
4
AP_FLAKE8_CLEAN
5
'''
6
7
from __future__ import annotations
8
9
import copy
10
import math
11
import os
12
import shutil
13
import tempfile
14
import time
15
import numpy
16
import pathlib
17
import re
18
19
from pymavlink import quaternion
20
from pymavlink import mavutil
21
from pymavlink import mavextra
22
from pymavlink import rotmat
23
24
from pysim import util
25
from pysim import vehicleinfo
26
27
import vehicle_test_suite
28
29
from vehicle_test_suite import NotAchievedException, AutoTestTimeoutException, PreconditionFailedException
30
from vehicle_test_suite import Test
31
from vehicle_test_suite import MAV_POS_TARGET_TYPE_MASK
32
from vehicle_test_suite import WaitAndMaintainArmed
33
from vehicle_test_suite import WaitAndMaintainAttitude
34
from vehicle_test_suite import WaitModeTimeout
35
36
from pymavlink.rotmat import Vector3, Matrix3
37
38
# get location of scripts
39
testdir = os.path.dirname(os.path.realpath(__file__))
40
SITL_START_LOCATION = mavutil.location(
41
-35.362938,
42
149.165085,
43
584.0805053710938,
44
270
45
)
46
47
# Flight mode switch positions are set-up in arducopter.param to be
48
# switch 1 = Circle
49
# switch 2 = Land
50
# switch 3 = RTL
51
# switch 4 = Auto
52
# switch 5 = Loiter
53
# switch 6 = Stabilize
54
55
56
class AutoTestCopter(vehicle_test_suite.TestSuite):
57
@staticmethod
58
def get_not_armable_mode_list():
59
return ["AUTO", "AUTOTUNE", "BRAKE", "CIRCLE", "FLIP", "LAND", "RTL", "SMART_RTL", "AVOID_ADSB", "FOLLOW"]
60
61
@staticmethod
62
def get_not_disarmed_settable_modes_list():
63
return ["FLIP", "AUTOTUNE"]
64
65
@staticmethod
66
def get_no_position_not_settable_modes_list():
67
return []
68
69
@staticmethod
70
def get_position_armable_modes_list():
71
return ["DRIFT", "GUIDED", "LOITER", "POSHOLD", "THROW"]
72
73
@staticmethod
74
def get_normal_armable_modes_list():
75
return ["ACRO", "ALT_HOLD", "STABILIZE", "GUIDED_NOGPS"]
76
77
def log_name(self):
78
return "ArduCopter"
79
80
def test_filepath(self):
81
return os.path.realpath(__file__)
82
83
def default_speedup(self):
84
return 100
85
86
def set_current_test_name(self, name):
87
self.current_test_name_directory = "ArduCopter_Tests/" + name + "/"
88
89
def sitl_start_location(self):
90
return SITL_START_LOCATION
91
92
def mavproxy_options(self):
93
ret = super(AutoTestCopter, self).mavproxy_options()
94
if self.frame != 'heli':
95
ret.append('--quadcopter')
96
return ret
97
98
def sitl_streamrate(self):
99
return 5
100
101
def vehicleinfo_key(self):
102
return 'ArduCopter'
103
104
def default_frame(self):
105
return "+"
106
107
def apply_defaultfile_parameters(self):
108
# Copter passes in a defaults_filepath in place of applying
109
# parameters afterwards.
110
pass
111
112
def defaults_filepath(self):
113
return self.model_defaults_filepath(self.frame)
114
115
def wait_disarmed_default_wait_time(self):
116
return 120
117
118
def close(self):
119
super(AutoTestCopter, self).close()
120
121
# [2014/05/07] FC Because I'm doing a cross machine build
122
# (source is on host, build is on guest VM) I cannot hard link
123
# This flag tells me that I need to copy the data out
124
if self.copy_tlog:
125
shutil.copy(self.logfile, self.buildlog)
126
127
def is_copter(self):
128
return True
129
130
def get_stick_arming_channel(self):
131
return int(self.get_parameter("RCMAP_YAW"))
132
133
def get_disarm_delay(self):
134
return int(self.get_parameter("DISARM_DELAY"))
135
136
def set_autodisarm_delay(self, delay):
137
self.set_parameter("DISARM_DELAY", delay)
138
139
def takeoff(self,
140
alt_min=30,
141
takeoff_throttle=1700,
142
require_absolute=True,
143
mode="STABILIZE",
144
timeout=120,
145
max_err=5,
146
alt_minimum_duration=0,
147
):
148
"""Takeoff get to 30m altitude."""
149
self.progress("TAKEOFF")
150
self.change_mode(mode)
151
if not self.armed():
152
self.wait_ready_to_arm(require_absolute=require_absolute, timeout=timeout)
153
self.zero_throttle()
154
self.arm_vehicle()
155
if mode == 'GUIDED':
156
self.user_takeoff(alt_min=alt_min, timeout=timeout, max_err=max_err)
157
else:
158
self.set_rc(3, takeoff_throttle)
159
self.wait_altitude(alt_min-1, alt_min+max_err, relative=True, timeout=timeout, minimum_duration=alt_minimum_duration)
160
self.hover()
161
self.progress("TAKEOFF COMPLETE")
162
163
def land_and_disarm(self, timeout=60):
164
"""Land the quad."""
165
self.progress("STARTING LANDING")
166
self.change_mode("LAND")
167
self.wait_landed_and_disarmed(timeout=timeout)
168
169
def wait_landed_and_disarmed(self, min_alt=6, timeout=60):
170
"""Wait to be landed and disarmed"""
171
m = self.assert_receive_message('GLOBAL_POSITION_INT')
172
alt = m.relative_alt / 1000.0 # mm -> m
173
if alt > min_alt:
174
self.wait_altitude(min_alt-1, min_alt+5, relative=True, timeout=timeout)
175
# self.wait_statustext("SIM Hit ground", timeout=timeout)
176
self.wait_disarmed()
177
178
def hover(self, hover_throttle=1500):
179
self.set_rc(3, hover_throttle)
180
181
# Climb/descend to a given altitude
182
def setAlt(self, desiredAlt=50):
183
pos = self.mav.location(relative_alt=True)
184
if pos.alt > desiredAlt:
185
self.set_rc(3, 1300)
186
self.wait_altitude((desiredAlt-5), desiredAlt, relative=True)
187
if pos.alt < (desiredAlt-5):
188
self.set_rc(3, 1800)
189
self.wait_altitude((desiredAlt-5), desiredAlt, relative=True)
190
self.hover()
191
192
# Takeoff, climb to given altitude, and fly east for 10 seconds
193
def takeoffAndMoveAway(self, dAlt=50, dDist=50):
194
self.progress("Centering sticks")
195
self.set_rc_from_map({
196
1: 1500,
197
2: 1500,
198
3: 1000,
199
4: 1500,
200
})
201
self.takeoff(alt_min=dAlt, mode='GUIDED')
202
self.change_mode("ALT_HOLD")
203
204
self.progress("Yaw to east")
205
self.set_rc(4, 1580)
206
self.wait_heading(90)
207
self.set_rc(4, 1500)
208
209
self.progress("Fly eastbound away from home")
210
self.set_rc(2, 1800)
211
self.delay_sim_time(10)
212
self.set_rc(2, 1500)
213
self.hover()
214
self.progress("Copter staging 50 meters east of home at 50 meters altitude In mode Alt Hold")
215
216
# loiter - fly south west, then loiter within 5m position and altitude
217
def ModeLoiter(self, holdtime=10, maxaltchange=5, maxdistchange=5):
218
"""Hold loiter position."""
219
self.takeoff(10, mode="LOITER")
220
221
# first aim south east
222
self.progress("turn south east")
223
self.set_rc(4, 1580)
224
self.wait_heading(170)
225
self.set_rc(4, 1500)
226
227
# fly south east 50m
228
self.set_rc(2, 1100)
229
self.wait_distance(50)
230
self.set_rc(2, 1500)
231
232
# wait for copter to slow moving
233
self.wait_groundspeed(0, 2)
234
235
m = self.assert_receive_message('VFR_HUD')
236
start_altitude = m.alt
237
start = self.mav.location()
238
tstart = self.get_sim_time()
239
self.progress("Holding loiter at %u meters for %u seconds" %
240
(start_altitude, holdtime))
241
while self.get_sim_time_cached() < tstart + holdtime:
242
m = self.assert_receive_message('VFR_HUD')
243
pos = self.mav.location()
244
delta = self.get_distance(start, pos)
245
alt_delta = math.fabs(m.alt - start_altitude)
246
self.progress("Loiter Dist: %.2fm, alt:%u" % (delta, m.alt))
247
if alt_delta > maxaltchange:
248
raise NotAchievedException(
249
"Loiter alt shifted %u meters (> limit %u)" %
250
(alt_delta, maxaltchange))
251
if delta > maxdistchange:
252
raise NotAchievedException(
253
"Loiter shifted %u meters (> limit of %u)" %
254
(delta, maxdistchange))
255
self.progress("Loiter OK for %u seconds" % holdtime)
256
257
self.progress("Climb to 30m")
258
self.change_alt(30)
259
260
self.progress("Descend to 20m")
261
self.change_alt(20)
262
263
self.do_RTL()
264
265
def ModeAltHold(self):
266
'''Test AltHold Mode'''
267
self.takeoff(10, mode="ALT_HOLD")
268
self.watch_altitude_maintained(altitude_min=9, altitude_max=11)
269
# feed in full elevator and aileron input and make sure we
270
# retain altitude:
271
self.set_rc_from_map({
272
1: 1000,
273
2: 1000,
274
})
275
self.watch_altitude_maintained(altitude_min=9, altitude_max=11)
276
self.set_rc_from_map({
277
1: 1500,
278
2: 1500,
279
})
280
self.do_RTL()
281
282
def fly_to_origin(self, final_alt=10):
283
origin = self.poll_message("GPS_GLOBAL_ORIGIN")
284
self.change_mode("GUIDED")
285
self.guided_move_global_relative_alt(origin.latitude,
286
origin.longitude,
287
final_alt)
288
289
def change_alt(self, alt_min, climb_throttle=1920, descend_throttle=1080):
290
"""Change altitude."""
291
def adjust_altitude(current_alt, target_alt, accuracy):
292
if math.fabs(current_alt - target_alt) <= accuracy:
293
self.hover()
294
elif current_alt < target_alt:
295
self.set_rc(3, climb_throttle)
296
else:
297
self.set_rc(3, descend_throttle)
298
self.wait_altitude(
299
(alt_min - 5),
300
alt_min,
301
relative=True,
302
called_function=lambda current_alt, target_alt: adjust_altitude(current_alt, target_alt, 1)
303
)
304
self.hover()
305
306
def RecordThenPlayMission(self, side=50, timeout=300):
307
'''Use switches to toggle in mission, then fly it'''
308
self.takeoff(20, mode="ALT_HOLD")
309
310
"""Fly a square, flying N then E ."""
311
tstart = self.get_sim_time()
312
313
# ensure all sticks in the middle
314
self.set_rc_from_map({
315
1: 1500,
316
2: 1500,
317
3: 1500,
318
4: 1500,
319
})
320
321
# switch to loiter mode temporarily to stop us from rising
322
self.change_mode('LOITER')
323
324
# first aim north
325
self.progress("turn right towards north")
326
self.set_rc(4, 1580)
327
self.wait_heading(10)
328
self.set_rc(4, 1500)
329
330
# save bottom left corner of box as waypoint
331
self.progress("Save WP 1 & 2")
332
self.save_wp()
333
334
# switch back to ALT_HOLD mode
335
self.change_mode('ALT_HOLD')
336
337
# pitch forward to fly north
338
self.progress("Going north %u meters" % side)
339
self.set_rc(2, 1300)
340
self.wait_distance(side)
341
self.set_rc(2, 1500)
342
343
# save top left corner of square as waypoint
344
self.progress("Save WP 3")
345
self.save_wp()
346
347
# roll right to fly east
348
self.progress("Going east %u meters" % side)
349
self.set_rc(1, 1700)
350
self.wait_distance(side)
351
self.set_rc(1, 1500)
352
353
# save top right corner of square as waypoint
354
self.progress("Save WP 4")
355
self.save_wp()
356
357
# pitch back to fly south
358
self.progress("Going south %u meters" % side)
359
self.set_rc(2, 1700)
360
self.wait_distance(side)
361
self.set_rc(2, 1500)
362
363
# save bottom right corner of square as waypoint
364
self.progress("Save WP 5")
365
self.save_wp()
366
367
# roll left to fly west
368
self.progress("Going west %u meters" % side)
369
self.set_rc(1, 1300)
370
self.wait_distance(side)
371
self.set_rc(1, 1500)
372
373
# save bottom left corner of square (should be near home) as waypoint
374
self.progress("Save WP 6")
375
self.save_wp()
376
377
# reduce throttle again
378
self.set_rc(3, 1500)
379
380
# descend to 10m
381
self.progress("Descend to 10m in Loiter")
382
self.change_mode('LOITER')
383
self.set_rc(3, 1200)
384
time_left = timeout - (self.get_sim_time() - tstart)
385
self.progress("timeleft = %u" % time_left)
386
if time_left < 20:
387
time_left = 20
388
self.wait_altitude(-10, 10, timeout=time_left, relative=True)
389
self.set_rc(3, 1500)
390
self.save_wp()
391
392
# Disarm and reset before testing mission
393
self.land_and_disarm()
394
self.set_rc(3, 1000)
395
self.change_mode('LOITER')
396
397
# save the stored mission to file
398
mavproxy = self.start_mavproxy()
399
num_wp = self.save_mission_to_file_using_mavproxy(
400
mavproxy,
401
os.path.join(testdir, "ch7_mission.txt"))
402
self.stop_mavproxy(mavproxy)
403
if not num_wp:
404
raise NotAchievedException("save_mission_to_file failed")
405
406
self.arm_vehicle()
407
self.progress("test: Fly a mission from 1 to %u" % num_wp)
408
self.change_mode('AUTO')
409
# Raise throttle in auto to trigger takeoff
410
self.set_rc(3, 1500)
411
self.wait_waypoint(0, num_wp-1, timeout=500)
412
self.progress("test: MISSION COMPLETE: passed!")
413
self.land_and_disarm()
414
415
def WPArcs(self):
416
'''Test WP Arc functionality'''
417
_ = self.load_and_start_mission("ArcWPMissionTest.txt")
418
419
self.wait_waypoint(0, 15, max_dist=10, timeout=400)
420
self.progress("Check that vehicle flys an arc between WP 15 and 16")
421
# A crude check, if the arc waypoint doesn't work then the copter will fly directly south between wp 15 and 16
422
# If the arc waypoint works then we expect the copter to arc 180 deg in a CW direction meaning it will travel east
423
# of WP 15 before attaining WP 16
424
tstart = self.get_sim_time()
425
timeout = 200
426
last_print_time = 0
427
while True:
428
now = self.get_sim_time_cached()
429
delta = now - tstart
430
pos = self.assert_receive_message('GLOBAL_POSITION_INT')
431
current_long = pos.lon * 1e-7
432
desired_long = 149.16522
433
current_wp = self.mav.waypoint_current()
434
if delta > timeout:
435
raise AutoTestTimeoutException(
436
"Failed to achieve position within %fs" % (timeout,)
437
)
438
if now - last_print_time > 1:
439
self.progress(
440
"Waiting for current long (%f) > desired long (%f) "
441
"(%.2fs remaining before timeout)"
442
% (current_long, desired_long, timeout - delta)
443
)
444
last_print_time = now
445
446
if current_wp > 19:
447
raise NotAchievedException(
448
"mission progressed beyond WP 19 before current longitude > "
449
"desired long (%f)" % desired_long
450
)
451
452
if (current_long > desired_long):
453
self.progress(
454
"Success: Copter traveled far enough east whilst traveling to "
455
"WP 19 that we are likely flying a WP Arc"
456
)
457
break
458
459
self.wait_disarmed()
460
461
def circle_from_arc(self, p1, p2, theta_deg):
462
"""
463
Compute a single circle center and radius given two points on a circle
464
and the signed central angle they subtend.
465
466
p1, p2: (x, y)
467
theta: signed central angle in radians
468
- the absolute value is used for geometry
469
- the sign determines which side of the chord the center is on
470
471
... with thanks to ChatGPT
472
473
"""
474
475
theta = math.radians(theta_deg)
476
477
(x1, y1), (x2, y2) = p1, p2
478
479
# chord vector and length
480
dx = x2 - x1
481
dy = y2 - y1
482
d = math.hypot(dx, dy)
483
484
# radius from absolute angle
485
a = abs(theta)
486
R = d / (2 * math.sin(a / 2))
487
488
# midpoint of chord
489
mx = (x1 + x2) / 2
490
my = (y1 + y2) / 2
491
492
# distance from midpoint to center
493
h = math.sqrt(R**2 - (d/2)**2)
494
495
# unit perpendicular
496
ux = -dy / d
497
uy = dx / d
498
499
# choose side based on sign of theta
500
sign = 1 if theta >= 0 else -1
501
502
cx = mx + sign * h * ux
503
cy = my + sign * h * uy
504
505
return (cx, cy), R
506
507
def WPArcs2(self):
508
'''more tests for waypoint arcs'''
509
self.set_parameters({
510
"AUTO_OPTIONS": 3,
511
})
512
513
def assert_no_movement(mav, m):
514
m_type = m.get_type()
515
if m_type != 'VFR_HUD':
516
return
517
if abs(m.climb) > 0.2 or abs(m.groundspeed) > 0.2:
518
raise NotAchievedException(f"Moved when I shouldn't {m=}")
519
520
def assert_no_lateral_movement(mav, m):
521
m_type = m.get_type()
522
if m_type != 'VFR_HUD':
523
return
524
print(f"{m.groundspeed=}")
525
if abs(m.groundspeed) > 0.2:
526
raise NotAchievedException(f"Moved when I shouldn't {m=}")
527
528
self.start_subtest("Shouldn't move if no takeoff")
529
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
530
self.context_push()
531
self.install_message_hook_context(assert_no_movement)
532
self.fly_simple_relhome_mission([
533
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
534
])
535
536
self.context_pop()
537
538
self.start_subtest("Shouldn't move if no takeoff")
539
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
540
self.context_push()
541
self.install_message_hook_context(assert_no_movement)
542
self.fly_simple_relhome_mission([
543
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 20, 20, 20, {"p1": 90}),
544
])
545
self.context_pop()
546
547
self.context_push()
548
self.start_subtest("Shouldn't move if no takeoff, even if we repeat ourselves")
549
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
550
self.install_message_hook_context(assert_no_movement)
551
self.fly_simple_relhome_mission([
552
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
553
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
554
])
555
self.context_pop()
556
557
self.start_subtest("Shouldn't move if no start waypoint")
558
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
559
self.context_push()
560
self.install_message_hook_context(assert_no_lateral_movement)
561
self.fly_simple_relhome_mission([
562
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
563
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
564
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
565
])
566
self.context_pop()
567
568
self.start_subtest("Shouldn't move if endpoint is startpoint")
569
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
570
self.context_push()
571
self.install_message_hook_context(assert_no_lateral_movement)
572
self.fly_simple_relhome_mission([
573
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
574
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
575
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
576
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
577
])
578
self.context_pop()
579
580
self.progress("Creating a log per-flight")
581
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
582
self.set_parameter("LOG_FILE_DSRMROT", 1)
583
self.arm_vehicle()
584
self.disarm_vehicle()
585
586
# this will move but won't arc (it should...):
587
self.start_subtest("Should move if no start waypoint")
588
self.context_push()
589
self.start_flying_simple_relhome_mission([
590
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
591
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 90}),
592
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
593
])
594
self.wait_groundspeed(1, 10)
595
self.wait_distance_to_home(10, 20)
596
self.wait_disarmed()
597
self.context_pop()
598
599
self.start_subtest("Degenerate arc - should be a straight-line segment")
600
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
601
self.start_flying_simple_relhome_mission([
602
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
603
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
604
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 0}),
605
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
606
])
607
self.wait_groundspeed(1, 10)
608
self.wait_distance_to_home(10, 20)
609
self.wait_disarmed()
610
611
self.start_subtest("Degenerate arc - no length")
612
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
613
self.start_flying_simple_relhome_mission([
614
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
615
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 10, 0, 20),
616
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 10, 0, 20, {"p1": 180}),
617
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
618
])
619
self.wait_groundspeed(1, 10)
620
self.wait_distance_to_home(5, 20)
621
self.wait_disarmed()
622
623
self.start_subtest("Should arc if we start with a waypoint")
624
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
625
pos, radius = self.circle_from_arc((0, 0), (90, 0), 90)
626
loc = self.offset_location_ne(self.home_position_as_mav_location(), pos[0], pos[1])
627
self.start_flying_simple_relhome_mission([
628
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
629
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
630
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 20, {"p1": 90}),
631
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
632
])
633
self.wait_altitude(19, 21, relative=True)
634
self.wait_groundspeed(1, 10)
635
self.wait_circling_point_with_radius(
636
loc, radius,
637
epsilon=1,
638
min_circle_time=10,
639
timeout=30,
640
track_angle=False,
641
)
642
self.wait_distance_to_home(80, 100)
643
self.wait_disarmed()
644
645
self.start_subtest("arc - should end up with a mirrored D")
646
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
647
self.context_push()
648
pos, radius = self.circle_from_arc((0, 0), (90, 0), -90)
649
loc = self.offset_location_ne(self.home_position_as_mav_location(), pos[0], pos[1])
650
self.start_flying_simple_relhome_mission([
651
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
652
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
653
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 20, {"p1": 270}),
654
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
655
])
656
self.wait_altitude(19, 21, relative=True)
657
self.wait_groundspeed(1, 10)
658
self.wait_circling_point_with_radius(
659
loc, radius,
660
epsilon=1,
661
min_circle_time=10,
662
timeout=30,
663
track_angle=False,
664
)
665
self.wait_disarmed(timeout=600)
666
self.context_pop()
667
668
self.start_subtest("arc - should end up with a D")
669
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
670
self.context_push()
671
pos, radius = self.circle_from_arc((0, 0), (90, 0), 90)
672
loc = self.offset_location_ne(self.home_position_as_mav_location(), pos[0], pos[1])
673
self.start_flying_simple_relhome_mission([
674
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
675
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
676
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 20, {"p1": -270}),
677
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
678
])
679
self.wait_altitude(19, 21, relative=True)
680
self.wait_groundspeed(1, 10)
681
self.wait_circling_point_with_radius(
682
loc, radius,
683
epsilon=1,
684
min_circle_time=10,
685
timeout=30,
686
track_angle=False,
687
)
688
self.wait_disarmed(timeout=600)
689
self.context_pop()
690
691
self.start_subtest("arc - should end up with helix up")
692
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
693
self.context_push()
694
pos = (45, 0)
695
radius = 45
696
loc = self.offset_location_ne(self.home_position_as_mav_location(), pos[0], pos[1])
697
self.start_flying_simple_relhome_mission([
698
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
699
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
700
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 100, {"p1": -2700}),
701
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
702
])
703
self.wait_altitude(19, 21, relative=True)
704
self.wait_groundspeed(1, 10)
705
self.wait_circling_point_with_radius(
706
loc, radius,
707
epsilon=1,
708
min_circle_time=360,
709
timeout=400,
710
track_angle=False,
711
)
712
self.wait_altitude(99, 101, relative=True, timeout=240)
713
self.wait_disarmed(timeout=600)
714
self.context_pop()
715
716
self.start_subtest("arc - should end up with helix down")
717
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
718
self.context_push()
719
self.start_flying_simple_relhome_mission([
720
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 100),
721
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 100),
722
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 2700}),
723
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
724
])
725
self.wait_altitude(90, 100, relative=True, timeout=120)
726
self.wait_groundspeed(1, 10)
727
self.wait_distance_to_home(10, 20)
728
self.wait_disarmed(timeout=600)
729
self.context_pop()
730
731
self.start_subtest("arc - chained arcs")
732
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
733
self.context_push()
734
self.start_flying_simple_relhome_mission([
735
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
736
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
737
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 90}),
738
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 60, 0, 20, {"p1": 90}),
739
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 60, 0, 20, {"p1": -90}),
740
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 60, 0, 20, {"p1": -90}),
741
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
742
])
743
self.wait_altitude(15, 25, relative=True, timeout=120)
744
self.wait_groundspeed(1, 10)
745
self.wait_distance_to_home(10, 20)
746
self.wait_disarmed(timeout=600)
747
self.context_pop()
748
749
self.start_subtest("arc - chained arcs - up/down")
750
self.change_mode('STABILIZE') # needed to ensure Copter mission state machine works
751
self.context_push()
752
self.start_flying_simple_relhome_mission([
753
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
754
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
755
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 180}),
756
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 60, 0, 40, {"p1": -180}),
757
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 40, {"p1": 180}),
758
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 120, 0, 20, {"p1": -180}),
759
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
760
])
761
self.wait_altitude(15, 25, relative=True, timeout=120)
762
self.wait_groundspeed(1, 10)
763
self.wait_distance_to_home(10, 20)
764
self.wait_disarmed(timeout=600)
765
self.context_pop()
766
767
# enter RTL mode and wait for the vehicle to disarm
768
def do_RTL(self, distance_min=None, check_alt=True, distance_max=10, timeout=250, quiet=False):
769
"""Enter RTL mode and wait for the vehicle to disarm at Home."""
770
self.change_mode("RTL")
771
self.hover()
772
self.wait_rtl_complete(check_alt=check_alt, distance_max=distance_max, timeout=timeout, quiet=True)
773
774
def wait_rtl_complete(self, check_alt=True, distance_max=10, timeout=250, quiet=False):
775
"""Wait for RTL to reach home and disarm"""
776
self.progress("Waiting RTL to reach Home and disarm")
777
tstart = self.get_sim_time()
778
while self.get_sim_time_cached() < tstart + timeout:
779
m = self.assert_receive_message('GLOBAL_POSITION_INT')
780
alt = m.relative_alt / 1000.0 # mm -> m
781
home_distance = self.distance_to_home(use_cached_home=True)
782
home = ""
783
alt_valid = alt <= 1
784
distance_valid = home_distance < distance_max
785
if check_alt:
786
if alt_valid and distance_valid:
787
home = "HOME"
788
else:
789
if distance_valid:
790
home = "HOME"
791
if not quiet:
792
self.progress("Alt: %.02f HomeDist: %.02f %s" %
793
(alt, home_distance, home))
794
795
# our post-condition is that we are disarmed:
796
if not self.armed():
797
if home == "":
798
raise NotAchievedException("Did not get home")
799
# success!
800
return
801
802
raise AutoTestTimeoutException("Did not get home and disarm")
803
804
def LoiterToAlt(self):
805
"""Loiter-To-Alt"""
806
807
self.context_push()
808
809
self.set_parameters({
810
"PLND_ENABLED": 1,
811
"PLND_TYPE": 4,
812
})
813
814
self.set_analog_rangefinder_parameters()
815
816
self.reboot_sitl()
817
818
num_wp = self.load_mission("copter_loiter_to_alt.txt")
819
820
self.change_mode('LOITER')
821
822
self.install_terrain_handlers_context()
823
self.wait_ready_to_arm()
824
825
self.arm_vehicle()
826
827
self.change_mode('AUTO')
828
829
self.set_rc(3, 1550)
830
831
self.wait_current_waypoint(2)
832
833
self.set_rc(3, 1500)
834
835
self.wait_waypoint(0, num_wp-1, timeout=500)
836
837
self.wait_disarmed()
838
839
self.context_pop()
840
self.reboot_sitl()
841
842
# Tests all actions and logic behind the radio failsafe
843
def ThrottleFailsafe(self, side=60, timeout=360):
844
'''Test Throttle Failsafe'''
845
self.start_subtest("If you haven't taken off yet RC failure should be instant disarm")
846
self.change_mode("STABILIZE")
847
self.set_parameter("DISARM_DELAY", 0)
848
self.arm_vehicle()
849
self.set_parameter("SIM_RC_FAIL", 1)
850
self.disarm_wait(timeout=1)
851
self.set_parameter("SIM_RC_FAIL", 0)
852
self.set_parameter("DISARM_DELAY", 10)
853
854
# Trigger an RC failure with the failsafe disabled. Verify no action taken.
855
self.start_subtest("Radio failsafe disabled test: FS_THR_ENABLE=0 should take no failsafe action")
856
self.set_parameter('FS_THR_ENABLE', 0)
857
self.set_parameter('FS_OPTIONS', 0)
858
self.takeoffAndMoveAway()
859
self.set_parameter("SIM_RC_FAIL", 1)
860
self.delay_sim_time(5)
861
self.wait_mode("ALT_HOLD")
862
self.set_parameter("SIM_RC_FAIL", 0)
863
self.delay_sim_time(5)
864
self.wait_mode("ALT_HOLD")
865
self.end_subtest("Completed Radio failsafe disabled test")
866
867
# Trigger an RC failure, verify radio failsafe triggers,
868
# restore radio, verify RC function by changing modes to circle
869
# and stabilize.
870
self.start_subtest("Radio failsafe recovery test")
871
self.set_parameter('FS_THR_ENABLE', 1)
872
self.set_parameter("SIM_RC_FAIL", 1)
873
self.wait_mode("RTL")
874
self.delay_sim_time(5)
875
self.set_parameter("SIM_RC_FAIL", 0)
876
self.delay_sim_time(5)
877
self.set_rc(5, 1050)
878
self.wait_mode("CIRCLE")
879
self.set_rc(5, 1950)
880
self.wait_mode("STABILIZE")
881
self.end_subtest("Completed Radio failsafe recovery test")
882
883
# Trigger and RC failure, verify failsafe triggers and RTL completes
884
self.start_subtest("Radio failsafe RTL with no options test: FS_THR_ENABLE=1 & FS_OPTIONS=0")
885
self.set_parameter("SIM_RC_FAIL", 1)
886
self.wait_mode("RTL")
887
self.wait_rtl_complete()
888
self.set_parameter("SIM_RC_FAIL", 0)
889
self.end_subtest("Completed Radio failsafe RTL with no options test")
890
891
# Trigger and RC failure, verify failsafe triggers and land completes
892
self.start_subtest("Radio failsafe LAND with no options test: FS_THR_ENABLE=3 & FS_OPTIONS=0")
893
self.set_parameter('FS_THR_ENABLE', 3)
894
self.takeoffAndMoveAway()
895
self.set_parameter("SIM_RC_FAIL", 1)
896
self.wait_mode("LAND")
897
self.wait_landed_and_disarmed()
898
self.set_parameter("SIM_RC_FAIL", 0)
899
self.end_subtest("Completed Radio failsafe LAND with no options test")
900
901
# Trigger and RC failure, verify failsafe triggers and SmartRTL completes
902
self.start_subtest("Radio failsafe SmartRTL->RTL with no options test: FS_THR_ENABLE=4 & FS_OPTIONS=0")
903
self.set_parameter('FS_THR_ENABLE', 4)
904
self.takeoffAndMoveAway()
905
self.set_parameter("SIM_RC_FAIL", 1)
906
self.wait_mode("SMART_RTL")
907
self.wait_disarmed()
908
self.set_parameter("SIM_RC_FAIL", 0)
909
self.end_subtest("Completed Radio failsafe SmartRTL->RTL with no options test")
910
911
# Trigger and RC failure, verify failsafe triggers and SmartRTL completes
912
self.start_subtest("Radio failsafe SmartRTL->Land with no options test: FS_THR_ENABLE=5 & FS_OPTIONS=0")
913
self.set_parameter('FS_THR_ENABLE', 5)
914
self.takeoffAndMoveAway()
915
self.set_parameter("SIM_RC_FAIL", 1)
916
self.wait_mode("SMART_RTL")
917
self.wait_disarmed()
918
self.set_parameter("SIM_RC_FAIL", 0)
919
self.end_subtest("Completed Radio failsafe SmartRTL_Land with no options test")
920
921
# Trigger a GPS failure and RC failure, verify RTL fails into
922
# land mode and completes
923
self.start_subtest("Radio failsafe RTL fails into land mode due to bad position.")
924
self.set_parameter('FS_THR_ENABLE', 1)
925
self.takeoffAndMoveAway()
926
self.set_parameter('SIM_GPS1_ENABLE', 0)
927
self.delay_sim_time(5)
928
self.set_parameter("SIM_RC_FAIL", 1)
929
self.wait_mode("LAND")
930
self.wait_landed_and_disarmed()
931
self.set_parameter("SIM_RC_FAIL", 0)
932
self.set_parameter('SIM_GPS1_ENABLE', 1)
933
self.wait_ekf_happy()
934
self.end_subtest("Completed Radio failsafe RTL fails into land mode due to bad position.")
935
936
# Trigger a GPS failure and RC failure, verify SmartRTL fails
937
# into land mode and completes
938
self.start_subtest("Radio failsafe SmartRTL->RTL fails into land mode due to bad position.")
939
self.set_parameter('FS_THR_ENABLE', 4)
940
self.takeoffAndMoveAway()
941
self.set_parameter('SIM_GPS1_ENABLE', 0)
942
self.delay_sim_time(5)
943
self.set_parameter("SIM_RC_FAIL", 1)
944
self.wait_mode("LAND")
945
self.wait_landed_and_disarmed()
946
self.set_parameter("SIM_RC_FAIL", 0)
947
self.set_parameter('SIM_GPS1_ENABLE', 1)
948
self.wait_ekf_happy()
949
self.end_subtest("Completed Radio failsafe SmartRTL->RTL fails into land mode due to bad position.")
950
951
# Trigger a GPS failure and RC failure, verify SmartRTL fails
952
# into land mode and completes
953
self.start_subtest("Radio failsafe SmartRTL->LAND fails into land mode due to bad position.")
954
self.set_parameter('FS_THR_ENABLE', 5)
955
self.takeoffAndMoveAway()
956
self.set_parameter('SIM_GPS1_ENABLE', 0)
957
self.delay_sim_time(5)
958
self.set_parameter("SIM_RC_FAIL", 1)
959
self.wait_mode("LAND")
960
self.wait_landed_and_disarmed()
961
self.set_parameter("SIM_RC_FAIL", 0)
962
self.set_parameter('SIM_GPS1_ENABLE', 1)
963
self.wait_ekf_happy()
964
self.end_subtest("Completed Radio failsafe SmartRTL->LAND fails into land mode due to bad position.")
965
966
# Trigger a GPS failure, then restore the GPS. Trigger an RC
967
# failure, verify SmartRTL fails into RTL and completes
968
self.start_subtest("Radio failsafe SmartRTL->RTL fails into RTL mode due to no path.")
969
self.set_parameter('FS_THR_ENABLE', 4)
970
self.takeoffAndMoveAway()
971
self.set_parameter('SIM_GPS1_ENABLE', 0)
972
self.wait_statustext("SmartRTL deactivated: bad position", timeout=60)
973
self.set_parameter('SIM_GPS1_ENABLE', 1)
974
self.wait_ekf_happy()
975
self.delay_sim_time(5)
976
self.set_parameter("SIM_RC_FAIL", 1)
977
self.wait_mode("RTL")
978
self.wait_rtl_complete()
979
self.set_parameter("SIM_RC_FAIL", 0)
980
self.end_subtest("Completed Radio failsafe SmartRTL->RTL fails into RTL mode due to no path.")
981
982
# Trigger a GPS failure, then restore the GPS. Trigger an RC
983
# failure, verify SmartRTL fails into Land and completes
984
self.start_subtest("Radio failsafe SmartRTL->LAND fails into land mode due to no path.")
985
self.set_parameter('FS_THR_ENABLE', 5)
986
self.takeoffAndMoveAway()
987
self.set_parameter('SIM_GPS1_ENABLE', 0)
988
self.wait_statustext("SmartRTL deactivated: bad position", timeout=60)
989
self.set_parameter('SIM_GPS1_ENABLE', 1)
990
self.wait_ekf_happy()
991
self.delay_sim_time(5)
992
self.set_parameter("SIM_RC_FAIL", 1)
993
self.wait_mode("LAND")
994
self.wait_landed_and_disarmed()
995
self.set_parameter("SIM_RC_FAIL", 0)
996
self.end_subtest("Completed Radio failsafe SmartRTL->LAND fails into land mode due to no path.")
997
998
# Trigger an RC failure in guided mode with the option enabled
999
# to continue in guided. Verify no failsafe action takes place
1000
self.start_subtest("Radio failsafe with option to continue in guided mode: FS_THR_ENABLE=1 & FS_OPTIONS=4")
1001
self.set_parameter("MAV_GCS_SYSID", self.mav.source_system)
1002
self.setGCSfailsafe(1)
1003
self.set_parameter('FS_THR_ENABLE', 1)
1004
self.set_parameter('FS_OPTIONS', 4)
1005
self.takeoffAndMoveAway()
1006
self.change_mode("GUIDED")
1007
self.set_parameter("SIM_RC_FAIL", 1)
1008
self.delay_sim_time(5)
1009
self.wait_mode("GUIDED")
1010
self.set_parameter("SIM_RC_FAIL", 0)
1011
self.delay_sim_time(5)
1012
self.change_mode("ALT_HOLD")
1013
self.setGCSfailsafe(0)
1014
# self.change_mode("RTL")
1015
# self.wait_disarmed()
1016
self.end_subtest("Completed Radio failsafe with option to continue in guided mode")
1017
1018
# Trigger an RC failure in AUTO mode with the option enabled
1019
# to continue the mission. Verify no failsafe action takes
1020
# place
1021
self.start_subtest("Radio failsafe RTL with option to continue mission: FS_THR_ENABLE=1 & FS_OPTIONS=1")
1022
self.set_parameter('FS_OPTIONS', 1)
1023
self.progress("# Load copter_mission")
1024
num_wp = self.load_mission("copter_mission.txt", strict=False)
1025
if not num_wp:
1026
raise NotAchievedException("load copter_mission failed")
1027
# self.takeoffAndMoveAway()
1028
self.change_mode("AUTO")
1029
self.set_parameter("SIM_RC_FAIL", 1)
1030
self.delay_sim_time(5)
1031
self.wait_mode("AUTO")
1032
self.set_parameter("SIM_RC_FAIL", 0)
1033
self.delay_sim_time(5)
1034
self.wait_mode("AUTO")
1035
# self.change_mode("RTL")
1036
# self.wait_disarmed()
1037
self.end_subtest("Completed Radio failsafe RTL with option to continue mission")
1038
1039
# Trigger an RC failure in AUTO mode without the option
1040
# enabled to continue. Verify failsafe triggers and RTL
1041
# completes
1042
self.start_subtest("Radio failsafe RTL in mission without "
1043
"option to continue should RTL: FS_THR_ENABLE=1 & FS_OPTIONS=0")
1044
self.set_parameter('FS_OPTIONS', 0)
1045
self.set_parameter("SIM_RC_FAIL", 1)
1046
self.wait_mode("RTL")
1047
self.wait_rtl_complete()
1048
self.clear_mission(mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
1049
self.set_parameter("SIM_RC_FAIL", 0)
1050
self.end_subtest("Completed Radio failsafe RTL in mission without option to continue")
1051
1052
self.progress("All radio failsafe tests complete")
1053
self.set_parameter('FS_THR_ENABLE', 0)
1054
self.reboot_sitl()
1055
1056
def ThrottleFailsafePassthrough(self):
1057
'''check servo passthrough on RC failsafe. Make sure it doesn't glitch to the bad RC input value'''
1058
channel = 7
1059
trim_value = 1450
1060
self.set_parameters({
1061
'RC%u_MIN' % channel: 1000,
1062
'RC%u_MAX' % channel: 2000,
1063
'SERVO%u_MIN' % channel: 1000,
1064
'SERVO%u_MAX' % channel: 2000,
1065
'SERVO%u_TRIM' % channel: trim_value,
1066
'SERVO%u_FUNCTION' % channel: 146, # scaled passthrough for channel 7
1067
'FS_THR_ENABLE': 1,
1068
'RC_FS_TIMEOUT': 10,
1069
'SERVO_RC_FS_MSK': 1 << (channel-1),
1070
})
1071
1072
self.reboot_sitl()
1073
1074
self.context_set_message_rate_hz('SERVO_OUTPUT_RAW', 200)
1075
1076
self.set_rc(channel, 1799)
1077
expected_servo_output_value = 1778 # 1778 because of weird trim
1078
self.wait_servo_channel_value(channel, expected_servo_output_value)
1079
# receiver goes into failsafe with wild override values:
1080
1081
def ensure_SERVO_values_never_input(mav, m):
1082
if m.get_type() != "SERVO_OUTPUT_RAW":
1083
return
1084
value = getattr(m, "servo%u_raw" % channel)
1085
if value != expected_servo_output_value and value != trim_value:
1086
raise NotAchievedException("Bad servo value %u received" % value)
1087
1088
self.install_message_hook_context(ensure_SERVO_values_never_input)
1089
self.progress("Forcing receiver into failsafe")
1090
self.set_rc_from_map({
1091
3: 800,
1092
channel: 1300,
1093
})
1094
self.wait_servo_channel_value(channel, trim_value)
1095
self.delay_sim_time(10)
1096
1097
# Tests all actions and logic behind the GCS failsafe
1098
def GCSFailsafe(self, side=60, timeout=360):
1099
'''Test GCS Failsafe'''
1100
# Test double-SmartRTL; ensure we do SmarRTL twice rather than
1101
# landing (tests fix for actual bug)
1102
self.set_parameter("MAV_GCS_SYSID", self.mav.source_system)
1103
self.context_push()
1104
self.start_subtest("GCS failsafe SmartRTL twice")
1105
self.setGCSfailsafe(3)
1106
self.set_parameter('FS_OPTIONS', 8)
1107
self.takeoffAndMoveAway()
1108
self.set_heartbeat_rate(0)
1109
self.wait_mode("SMART_RTL")
1110
self.wait_disarmed()
1111
self.set_heartbeat_rate(self.speedup)
1112
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1113
1114
self.takeoffAndMoveAway()
1115
self.set_heartbeat_rate(0)
1116
self.wait_statustext("GCS Failsafe")
1117
1118
def ensure_smartrtl(mav, m):
1119
if m.get_type() != "HEARTBEAT":
1120
return
1121
# can't use mode_is here because we're in the message hook
1122
print("Mode: %s" % self.mav.flightmode)
1123
if self.mav.flightmode != "SMART_RTL":
1124
raise NotAchievedException("Not in SMART_RTL")
1125
self.install_message_hook_context(ensure_smartrtl)
1126
1127
self.set_heartbeat_rate(self.speedup)
1128
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1129
self.set_heartbeat_rate(0)
1130
self.wait_statustext("GCS Failsafe")
1131
1132
self.wait_disarmed()
1133
1134
self.end_subtest("GCS failsafe SmartRTL twice")
1135
self.set_heartbeat_rate(self.speedup)
1136
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1137
self.context_pop()
1138
1139
# Trigger telemetry loss with failsafe disabled. Verify no action taken.
1140
self.start_subtest("GCS failsafe disabled test: FS_GCS_ENABLE=0 should take no failsafe action")
1141
self.setGCSfailsafe(0)
1142
self.takeoffAndMoveAway()
1143
self.set_heartbeat_rate(0)
1144
self.delay_sim_time(5)
1145
self.wait_mode("ALT_HOLD")
1146
self.set_heartbeat_rate(self.speedup)
1147
self.delay_sim_time(5)
1148
self.wait_mode("ALT_HOLD")
1149
self.end_subtest("Completed GCS failsafe disabled test")
1150
1151
# Trigger telemetry loss with failsafe enabled. Verify
1152
# failsafe triggers to RTL. Restore telemetry, verify failsafe
1153
# clears, and change modes.
1154
self.start_subtest("GCS failsafe recovery test: FS_GCS_ENABLE=1 & FS_OPTIONS=0")
1155
self.setGCSfailsafe(1)
1156
self.set_parameter('FS_OPTIONS', 0)
1157
self.set_heartbeat_rate(0)
1158
self.wait_mode("RTL")
1159
self.set_heartbeat_rate(self.speedup)
1160
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1161
self.change_mode("LOITER")
1162
self.end_subtest("Completed GCS failsafe recovery test")
1163
1164
# Trigger telemetry loss with failsafe enabled. Verify
1165
# failsafe triggers to RTL. Restore telemetry, verify failsafe
1166
# clears, and change modes.
1167
self.start_subtest("GCS failsafe recovery test: FS_GCS_ENABLE=1 & FS_OPTIONS=0 & FS_GCS_TIMEOUT=10")
1168
self.setGCSfailsafe(1)
1169
self.set_parameter('FS_OPTIONS', 0)
1170
old_gcs_timeout = self.get_parameter("FS_GCS_TIMEOUT")
1171
new_gcs_timeout = old_gcs_timeout * 2
1172
self.set_parameter("FS_GCS_TIMEOUT", new_gcs_timeout)
1173
self.set_heartbeat_rate(0)
1174
self.delay_sim_time(old_gcs_timeout + (new_gcs_timeout - old_gcs_timeout) / 2)
1175
self.assert_mode("LOITER")
1176
self.wait_mode("RTL")
1177
self.set_heartbeat_rate(self.speedup)
1178
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1179
self.change_mode("LOITER")
1180
self.set_parameter('FS_GCS_TIMEOUT', old_gcs_timeout)
1181
self.end_subtest("Completed GCS failsafe recovery test")
1182
1183
# Trigger telemetry loss with failsafe enabled. Verify failsafe triggers and RTL completes
1184
self.start_subtest("GCS failsafe RTL with no options test: FS_GCS_ENABLE=1 & FS_OPTIONS=0")
1185
self.setGCSfailsafe(1)
1186
self.set_parameter('FS_OPTIONS', 0)
1187
self.set_heartbeat_rate(0)
1188
self.wait_mode("RTL")
1189
self.wait_rtl_complete()
1190
self.set_heartbeat_rate(self.speedup)
1191
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1192
self.end_subtest("Completed GCS failsafe RTL with no options test")
1193
1194
# Trigger telemetry loss with failsafe enabled. Verify failsafe triggers and land completes
1195
self.start_subtest("GCS failsafe LAND with no options test: FS_GCS_ENABLE=5 & FS_OPTIONS=0")
1196
self.setGCSfailsafe(5)
1197
self.takeoffAndMoveAway()
1198
self.set_heartbeat_rate(0)
1199
self.wait_mode("LAND")
1200
self.wait_landed_and_disarmed()
1201
self.set_heartbeat_rate(self.speedup)
1202
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1203
self.end_subtest("Completed GCS failsafe land with no options test")
1204
1205
# Trigger telemetry loss with failsafe enabled. Verify failsafe triggers and SmartRTL completes
1206
self.start_subtest("GCS failsafe SmartRTL->RTL with no options test: FS_GCS_ENABLE=3 & FS_OPTIONS=0")
1207
self.setGCSfailsafe(3)
1208
self.takeoffAndMoveAway()
1209
self.set_heartbeat_rate(0)
1210
self.wait_mode("SMART_RTL")
1211
self.wait_disarmed()
1212
self.set_heartbeat_rate(self.speedup)
1213
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1214
self.end_subtest("Completed GCS failsafe SmartRTL->RTL with no options test")
1215
1216
# Trigger telemetry loss with failsafe enabled. Verify failsafe triggers and SmartRTL completes
1217
self.start_subtest("GCS failsafe SmartRTL->Land with no options test: FS_GCS_ENABLE=4 & FS_OPTIONS=0")
1218
self.setGCSfailsafe(4)
1219
self.takeoffAndMoveAway()
1220
self.set_heartbeat_rate(0)
1221
self.wait_mode("SMART_RTL")
1222
self.wait_disarmed()
1223
self.set_heartbeat_rate(self.speedup)
1224
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1225
self.end_subtest("Completed GCS failsafe SmartRTL->Land with no options test")
1226
1227
# Trigger telemetry loss with an invalid failsafe value. Verify failsafe triggers and RTL completes
1228
self.start_subtest("GCS failsafe invalid value with no options test: FS_GCS_ENABLE=99 & FS_OPTIONS=0")
1229
self.setGCSfailsafe(99)
1230
self.takeoffAndMoveAway()
1231
self.set_heartbeat_rate(0)
1232
self.wait_mode("RTL")
1233
self.wait_rtl_complete()
1234
self.set_heartbeat_rate(self.speedup)
1235
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1236
self.end_subtest("Completed GCS failsafe invalid value with no options test")
1237
1238
# Trigger telemetry loss with failsafe enabled to test FS_OPTIONS settings
1239
self.start_subtest("GCS failsafe with option bit tests: FS_GCS_ENABLE=1 & FS_OPTIONS=64/2/16")
1240
num_wp = self.load_mission("copter_mission.txt", strict=False)
1241
if not num_wp:
1242
raise NotAchievedException("load copter_mission failed")
1243
self.setGCSfailsafe(1)
1244
self.set_parameter('FS_OPTIONS', 16)
1245
self.takeoffAndMoveAway()
1246
self.progress("Testing continue in pilot controlled modes")
1247
self.set_heartbeat_rate(0)
1248
self.wait_statustext("GCS Failsafe - Continuing Pilot Control", timeout=60)
1249
self.delay_sim_time(5)
1250
self.wait_mode("ALT_HOLD")
1251
self.set_heartbeat_rate(self.speedup)
1252
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1253
1254
self.progress("Testing continue in auto mission")
1255
self.set_parameter('FS_OPTIONS', 2)
1256
self.change_mode("AUTO")
1257
self.delay_sim_time(5)
1258
self.set_heartbeat_rate(0)
1259
self.wait_statustext("GCS Failsafe - Continuing Auto Mode", timeout=60)
1260
self.delay_sim_time(5)
1261
self.wait_mode("AUTO")
1262
self.set_heartbeat_rate(self.speedup)
1263
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1264
1265
self.progress("Testing continue landing in land mode")
1266
self.set_parameter('FS_OPTIONS', 8)
1267
self.change_mode("LAND")
1268
self.delay_sim_time(5)
1269
self.set_heartbeat_rate(0)
1270
self.wait_statustext("GCS Failsafe - Continuing Landing", timeout=60)
1271
self.delay_sim_time(5)
1272
self.wait_mode("LAND")
1273
self.wait_landed_and_disarmed()
1274
self.set_heartbeat_rate(self.speedup)
1275
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
1276
self.end_subtest("Completed GCS failsafe with option bits")
1277
1278
self.setGCSfailsafe(0)
1279
self.set_parameter('FS_OPTIONS', 0)
1280
self.progress("All GCS failsafe tests complete")
1281
1282
def CustomController(self, timeout=300):
1283
'''Test Custom Controller'''
1284
self.progress("Configure custom controller parameters")
1285
self.set_parameters({
1286
'CC_TYPE': 2,
1287
'CC_AXIS_MASK': 7,
1288
'RC6_OPTION': 109,
1289
})
1290
self.set_rc(6, 1000)
1291
self.reboot_sitl()
1292
1293
if self.get_parameter("CC_TYPE") != 2 :
1294
raise NotAchievedException("Custom controller is not switched to PID backend.")
1295
1296
# check if we can retrieve any param inside PID backend
1297
self.get_parameter("CC2_RAT_YAW_P")
1298
1299
# takeoff in GPS mode and switch to CIRCLE
1300
self.takeoff(10, mode="LOITER", takeoff_throttle=2000)
1301
self.change_mode("CIRCLE")
1302
1303
self.context_push()
1304
self.context_collect('STATUSTEXT')
1305
1306
# switch custom controller on
1307
self.set_rc(6, 2000)
1308
self.wait_statustext("Custom controller is ON", check_context=True)
1309
1310
# wait 20 second to see if the custom controller destabilize the aircraft
1311
if self.wait_altitude(7, 13, relative=True, minimum_duration=20) :
1312
raise NotAchievedException("Custom controller is not stable.")
1313
1314
# switch custom controller off
1315
self.set_rc(6, 1000)
1316
self.wait_statustext("Custom controller is OFF", check_context=True)
1317
1318
self.context_pop()
1319
self.do_RTL()
1320
self.progress("Custom controller test complete")
1321
1322
# Tests all actions and logic behind the battery failsafe
1323
def BatteryFailsafe(self, timeout=300):
1324
'''Fly Battery Failsafe'''
1325
self.progress("Configure battery failsafe parameters")
1326
self.set_parameters({
1327
'SIM_SPEEDUP': 4,
1328
'BATT_LOW_VOLT': 11.5,
1329
'BATT_CRT_VOLT': 10.1,
1330
'BATT_FS_LOW_ACT': 0,
1331
'BATT_FS_CRT_ACT': 0,
1332
'FS_OPTIONS': 0,
1333
'SIM_BATT_VOLTAGE': 12.5,
1334
})
1335
1336
# Trigger low battery condition with failsafe disabled. Verify
1337
# no action taken.
1338
self.start_subtest("Batt failsafe disabled test")
1339
self.takeoffAndMoveAway()
1340
m = self.assert_receive_message('BATTERY_STATUS')
1341
if m.charge_state != mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_OK:
1342
raise NotAchievedException("Expected state ok")
1343
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
1344
self.wait_statustext("Battery 1 is low", timeout=60)
1345
m = self.assert_receive_message('BATTERY_STATUS')
1346
if m.charge_state != mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_LOW:
1347
raise NotAchievedException("Expected state low")
1348
self.delay_sim_time(5)
1349
self.wait_mode("ALT_HOLD")
1350
self.set_parameter('SIM_BATT_VOLTAGE', 10.0)
1351
self.wait_statustext("Battery 1 is critical", timeout=60)
1352
m = self.assert_receive_message('BATTERY_STATUS')
1353
if m.charge_state != mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_CRITICAL:
1354
raise NotAchievedException("Expected state critical")
1355
self.delay_sim_time(5)
1356
self.wait_mode("ALT_HOLD")
1357
self.change_mode("RTL")
1358
self.wait_rtl_complete()
1359
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
1360
self.reboot_sitl()
1361
self.end_subtest("Completed Batt failsafe disabled test")
1362
1363
# TWO STAGE BATTERY FAILSAFE: Trigger low battery condition,
1364
# then critical battery condition. Verify RTL and Land actions
1365
# complete.
1366
self.start_subtest("Two stage battery failsafe test with RTL and Land")
1367
self.takeoffAndMoveAway()
1368
self.delay_sim_time(3)
1369
self.set_parameters({
1370
'BATT_FS_LOW_ACT': 2,
1371
'BATT_FS_CRT_ACT': 1,
1372
'SIM_BATT_VOLTAGE': 11.4,
1373
})
1374
self.wait_statustext("Battery 1 is low", timeout=60)
1375
self.delay_sim_time(5)
1376
self.wait_mode("RTL")
1377
self.delay_sim_time(10)
1378
self.set_parameter('SIM_BATT_VOLTAGE', 10.0)
1379
self.wait_statustext("Battery 1 is critical", timeout=60)
1380
self.delay_sim_time(5)
1381
self.wait_mode("LAND")
1382
self.wait_landed_and_disarmed()
1383
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
1384
self.reboot_sitl()
1385
self.end_subtest("Completed two stage battery failsafe test with RTL and Land")
1386
1387
# TWO STAGE BATTERY FAILSAFE: Trigger low battery condition,
1388
# then critical battery condition. Verify both SmartRTL
1389
# actions complete
1390
self.start_subtest("Two stage battery failsafe test with SmartRTL")
1391
self.takeoffAndMoveAway()
1392
self.set_parameter('BATT_FS_LOW_ACT', 3)
1393
self.set_parameter('BATT_FS_CRT_ACT', 4)
1394
self.delay_sim_time(10)
1395
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
1396
self.wait_statustext("Battery 1 is low", timeout=60)
1397
self.delay_sim_time(5)
1398
self.wait_mode("SMART_RTL")
1399
self.change_mode("LOITER")
1400
self.delay_sim_time(10)
1401
self.set_parameter('SIM_BATT_VOLTAGE', 10.0)
1402
self.wait_statustext("Battery 1 is critical", timeout=60)
1403
self.delay_sim_time(5)
1404
self.wait_mode("SMART_RTL")
1405
self.wait_disarmed()
1406
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
1407
self.reboot_sitl()
1408
self.end_subtest("Completed two stage battery failsafe test with SmartRTL")
1409
1410
# Trigger low battery condition in land mode with FS_OPTIONS
1411
# set to allow land mode to continue. Verify landing completes
1412
# uninterrupted.
1413
self.start_subtest("Battery failsafe with FS_OPTIONS set to continue landing")
1414
self.takeoffAndMoveAway()
1415
self.set_parameter('FS_OPTIONS', 8)
1416
self.change_mode("LAND")
1417
self.delay_sim_time(5)
1418
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
1419
self.wait_statustext("Battery 1 is low", timeout=60)
1420
self.delay_sim_time(5)
1421
self.wait_mode("LAND")
1422
self.wait_landed_and_disarmed()
1423
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
1424
self.reboot_sitl()
1425
self.end_subtest("Completed battery failsafe with FS_OPTIONS set to continue landing")
1426
1427
# Trigger a critical battery condition, which triggers a land
1428
# mode failsafe. Trigger an RC failure. Verify the RC failsafe
1429
# is prevented from stopping the low battery landing.
1430
self.start_subtest("Battery failsafe critical landing")
1431
self.takeoffAndMoveAway(100, 50)
1432
self.set_parameters({
1433
'FS_OPTIONS': 0,
1434
'BATT_FS_LOW_ACT': 1,
1435
'BATT_FS_CRT_ACT': 1,
1436
'FS_THR_ENABLE': 1,
1437
})
1438
self.delay_sim_time(5)
1439
self.set_parameter('SIM_BATT_VOLTAGE', 10.0)
1440
self.wait_statustext("Battery 1 is critical", timeout=60)
1441
self.wait_mode("LAND")
1442
self.delay_sim_time(10)
1443
self.set_parameter("SIM_RC_FAIL", 1)
1444
self.delay_sim_time(10)
1445
self.wait_mode("LAND")
1446
self.wait_landed_and_disarmed()
1447
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
1448
self.set_parameter("SIM_RC_FAIL", 0)
1449
self.reboot_sitl()
1450
self.end_subtest("Completed battery failsafe critical landing")
1451
1452
# Trigger low battery condition with failsafe set to brake/land
1453
self.start_subtest("Battery failsafe brake/land")
1454
self.context_push()
1455
self.takeoffAndMoveAway()
1456
self.set_parameter('BATT_FS_LOW_ACT', 7)
1457
self.delay_sim_time(10)
1458
self.change_mode('LOITER')
1459
self.set_rc(1, 2000)
1460
self.wait_groundspeed(8, 10)
1461
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
1462
self.wait_statustext("Battery 1 is low", timeout=60)
1463
self.wait_mode('BRAKE')
1464
self.set_rc(1, 1500)
1465
self.disarm_vehicle(force=True)
1466
self.context_pop()
1467
self.reboot_sitl()
1468
self.end_subtest("Completed brake/land failsafe test")
1469
1470
self.start_subtest("Battery failsafe brake/land - land")
1471
self.context_push()
1472
self.takeoffAndMoveAway()
1473
self.set_parameter('BATT_FS_LOW_ACT', 7)
1474
self.set_parameter('SIM_GPS1_ENABLE', 0)
1475
self.delay_sim_time(10)
1476
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
1477
self.wait_statustext("Battery 1 is low", timeout=60)
1478
self.wait_mode('LAND')
1479
self.disarm_vehicle(force=True)
1480
self.reboot_sitl()
1481
self.context_pop()
1482
self.end_subtest("Completed brake/land failsafe test")
1483
1484
# Trigger low battery condition with failsafe set to terminate. Copter will disarm and crash.
1485
self.start_subtest("Battery failsafe terminate")
1486
self.takeoffAndMoveAway()
1487
self.set_parameter('BATT_FS_LOW_ACT', 5)
1488
self.delay_sim_time(10)
1489
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
1490
self.wait_statustext("Battery 1 is low", timeout=60)
1491
self.wait_disarmed()
1492
self.end_subtest("Completed terminate failsafe test")
1493
1494
self.progress("All Battery failsafe tests complete")
1495
1496
def BatteryMissing(self):
1497
''' Test battery health pre-arm and missing failsafe'''
1498
self.context_push()
1499
1500
# Should be good to arm with no changes
1501
self.wait_ready_to_arm()
1502
1503
# Make monitor unhealthy, this should result in unhealthy prearm
1504
self.set_parameters({
1505
'BATT_VOLT_PIN': -1,
1506
})
1507
1508
self.drain_mav()
1509
1510
# Battery should go unhealthy immediately
1511
self.assert_prearm_failure("Battery 1 unhealthy", other_prearm_failures_fatal=False)
1512
1513
# Return monitor to health
1514
self.context_pop()
1515
self.context_push()
1516
1517
self.wait_ready_to_arm()
1518
1519
# take off and then trigger in flight
1520
self.takeoff(10, mode="LOITER")
1521
self.set_parameters({
1522
'BATT_VOLT_PIN': -1,
1523
})
1524
1525
# Should trigger missing failsafe
1526
self.wait_statustext("Battery 1 is missing")
1527
1528
# Done, reset params and reboot to clear failsafe
1529
self.land_and_disarm()
1530
self.context_pop()
1531
self.reboot_sitl()
1532
1533
def VibrationFailsafe(self):
1534
'''Test Vibration Failsafe'''
1535
self.context_push()
1536
1537
# takeoff in Loiter to 20m
1538
self.takeoff(20, mode="LOITER")
1539
1540
# simulate accel bias caused by high vibration
1541
self.set_parameters({
1542
'SIM_ACC1_BIAS_Z': 2,
1543
'SIM_ACC2_BIAS_Z': 2,
1544
'SIM_ACC3_BIAS_Z': 2,
1545
})
1546
1547
# wait for Vibration compensation warning and change to LAND mode
1548
self.wait_statustext("Vibration compensation ON", timeout=30)
1549
self.change_mode("LAND")
1550
1551
# check vehicle descends to 2m or less within 40 seconds
1552
self.wait_altitude(-5, 2, timeout=50, relative=True)
1553
1554
# force disarm of vehicle (it will likely not automatically disarm)
1555
self.disarm_vehicle(force=True)
1556
1557
# revert simulated accel bias and reboot to restore EKF health
1558
self.context_pop()
1559
self.reboot_sitl()
1560
1561
# Tests the motor failsafe
1562
def TakeoffCheck(self):
1563
'''Test takeoff check'''
1564
self.set_parameters({
1565
"AHRS_EKF_TYPE": 10,
1566
'SIM_ESC_TELEM': 1,
1567
'SIM_ESC_ARM_RPM': 500,
1568
'TKOFF_RPM_MIN': 1000,
1569
})
1570
1571
self.test_takeoff_check_mode("STABILIZE")
1572
self.test_takeoff_check_mode("ACRO")
1573
self.test_takeoff_check_mode("LOITER")
1574
self.test_takeoff_check_mode("ALT_HOLD")
1575
# self.test_takeoff_check_mode("FLOWHOLD")
1576
self.test_takeoff_check_mode("GUIDED", True)
1577
self.test_takeoff_check_mode("POSHOLD")
1578
# self.test_takeoff_check_mode("SPORT")
1579
1580
self.set_parameters({
1581
"AHRS_EKF_TYPE": 10,
1582
'SIM_ESC_TELEM': 1,
1583
'TKOFF_RPM_MIN': 1,
1584
'TKOFF_RPM_MAX': 3,
1585
})
1586
self.test_takeoff_check_mode("STABILIZE")
1587
self.test_takeoff_check_mode("ACRO")
1588
self.test_takeoff_check_mode("LOITER")
1589
self.test_takeoff_check_mode("ALT_HOLD")
1590
# self.test_takeoff_check_mode("FLOWHOLD")
1591
self.test_takeoff_check_mode("GUIDED", True)
1592
self.test_takeoff_check_mode("POSHOLD")
1593
# self.test_takeoff_check_mode("SPORT")
1594
1595
def assert_dataflash_message_field_level_at(self,
1596
mtype,
1597
field,
1598
level,
1599
maintain=1,
1600
tolerance=0.05,
1601
timeout=30,
1602
condition=None,
1603
dfreader_start_timestamp=None,
1604
verbose=False):
1605
'''wait for EKF's accel bias to reach a level and maintain it'''
1606
1607
if verbose:
1608
self.progress("current onboard log filepath: %s" % self.current_onboard_log_filepath())
1609
dfreader = self.dfreader_for_current_onboard_log()
1610
1611
achieve_start = None
1612
current_value = None
1613
while True:
1614
m = dfreader.recv_match(type=mtype, condition=condition)
1615
if m is None:
1616
raise NotAchievedException("%s.%s did not maintain %f" %
1617
(mtype, field, level))
1618
if dfreader_start_timestamp is not None:
1619
if m.TimeUS < dfreader_start_timestamp:
1620
continue
1621
if verbose:
1622
print("m=%s" % str(m))
1623
current_value = getattr(m, field)
1624
1625
if abs(current_value - level) > tolerance:
1626
if achieve_start is not None:
1627
self.progress("Achieve stop at %u" % m.TimeUS)
1628
achieve_start = None
1629
continue
1630
1631
dfreader_now = m.TimeUS
1632
if achieve_start is None:
1633
self.progress("Achieve start at %u (got=%f want=%f)" %
1634
(dfreader_now, current_value, level))
1635
if maintain is None:
1636
return
1637
achieve_start = m.TimeUS
1638
continue
1639
1640
# we're achieving....
1641
if dfreader_now - achieve_start > maintain*1e6:
1642
return dfreader_now
1643
1644
# Tests any EK3 accel bias is subtracted from the correct IMU data
1645
def EK3AccelBias(self):
1646
'''Test EK3 Accel Bias data'''
1647
self.context_push()
1648
1649
self.start_test("Test zero bias")
1650
self.delay_sim_time(2)
1651
dfreader_tstart = self.assert_dataflash_message_field_level_at(
1652
"XKF2",
1653
"AZ",
1654
0.0,
1655
condition="XKF2.C==1",
1656
maintain=1,
1657
)
1658
1659
# Add 2m/s/s bias to the second IMU
1660
self.set_parameters({
1661
'SIM_ACC2_BIAS_Z': 0.7,
1662
})
1663
1664
self.start_subtest("Ensuring second core has bias")
1665
self.delay_sim_time(30)
1666
dfreader_tstart = self.assert_dataflash_message_field_level_at(
1667
"XKF2", "AZ", 0.7,
1668
condition="XKF2.C==1",
1669
)
1670
1671
self.start_subtest("Ensuring earth frame is compensated")
1672
self.assert_dataflash_message_field_level_at(
1673
"RATE", "A", 0,
1674
maintain=1,
1675
tolerance=2, # RATE.A is in cm/s/s
1676
dfreader_start_timestamp=dfreader_tstart,
1677
)
1678
1679
# now switch the EKF to only using the second core:
1680
self.set_parameters({
1681
'SIM_ACC2_BIAS_Z': 0.0,
1682
"EK3_IMU_MASK": 0b10,
1683
})
1684
self.reboot_sitl()
1685
1686
self.delay_sim_time(30)
1687
dfreader_tstart = self.assert_dataflash_message_field_level_at(
1688
"XKF2", "AZ", 0.0,
1689
condition="XKF2.C==0",
1690
)
1691
1692
# Add 2m/s/s bias to the second IMU
1693
self.set_parameters({
1694
'SIM_ACC2_BIAS_Z': 0.7,
1695
})
1696
1697
self.start_subtest("Ensuring first core now has bias")
1698
self.delay_sim_time(30)
1699
dfreader_tstart = self.assert_dataflash_message_field_level_at(
1700
"XKF2", "AZ", 0.7,
1701
condition="XKF2.C==0",
1702
)
1703
1704
self.start_subtest("Ensuring earth frame is compensated")
1705
self.assert_dataflash_message_field_level_at(
1706
"RATE", "A", 0,
1707
maintain=1,
1708
tolerance=2, # RATE.A is in cm/s/s
1709
dfreader_start_timestamp=dfreader_tstart,
1710
verbose=True,
1711
)
1712
1713
# revert simulated accel bias and reboot to restore EKF health
1714
self.context_pop()
1715
self.reboot_sitl()
1716
1717
# StabilityPatch - fly south, then hold loiter within 5m
1718
# position and altitude and reduce 1 motor to 60% efficiency
1719
def StabilityPatch(self,
1720
holdtime=30,
1721
maxaltchange=5,
1722
maxdistchange=10):
1723
'''Fly stability patch'''
1724
self.takeoff(10, mode="LOITER")
1725
1726
# first south
1727
self.progress("turn south")
1728
self.set_rc(4, 1580)
1729
self.wait_heading(180)
1730
self.set_rc(4, 1500)
1731
1732
# fly west 80m
1733
self.set_rc(2, 1100)
1734
self.wait_distance(80)
1735
self.set_rc(2, 1500)
1736
1737
# wait for copter to slow moving
1738
self.wait_groundspeed(0, 2)
1739
1740
m = self.assert_receive_message('VFR_HUD')
1741
start_altitude = m.alt
1742
start = self.mav.location()
1743
tstart = self.get_sim_time()
1744
self.progress("Holding loiter at %u meters for %u seconds" %
1745
(start_altitude, holdtime))
1746
1747
# cut motor 1's to efficiency
1748
self.progress("Cutting motor 1 to 65% efficiency")
1749
self.set_parameters({
1750
"SIM_ENGINE_MUL": 0.65,
1751
"SIM_ENGINE_FAIL": 1 << 0, # motor 1
1752
})
1753
1754
while self.get_sim_time_cached() < tstart + holdtime:
1755
m = self.assert_receive_message('VFR_HUD')
1756
pos = self.mav.location()
1757
delta = self.get_distance(start, pos)
1758
alt_delta = math.fabs(m.alt - start_altitude)
1759
self.progress("Loiter Dist: %.2fm, alt:%u" % (delta, m.alt))
1760
if alt_delta > maxaltchange:
1761
raise NotAchievedException(
1762
"Loiter alt shifted %u meters (> limit %u)" %
1763
(alt_delta, maxaltchange))
1764
if delta > maxdistchange:
1765
raise NotAchievedException(
1766
("Loiter shifted %u meters (> limit of %u)" %
1767
(delta, maxdistchange)))
1768
1769
# restore motor 1 to 100% efficiency
1770
self.set_parameter("SIM_ENGINE_MUL", 1.0)
1771
1772
self.progress("Stability patch and Loiter OK for %us" % holdtime)
1773
1774
self.progress("RTL after stab patch")
1775
self.do_RTL()
1776
1777
def debug_arming_issue(self):
1778
while True:
1779
self.send_mavlink_arm_command()
1780
m = self.mav.recv_match(blocking=True, timeout=1)
1781
if m is None:
1782
continue
1783
if m.get_type() in ["STATUSTEXT", "COMMAND_ACK"]:
1784
print("Got: %s" % str(m))
1785
if self.mav.motors_armed():
1786
self.progress("Armed")
1787
return
1788
1789
# fly_fence_test - fly east until you hit the horizontal circular fence
1790
avoid_behave_slide = 0
1791
1792
def fly_fence_avoid_test_radius_check(self, timeout=180, avoid_behave=avoid_behave_slide):
1793
using_mode = "LOITER" # must be something which adjusts velocity!
1794
self.change_mode(using_mode)
1795
fence_radius = 15
1796
fence_margin = 3
1797
self.set_parameters({
1798
"FENCE_ENABLE": 1, # fence
1799
"FENCE_TYPE": 2, # circle
1800
"FENCE_RADIUS": fence_radius,
1801
"FENCE_MARGIN": fence_margin,
1802
"AVOID_ENABLE": 1,
1803
"AVOID_BEHAVE": avoid_behave,
1804
"RC10_OPTION": 40, # avoid-enable
1805
})
1806
self.wait_ready_to_arm()
1807
self.set_rc(10, 2000)
1808
home_distance = self.distance_to_home(use_cached_home=True)
1809
if home_distance > 5:
1810
raise PreconditionFailedException("Expected to be within 5m of home")
1811
self.zero_throttle()
1812
self.arm_vehicle()
1813
self.set_rc(3, 1700)
1814
self.wait_altitude(10, 100, relative=True)
1815
self.set_rc(3, 1500)
1816
self.set_rc(2, 1400)
1817
self.wait_distance_to_home(12, 20, timeout=30)
1818
tstart = self.get_sim_time()
1819
push_time = 70 # push against barrier for 60 seconds
1820
failed_max = False
1821
failed_min = False
1822
while True:
1823
if self.get_sim_time() - tstart > push_time:
1824
self.progress("Push time up")
1825
break
1826
# make sure we don't RTL:
1827
if not self.mode_is(using_mode):
1828
raise NotAchievedException("Changed mode away from %s" % using_mode)
1829
distance = self.distance_to_home(use_cached_home=True)
1830
inner_radius = fence_radius - fence_margin
1831
want_min = inner_radius - 1 # allow 1m either way
1832
want_max = inner_radius + 1 # allow 1m either way
1833
self.progress("Push: distance=%f %f<want<%f" %
1834
(distance, want_min, want_max))
1835
if distance < want_min:
1836
if failed_min is False:
1837
self.progress("Failed min")
1838
failed_min = True
1839
if distance > want_max:
1840
if failed_max is False:
1841
self.progress("Failed max")
1842
failed_max = True
1843
if failed_min and failed_max:
1844
raise NotAchievedException("Failed both min and max checks. Clever")
1845
if failed_min:
1846
raise NotAchievedException("Failed min")
1847
if failed_max:
1848
raise NotAchievedException("Failed max")
1849
self.set_rc(2, 1500)
1850
self.do_RTL()
1851
1852
def HorizontalAvoidFence(self, timeout=180):
1853
'''Test horizontal Avoidance fence'''
1854
self.fly_fence_avoid_test_radius_check(avoid_behave=1, timeout=timeout)
1855
self.fly_fence_avoid_test_radius_check(avoid_behave=0, timeout=timeout)
1856
1857
# fly_fence_test - fly east until you hit the horizontal circular fence
1858
def HorizontalFence(self, timeout=180):
1859
'''Test horizontal fence'''
1860
# enable fence, disable avoidance
1861
self.set_parameters({
1862
"FENCE_ENABLE": 1,
1863
"AVOID_ENABLE": 0,
1864
})
1865
1866
self.change_mode("LOITER")
1867
self.wait_ready_to_arm()
1868
1869
# fence requires home to be set:
1870
m = self.poll_home_position(quiet=False)
1871
1872
self.start_subtest("ensure we can't arm if outside fence")
1873
self.load_fence("fence-in-middle-of-nowhere.txt")
1874
1875
self.delay_sim_time(5) # let fence check run so it loads-from-eeprom
1876
self.assert_prearm_failure("Vehicle breaching Polygon fence")
1877
self.progress("Failed to arm outside fence (good!)")
1878
self.clear_fence()
1879
self.delay_sim_time(5) # let fence breach clear
1880
self.drain_mav()
1881
self.end_subtest("ensure we can't arm if outside fence")
1882
1883
self.start_subtest("ensure we can't arm with bad radius")
1884
self.context_push()
1885
self.set_parameter("FENCE_RADIUS", -1)
1886
self.assert_prearm_failure("Invalid Circle FENCE_RADIUS value")
1887
self.context_pop()
1888
self.progress("Failed to arm with bad radius")
1889
self.drain_mav()
1890
self.end_subtest("ensure we can't arm with bad radius")
1891
1892
self.start_subtest("ensure we can't arm with bad alt")
1893
self.context_push()
1894
self.set_parameter("FENCE_ALT_MAX", -1)
1895
self.assert_prearm_failure("Invalid FENCE_ALT_MAX value")
1896
self.context_pop()
1897
self.progress("Failed to arm with bad altitude")
1898
self.end_subtest("ensure we can't arm with bad radius")
1899
1900
self.start_subtest("Check breach-fence behaviour")
1901
self.set_parameter("FENCE_TYPE", 2)
1902
self.takeoff(10, mode="LOITER")
1903
1904
# first east
1905
self.progress("turn east")
1906
self.set_rc(4, 1580)
1907
self.wait_heading(160, timeout=60)
1908
self.set_rc(4, 1500)
1909
1910
fence_radius = self.get_parameter("FENCE_RADIUS")
1911
1912
self.progress("flying forward (east) until we hit fence")
1913
pitching_forward = True
1914
self.set_rc(2, 1100)
1915
1916
self.progress("Waiting for fence breach")
1917
tstart = self.get_sim_time()
1918
while not self.mode_is("RTL"):
1919
if self.get_sim_time_cached() - tstart > 30:
1920
raise NotAchievedException("Did not breach fence")
1921
1922
m = self.assert_receive_message('GLOBAL_POSITION_INT')
1923
alt = m.relative_alt / 1000.0 # mm -> m
1924
home_distance = self.distance_to_home(use_cached_home=True)
1925
self.progress("Alt: %.02f HomeDistance: %.02f (fence radius=%f)" %
1926
(alt, home_distance, fence_radius))
1927
1928
self.progress("Waiting until we get home and disarm")
1929
tstart = self.get_sim_time()
1930
while self.get_sim_time_cached() < tstart + timeout:
1931
m = self.assert_receive_message('GLOBAL_POSITION_INT')
1932
alt = m.relative_alt / 1000.0 # mm -> m
1933
home_distance = self.distance_to_home(use_cached_home=True)
1934
self.progress("Alt: %.02f HomeDistance: %.02f" %
1935
(alt, home_distance))
1936
# recenter pitch sticks once we're home so we don't fly off again
1937
if pitching_forward and home_distance < 50:
1938
pitching_forward = False
1939
self.set_rc(2, 1475)
1940
# disable fence
1941
self.set_parameter("FENCE_ENABLE", 0)
1942
if (alt <= 1 and home_distance < 10) or (not self.armed() and home_distance < 10):
1943
# reduce throttle
1944
self.zero_throttle()
1945
self.change_mode("LAND")
1946
self.wait_landed_and_disarmed()
1947
self.progress("Reached home OK")
1948
self.zero_throttle()
1949
return
1950
1951
# give we're testing RTL, doing one here probably doesn't make sense
1952
home_distance = self.distance_to_home(use_cached_home=True)
1953
raise AutoTestTimeoutException(
1954
"Fence test failed to reach home (%fm distance) - "
1955
"timed out after %u seconds" % (home_distance, timeout,))
1956
1957
# MaxAltFence - fly up until you hit the fence ceiling
1958
def MaxAltFence(self):
1959
'''Test Max Alt Fence'''
1960
self.takeoff(10, mode="LOITER")
1961
"""Hold loiter position."""
1962
1963
# enable fence, disable avoidance
1964
self.set_parameters({
1965
"FENCE_ENABLE": 1,
1966
"AVOID_ENABLE": 0,
1967
"FENCE_TYPE": 1,
1968
"FENCE_ENABLE" : 1,
1969
})
1970
1971
self.change_alt(10)
1972
1973
# first east
1974
self.progress("turning east")
1975
self.set_rc(4, 1580)
1976
self.wait_heading(160, timeout=60)
1977
self.set_rc(4, 1500)
1978
1979
self.progress("flying east 20m")
1980
self.set_rc(2, 1100)
1981
self.wait_distance(20)
1982
1983
self.progress("flying up")
1984
self.set_rc_from_map({
1985
2: 1500,
1986
3: 1800,
1987
})
1988
1989
# wait for fence to trigger
1990
self.wait_mode('RTL', timeout=120)
1991
1992
self.wait_rtl_complete()
1993
1994
self.zero_throttle()
1995
1996
# MaxAltFence - fly up and make sure fence action does not trigger
1997
# Also check that the vehicle will not try and descend too fast when trying to backup from a max alt fence due to avoidance
1998
def MaxAltFenceAvoid(self):
1999
'''Test Max Alt Fence Avoidance'''
2000
self.takeoff(10, mode="LOITER")
2001
"""Hold loiter position."""
2002
2003
# enable fence, only max altitude, default is 100m
2004
# No action, rely on avoidance to prevent the breach
2005
self.set_parameters({
2006
"FENCE_ENABLE": 1,
2007
"FENCE_TYPE": 1,
2008
"FENCE_ACTION": 0,
2009
})
2010
2011
# Try and fly past the fence
2012
self.set_rc(3, 1920)
2013
2014
# Avoid should prevent the vehicle flying past the fence, so the altitude wait should timeouts
2015
try:
2016
self.wait_altitude(140, 150, timeout=90, relative=True)
2017
raise NotAchievedException("Avoid should prevent reaching altitude")
2018
except AutoTestTimeoutException:
2019
pass
2020
except Exception as e:
2021
raise e
2022
2023
# Check descent is not too fast, allow 10% above the configured backup speed
2024
max_descent_rate = -self.get_parameter("AVOID_BACKUP_SPD") * 1.1
2025
2026
def get_climb_rate(mav, m):
2027
m_type = m.get_type()
2028
if m_type != 'VFR_HUD':
2029
return
2030
if m.climb < max_descent_rate:
2031
raise NotAchievedException("Descending too fast want %f got %f" % (max_descent_rate, m.climb))
2032
2033
self.context_push()
2034
self.install_message_hook_context(get_climb_rate)
2035
2036
# Reduce fence alt, this will result in a fence breach, but there is no action.
2037
# Avoid should then backup the vehicle to be under the new fence alt.
2038
self.set_parameters({
2039
"FENCE_ALT_MAX": 50,
2040
})
2041
self.wait_altitude(40, 50, timeout=90, relative=True)
2042
2043
self.context_pop()
2044
2045
self.set_rc(3, 1500)
2046
self.do_RTL()
2047
2048
# fly_alt_min_fence_test - fly down until you hit the fence floor
2049
def MinAltFence(self):
2050
'''Test Min Alt Fence'''
2051
self.takeoff(30, mode="LOITER", timeout=60)
2052
2053
# enable fence, disable avoidance
2054
self.set_parameters({
2055
"AVOID_ENABLE": 0,
2056
"FENCE_ENABLE" : 1,
2057
"FENCE_TYPE": 8,
2058
"FENCE_ALT_MIN": 20,
2059
})
2060
2061
self.change_alt(30)
2062
2063
# Activate the floor fence
2064
# TODO this test should run without requiring this
2065
self.do_fence_enable()
2066
2067
# first east
2068
self.progress("turn east")
2069
self.set_rc(4, 1580)
2070
self.wait_heading(160, timeout=60)
2071
self.set_rc(4, 1500)
2072
2073
# fly forward (east) at least 20m
2074
self.set_rc(2, 1100)
2075
self.wait_distance(20)
2076
2077
# stop flying forward and start flying down:
2078
self.set_rc_from_map({
2079
2: 1500,
2080
3: 1200,
2081
})
2082
2083
# wait for fence to trigger
2084
self.wait_mode('RTL', timeout=120)
2085
2086
self.wait_rtl_complete()
2087
2088
# Disable the fence using mavlink command to ensure cleaned up SITL state
2089
self.do_fence_disable()
2090
2091
self.zero_throttle()
2092
2093
# MinAltFenceAvoid - fly down and make sure fence action does not trigger
2094
# Also check that the vehicle will not try and ascend too fast when trying to backup from a min alt fence due to avoidance
2095
def MinAltFenceAvoid(self):
2096
'''Test Min Alt Fence Avoidance'''
2097
2098
# enable fence, only min altitude
2099
# No action, rely on avoidance to prevent the breach
2100
self.set_parameters({
2101
"FENCE_ENABLE": 1,
2102
"FENCE_TYPE": 8,
2103
"FENCE_ALT_MIN": 20,
2104
"FENCE_ACTION": 0,
2105
})
2106
self.reboot_sitl()
2107
2108
self.takeoff(30, mode="LOITER")
2109
"""Hold loiter position."""
2110
2111
# Try and fly past the fence
2112
self.set_rc(3, 1120)
2113
2114
# Avoid should prevent the vehicle flying past the fence, so the altitude wait should timeouts
2115
try:
2116
self.wait_altitude(10, 15, timeout=90, relative=True)
2117
raise NotAchievedException("Avoid should prevent reaching altitude")
2118
except AutoTestTimeoutException:
2119
pass
2120
except Exception as e:
2121
raise e
2122
2123
# Check ascent is not too fast, allow 10% above the configured backup speed
2124
max_ascent_rate = self.get_parameter("AVOID_BACKUP_SPD") * 1.1
2125
2126
def get_climb_rate(mav, m):
2127
m_type = m.get_type()
2128
if m_type != 'VFR_HUD':
2129
return
2130
if m.climb > max_ascent_rate:
2131
raise NotAchievedException("Ascending too fast want %f got %f" % (max_ascent_rate, m.climb))
2132
2133
self.context_push()
2134
self.install_message_hook_context(get_climb_rate)
2135
2136
# Reduce fence alt, this will result in a fence breach, but there is no action.
2137
# Avoid should then backup the vehicle to be over the new fence alt.
2138
self.set_parameters({
2139
"FENCE_ALT_MIN": 30,
2140
})
2141
self.wait_altitude(30, 40, timeout=90, relative=True)
2142
2143
self.context_pop()
2144
2145
self.set_rc(3, 1500)
2146
self.do_RTL()
2147
2148
def FenceFloorEnabledLanding(self):
2149
"""Ensures we can initiate and complete an RTL while the fence is
2150
enabled.
2151
"""
2152
fence_bit = mavutil.mavlink.MAV_SYS_STATUS_GEOFENCE
2153
2154
self.progress("Test Landing while fence floor enabled")
2155
self.set_parameters({
2156
"AVOID_ENABLE": 0,
2157
"FENCE_ENABLE" : 1,
2158
"FENCE_TYPE": 15,
2159
"FENCE_ALT_MIN": 20,
2160
"FENCE_ALT_MAX": 30,
2161
})
2162
2163
self.change_mode("GUIDED")
2164
self.wait_ready_to_arm()
2165
self.arm_vehicle()
2166
self.user_takeoff(alt_min=25)
2167
2168
# Check fence is enabled
2169
self.assert_fence_enabled()
2170
2171
# Change to RC controlled mode
2172
self.change_mode('LOITER')
2173
2174
self.set_rc(3, 1800)
2175
2176
self.wait_mode('RTL', timeout=120)
2177
# center throttle
2178
self.set_rc(3, 1500)
2179
2180
# wait until we are below the fence floor and re-enter loiter
2181
self.wait_altitude(5, 15, relative=True)
2182
self.change_mode('LOITER')
2183
# wait for manual recovery to expire
2184
self.delay_sim_time(15)
2185
2186
# lower throttle and try and land
2187
self.set_rc(3, 1300)
2188
self.wait_altitude(0, 2, relative=True)
2189
self.zero_throttle()
2190
self.wait_landed_and_disarmed()
2191
self.assert_fence_enabled()
2192
# must not be in RTL
2193
self.assert_mode("LOITER")
2194
2195
# Assert fence is healthy since it was enabled automatically
2196
self.assert_sensor_state(fence_bit, healthy=True)
2197
2198
# Disable the fence using mavlink command to ensure cleaned up SITL state
2199
self.do_fence_disable()
2200
self.assert_fence_disabled()
2201
2202
def FenceFloorAutoDisableLanding(self):
2203
"""Ensures we can initiate and complete an RTL while the fence is enabled"""
2204
2205
fence_bit = mavutil.mavlink.MAV_SYS_STATUS_GEOFENCE
2206
2207
self.progress("Test Landing while fence floor enabled")
2208
self.set_parameters({
2209
"AVOID_ENABLE": 0,
2210
"FENCE_TYPE": 11,
2211
"FENCE_ALT_MIN": 10,
2212
"FENCE_ALT_MAX": 20,
2213
"FENCE_AUTOENABLE" : 1,
2214
})
2215
2216
self.change_mode("GUIDED")
2217
self.wait_ready_to_arm()
2218
self.arm_vehicle()
2219
self.takeoff(alt_min=15, mode="GUIDED")
2220
2221
# Check fence is enabled
2222
self.assert_fence_enabled()
2223
2224
# Change to RC controlled mode
2225
self.change_mode('LOITER')
2226
2227
self.set_rc(3, 1800)
2228
2229
self.wait_mode('RTL', timeout=120)
2230
2231
self.wait_landed_and_disarmed(0)
2232
# the breach should have cleared since we auto-disable the
2233
# fence on landing
2234
self.assert_fence_disabled()
2235
2236
# Assert fences have gone now that we have landed and disarmed
2237
self.assert_sensor_state(fence_bit, present=True, enabled=False)
2238
2239
def FenceFloorAutoEnableOnArming(self):
2240
"""Ensures we can auto-enable fences on arming and still takeoff and land"""
2241
2242
fence_bit = mavutil.mavlink.MAV_SYS_STATUS_GEOFENCE
2243
2244
self.set_parameters({
2245
"AVOID_ENABLE": 0,
2246
"FENCE_TYPE": 11,
2247
"FENCE_ALT_MIN": 10,
2248
"FENCE_ALT_MAX": 20,
2249
"FENCE_AUTOENABLE" : 3,
2250
})
2251
2252
self.change_mode("GUIDED")
2253
# Check fence is not enabled
2254
self.assert_fence_disabled()
2255
2256
self.wait_ready_to_arm()
2257
self.arm_vehicle()
2258
self.takeoff(alt_min=15, mode="GUIDED")
2259
2260
# Check fence is enabled
2261
self.assert_fence_enabled()
2262
2263
# Change to RC controlled mode
2264
self.change_mode('LOITER')
2265
2266
self.set_rc(3, 1800)
2267
2268
self.wait_mode('RTL', timeout=120)
2269
# Assert fence is not healthy now that we are in RTL
2270
self.assert_sensor_state(fence_bit, healthy=False)
2271
2272
self.wait_landed_and_disarmed(0)
2273
# the breach should have cleared since we auto-disable the
2274
# fence on landing
2275
self.assert_fence_disabled()
2276
2277
# Assert fences have gone now that we have landed and disarmed
2278
self.assert_sensor_state(fence_bit, present=True, enabled=False)
2279
2280
# Disable the fence using mavlink command to ensure cleaned up SITL state
2281
self.assert_fence_disabled()
2282
2283
def FenceMargin(self, timeout=180):
2284
'''Test warning on crossing fence margin'''
2285
# enable fence, disable avoidance
2286
self.set_parameters({
2287
"FENCE_ENABLE": 1,
2288
"FENCE_TYPE": 6, # polygon and circle fences
2289
"FENCE_MARGIN" : 30,
2290
"FENCE_RADIUS" : 150,
2291
"AVOID_ENABLE": 0,
2292
"FENCE_OPTIONS": 4
2293
})
2294
2295
self.change_mode("LOITER")
2296
self.wait_ready_to_arm()
2297
2298
# fence requires home to be set:
2299
m = self.poll_home_position(quiet=False)
2300
2301
# 110m polyfence
2302
home_loc = self.mav.location()
2303
radius = self.get_parameter("FENCE_RADIUS")
2304
if self.use_map and self.mavproxy is not None:
2305
self.mavproxy.send("map circle %f %f %f green\n" % (home_loc.lat, home_loc.lng, radius))
2306
2307
locs = [
2308
self.offset_location_ne(home_loc, -110, -110),
2309
self.offset_location_ne(home_loc, 110, -110),
2310
self.offset_location_ne(home_loc, 110, 110),
2311
self.offset_location_ne(home_loc, -110, 110),
2312
]
2313
self.upload_fences_from_locations([
2314
(mavutil.mavlink.MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION, locs),
2315
])
2316
2317
self.takeoff(10, mode="LOITER")
2318
2319
# first east
2320
self.progress("turn east")
2321
self.set_rc(4, 1580)
2322
self.wait_heading(160, timeout=60)
2323
self.set_rc(4, 1500)
2324
2325
fence_radius = self.get_parameter("FENCE_RADIUS")
2326
2327
self.progress("flying forward (east) until we hit fence")
2328
pitching_forward = True
2329
self.set_rc(2, 1100)
2330
self.wait_statustext("Polygon fence in ([0-9]+[.])?[0-9]?m", regex=True)
2331
2332
self.wait_statustext("Circle and Polygon fences in ([0-9]+[.])?[0-9]?m", regex=True)
2333
self.progress("Waiting for fence breach")
2334
tstart = self.get_sim_time()
2335
while not self.mode_is("RTL"):
2336
if self.get_sim_time_cached() - tstart > 30:
2337
raise NotAchievedException("Did not breach fence")
2338
2339
m = self.assert_receive_message('GLOBAL_POSITION_INT')
2340
alt = m.relative_alt / 1000.0 # mm -> m
2341
home_distance = self.distance_to_home(use_cached_home=True)
2342
self.progress("Alt: %.02f HomeDistance: %.02f (fence radius=%f)" %
2343
(alt, home_distance, fence_radius))
2344
2345
self.wait_statustext("Circle fence cleared margin breach")
2346
self.progress("Waiting until we get home and disarm")
2347
tstart = self.get_sim_time()
2348
while self.get_sim_time_cached() < tstart + timeout:
2349
m = self.assert_receive_message('GLOBAL_POSITION_INT')
2350
alt = m.relative_alt / 1000.0 # mm -> m
2351
home_distance = self.distance_to_home(use_cached_home=True)
2352
self.progress("Alt: %.02f HomeDistance: %.02f" %
2353
(alt, home_distance))
2354
# recenter pitch sticks once we're home so we don't fly off again
2355
if pitching_forward and home_distance < 50:
2356
pitching_forward = False
2357
self.set_rc(2, 1475)
2358
# disable fence
2359
self.set_parameter("FENCE_ENABLE", 0)
2360
if (alt <= 1 and home_distance < 10) or (not self.armed() and home_distance < 10):
2361
# reduce throttle
2362
self.zero_throttle()
2363
self.change_mode("LAND")
2364
self.wait_landed_and_disarmed()
2365
self.progress("Reached home OK")
2366
self.zero_throttle()
2367
return
2368
2369
# give we're testing RTL, doing one here probably doesn't make sense
2370
home_distance = self.distance_to_home(use_cached_home=True)
2371
raise AutoTestTimeoutException(
2372
"Fence test failed to reach home (%fm distance) - "
2373
"timed out after %u seconds" % (home_distance, timeout,))
2374
2375
def FenceUpload_MissionItem(self, timeout=180):
2376
'''Test MISSION_ITEM fence upload/download'''
2377
self.set_parameters({
2378
"FENCE_ENABLE": 1,
2379
"FENCE_TYPE": 6, # polygon and circle fences
2380
})
2381
2382
self.poll_home_position(quiet=False)
2383
2384
home_loc = self.mav.location()
2385
2386
fence_loc = [
2387
self.offset_location_ne(home_loc, -110, -110),
2388
self.offset_location_ne(home_loc, 110, -110),
2389
self.offset_location_ne(home_loc, 110, 110),
2390
self.offset_location_ne(home_loc, -110, 110),
2391
]
2392
2393
seq = 0
2394
items = []
2395
mission_type = mavutil.mavlink.MAV_MISSION_TYPE_FENCE
2396
count = len(fence_loc)
2397
for loc in fence_loc:
2398
item = self.mav.mav.mission_item_encode(
2399
1,
2400
1,
2401
seq,
2402
mavutil.mavlink.MAV_FRAME_GLOBAL,
2403
mavutil.mavlink.MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION,
2404
0, 0,
2405
count, 0, 0, 0,
2406
loc.lat, loc.lng, 33.0,
2407
mission_type
2408
)
2409
items.append(item)
2410
seq += 1
2411
2412
self.upload_using_mission_protocol(mission_type, items)
2413
downloaded_items = self.download_using_mission_protocol(mission_type)
2414
2415
if len(downloaded_items) != len(items):
2416
raise NotAchievedException(f"Mismatch in number of items: sent={len(items)} received={len(downloaded_items)}")
2417
2418
for i, (sent, received) in enumerate(zip(items, downloaded_items)):
2419
mismatches = []
2420
2421
# Normalize lat/lon to float before comparison
2422
sent_lat = sent.x
2423
sent_lng = sent.y
2424
recv_lat = received.x / 1e7 if isinstance(received.x, int) else received.x
2425
recv_lng = received.y / 1e7 if isinstance(received.y, int) else received.y
2426
2427
if sent.command != received.command:
2428
mismatches.append(f"command: {sent.command} != {received.command}")
2429
if not math.isclose(sent_lat, recv_lat, abs_tol=1e-2):
2430
mismatches.append(f"lat: {sent_lat} != {recv_lat}")
2431
if not math.isclose(sent_lng, recv_lng, abs_tol=1e-2):
2432
mismatches.append(f"lng: {sent_lng} != {recv_lng}")
2433
if not math.isclose(sent.param1, received.param1, abs_tol=1e-3):
2434
mismatches.append(f"param1: {sent.param1} != {received.param1}")
2435
2436
if mismatches:
2437
raise NotAchievedException(f"Mismatch in item {i}: " + "; ".join(mismatches))
2438
2439
print("Fence upload/download verification passed.")
2440
2441
def assert_fence_not_breached(self):
2442
m = self.assert_receive_message('FENCE_STATUS', timeout=2)
2443
if m.breach_status != 0:
2444
raise NotAchievedException("Fence is breached")
2445
2446
def assert_fence_breached(self):
2447
m = self.assert_receive_message('FENCE_STATUS', timeout=2)
2448
if m.breach_status == 0:
2449
raise NotAchievedException("Fence is not breached")
2450
2451
def BackupFence(self):
2452
'''ensure the lateral backup fence functionality works'''
2453
self.set_parameters({
2454
"FENCE_RADIUS": 10,
2455
"FENCE_TYPE": 2, # circle only
2456
"FENCE_ENABLE": 1,
2457
"AVOID_ENABLE": 0,
2458
})
2459
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_GEOFENCE,
2460
present=True,
2461
enabled=True,
2462
healthy=True,
2463
timeout=30)
2464
self.assert_fence_not_breached()
2465
self.takeoff(10, mode='LOITER')
2466
self.set_rc(2, 1300)
2467
self.wait_mode('RTL', timeout=30)
2468
self.assert_fence_breached()
2469
self.change_mode('LOITER')
2470
# continue to suck as a pilot
2471
self.wait_mode('RTL', timeout=30)
2472
self.assert_fence_breached()
2473
self.set_rc(2, 1500) # stop sucking
2474
self.wait_disarmed()
2475
self.assert_at_home()
2476
2477
def GPSGlitchLoiter(self, timeout=30, max_distance=20):
2478
"""fly_gps_glitch_loiter_test. Fly south east in loiter and test
2479
reaction to gps glitch."""
2480
self.takeoff(10, mode="LOITER")
2481
2482
# turn on simulator display of gps and actual position
2483
if self.use_map:
2484
self.show_gps_and_sim_positions(True)
2485
2486
# set-up gps glitch array
2487
glitch_lat = [0.0002996,
2488
0.0006958,
2489
0.0009431,
2490
0.0009991,
2491
0.0009444,
2492
0.0007716,
2493
0.0006221]
2494
glitch_lon = [0.0000717,
2495
0.0000912,
2496
0.0002761,
2497
0.0002626,
2498
0.0002807,
2499
0.0002049,
2500
0.0001304]
2501
glitch_num = len(glitch_lat)
2502
self.progress("GPS Glitches:")
2503
for i in range(1, glitch_num):
2504
self.progress("glitch %d %.7f %.7f" %
2505
(i, glitch_lat[i], glitch_lon[i]))
2506
2507
# turn south east
2508
self.progress("turn south east")
2509
self.set_rc(4, 1580)
2510
try:
2511
self.wait_heading(150)
2512
self.set_rc(4, 1500)
2513
# fly forward (south east) at least 60m
2514
self.set_rc(2, 1100)
2515
self.wait_distance(60)
2516
self.set_rc(2, 1500)
2517
# wait for copter to slow down
2518
except Exception as e:
2519
if self.use_map:
2520
self.show_gps_and_sim_positions(False)
2521
raise e
2522
2523
# record time and position
2524
tstart = self.get_sim_time()
2525
tnow = tstart
2526
start_pos = self.sim_location()
2527
2528
# initialise current glitch
2529
glitch_current = 0
2530
self.progress("Apply first glitch")
2531
self.set_parameters({
2532
"SIM_GPS1_GLTCH_X": glitch_lat[glitch_current],
2533
"SIM_GPS1_GLTCH_Y": glitch_lon[glitch_current],
2534
})
2535
2536
# record position for 30 seconds
2537
while tnow < tstart + timeout:
2538
tnow = self.get_sim_time_cached()
2539
desired_glitch_num = int((tnow - tstart) * 2.2)
2540
if desired_glitch_num > glitch_current and glitch_current != -1:
2541
glitch_current = desired_glitch_num
2542
# turn off glitching if we've reached the end of glitch list
2543
if glitch_current >= glitch_num:
2544
glitch_current = -1
2545
self.progress("Completed Glitches")
2546
self.set_parameters({
2547
"SIM_GPS1_GLTCH_X": 0,
2548
"SIM_GPS1_GLTCH_Y": 0,
2549
})
2550
else:
2551
self.progress("Applying glitch %u" % glitch_current)
2552
# move onto the next glitch
2553
self.set_parameters({
2554
"SIM_GPS1_GLTCH_X": glitch_lat[glitch_current],
2555
"SIM_GPS1_GLTCH_Y": glitch_lon[glitch_current],
2556
})
2557
2558
# start displaying distance moved after all glitches applied
2559
if glitch_current == -1:
2560
m = self.assert_receive_message(type='GLOBAL_POSITION_INT')
2561
alt = m.alt/1000.0 # mm -> m
2562
curr_pos = self.sim_location()
2563
moved_distance = self.get_distance(curr_pos, start_pos)
2564
self.progress("Alt: %.02f Moved: %.0f" %
2565
(alt, moved_distance))
2566
if moved_distance > max_distance:
2567
raise NotAchievedException(
2568
"Moved over %u meters, Failed!" % max_distance)
2569
else:
2570
self.drain_mav()
2571
2572
# disable gps glitch
2573
if glitch_current != -1:
2574
self.set_parameters({
2575
"SIM_GPS1_GLTCH_X": 0,
2576
"SIM_GPS1_GLTCH_Y": 0,
2577
})
2578
if self.use_map:
2579
self.show_gps_and_sim_positions(False)
2580
2581
self.progress("GPS glitch test passed!"
2582
" stayed within %u meters for %u seconds" %
2583
(max_distance, timeout))
2584
self.do_RTL()
2585
# re-arming is problematic because the GPS is glitching!
2586
self.reboot_sitl()
2587
2588
def GPSGlitchLoiter2(self):
2589
"""test vehicle handles GPS glitch (aka EKF Reset) without twitching"""
2590
self.context_push()
2591
self.takeoff(10, mode="LOITER")
2592
2593
# wait for vehicle to level
2594
self.wait_attitude(desroll=0, despitch=0, timeout=10, tolerance=1)
2595
2596
# apply glitch
2597
self.set_parameter("SIM_GPS1_GLTCH_X", 0.001)
2598
2599
# check lean angles remain stable for 20 seconds
2600
tstart = self.get_sim_time()
2601
while self.get_sim_time_cached() - tstart < 20:
2602
m = self.assert_receive_message('ATTITUDE')
2603
roll_deg = math.degrees(m.roll)
2604
pitch_deg = math.degrees(m.pitch)
2605
self.progress("checking att: roll=%f pitch=%f " % (roll_deg, pitch_deg))
2606
if abs(roll_deg) > 2 or abs(pitch_deg) > 2:
2607
raise NotAchievedException("fly_gps_glitch_loiter_test2 failed, roll or pitch moved during GPS glitch")
2608
2609
# RTL, remove glitch and reboot sitl
2610
self.do_RTL()
2611
self.context_pop()
2612
self.reboot_sitl()
2613
2614
def GPSGlitchAuto(self, timeout=180):
2615
'''fly mission and test reaction to gps glitch'''
2616
# set-up gps glitch array
2617
glitch_lat = [0.0002996,
2618
0.0006958,
2619
0.0009431,
2620
0.0009991,
2621
0.0009444,
2622
0.0007716,
2623
0.0006221]
2624
glitch_lon = [0.0000717,
2625
0.0000912,
2626
0.0002761,
2627
0.0002626,
2628
0.0002807,
2629
0.0002049,
2630
0.0001304]
2631
glitch_num = len(glitch_lat)
2632
self.progress("GPS Glitches:")
2633
for i in range(1, glitch_num):
2634
self.progress("glitch %d %.7f %.7f" %
2635
(i, glitch_lat[i], glitch_lon[i]))
2636
2637
# Fly mission #1
2638
self.progress("# Load copter_glitch_mission")
2639
# load the waypoint count
2640
num_wp = self.load_mission("copter_glitch_mission.txt", strict=False)
2641
if not num_wp:
2642
raise NotAchievedException("load copter_glitch_mission failed")
2643
2644
# turn on simulator display of gps and actual position
2645
if self.use_map:
2646
self.show_gps_and_sim_positions(True)
2647
2648
self.progress("test: Fly a mission from 1 to %u" % num_wp)
2649
self.set_current_waypoint(1)
2650
2651
self.change_mode("STABILIZE")
2652
self.wait_ready_to_arm()
2653
self.zero_throttle()
2654
self.arm_vehicle()
2655
2656
# switch into AUTO mode and raise throttle
2657
self.change_mode('AUTO')
2658
self.set_rc(3, 1500)
2659
2660
# wait until 100m from home
2661
try:
2662
self.wait_distance(100, 5, 90)
2663
except Exception as e:
2664
if self.use_map:
2665
self.show_gps_and_sim_positions(False)
2666
raise e
2667
2668
# stop and test loss of GPS for a short time - it should resume GPS use without falling back into a non aiding mode
2669
self.change_mode("LOITER")
2670
self.set_parameters({
2671
"SIM_GPS1_ENABLE": 0,
2672
})
2673
self.delay_sim_time(2)
2674
self.set_parameters({
2675
"SIM_GPS1_ENABLE": 1,
2676
})
2677
# regaining GPS should not result in it falling back to a non-navigation mode
2678
self.wait_ekf_flags(mavutil.mavlink.ESTIMATOR_POS_HORIZ_ABS, 0, timeout=1)
2679
# It should still be navigating after enougnh time has passed for any pending timeouts to activate.
2680
self.delay_sim_time(10)
2681
self.wait_ekf_flags(mavutil.mavlink.ESTIMATOR_POS_HORIZ_ABS, 0, timeout=1)
2682
self.change_mode("AUTO")
2683
2684
# record time and position
2685
tstart = self.get_sim_time()
2686
2687
# initialise current glitch
2688
glitch_current = 0
2689
self.progress("Apply first glitch")
2690
self.set_parameters({
2691
"SIM_GPS1_GLTCH_X": glitch_lat[glitch_current],
2692
"SIM_GPS1_GLTCH_Y": glitch_lon[glitch_current],
2693
})
2694
2695
# record position for 30 seconds
2696
while glitch_current < glitch_num:
2697
tnow = self.get_sim_time()
2698
desired_glitch_num = int((tnow - tstart) * 2.2)
2699
if desired_glitch_num > glitch_current and glitch_current != -1:
2700
glitch_current = desired_glitch_num
2701
# apply next glitch
2702
if glitch_current < glitch_num:
2703
self.progress("Applying glitch %u" % glitch_current)
2704
self.set_parameters({
2705
"SIM_GPS1_GLTCH_X": glitch_lat[glitch_current],
2706
"SIM_GPS1_GLTCH_Y": glitch_lon[glitch_current],
2707
})
2708
2709
# turn off glitching
2710
self.progress("Completed Glitches")
2711
self.set_parameters({
2712
"SIM_GPS1_GLTCH_X": 0,
2713
"SIM_GPS1_GLTCH_Y": 0,
2714
})
2715
2716
# continue with the mission
2717
self.wait_waypoint(0, num_wp-1, timeout=500)
2718
2719
# wait for arrival back home
2720
self.wait_distance_to_home(0, 10, timeout=timeout)
2721
2722
# turn off simulator display of gps and actual position
2723
if self.use_map:
2724
self.show_gps_and_sim_positions(False)
2725
2726
self.progress("GPS Glitch test Auto completed: passed!")
2727
self.wait_disarmed()
2728
# re-arming is problematic because the GPS is glitching!
2729
self.reboot_sitl()
2730
2731
# fly_simple - assumes the simple bearing is initialised to be
2732
# directly north flies a box with 100m west, 15 seconds north,
2733
# 50 seconds east, 15 seconds south
2734
def SimpleMode(self, side=50):
2735
'''Fly in SIMPLE mode'''
2736
self.takeoff(10, mode="LOITER")
2737
2738
# set SIMPLE mode for all flight modes
2739
self.set_parameter("SIMPLE", 63)
2740
2741
# switch to stabilize mode
2742
self.change_mode('STABILIZE')
2743
self.set_rc(3, 1545)
2744
2745
# fly south 50m
2746
self.progress("# Flying south %u meters" % side)
2747
self.set_rc(1, 1300)
2748
self.wait_distance(side, 5, 60)
2749
self.set_rc(1, 1500)
2750
2751
# fly west 8 seconds
2752
self.progress("# Flying west for 8 seconds")
2753
self.set_rc(2, 1300)
2754
tstart = self.get_sim_time()
2755
while self.get_sim_time_cached() < (tstart + 8):
2756
self.assert_receive_message('VFR_HUD')
2757
self.set_rc(2, 1500)
2758
2759
# fly north 25 meters
2760
self.progress("# Flying north %u meters" % (side/2.0))
2761
self.set_rc(1, 1700)
2762
self.wait_distance(side/2, 5, 60)
2763
self.set_rc(1, 1500)
2764
2765
# fly east 8 seconds
2766
self.progress("# Flying east for 8 seconds")
2767
self.set_rc(2, 1700)
2768
tstart = self.get_sim_time()
2769
while self.get_sim_time_cached() < (tstart + 8):
2770
self.assert_receive_message('VFR_HUD')
2771
self.set_rc(2, 1500)
2772
2773
# hover in place
2774
self.hover()
2775
2776
self.do_RTL(timeout=500)
2777
2778
# fly_super_simple - flies a circle around home for 45 seconds
2779
def SuperSimpleCircle(self, timeout=45):
2780
'''Fly a circle in SUPER SIMPLE mode'''
2781
self.takeoff(10, mode="LOITER")
2782
2783
# fly forward 20m
2784
self.progress("# Flying forward 20 meters")
2785
self.set_rc(2, 1300)
2786
self.wait_distance(20, 5, 60)
2787
self.set_rc(2, 1500)
2788
2789
# set SUPER SIMPLE mode for all flight modes
2790
self.set_parameter("SUPER_SIMPLE", 63)
2791
2792
# switch to stabilize mode
2793
self.change_mode("ALT_HOLD")
2794
self.set_rc(3, 1500)
2795
2796
# start copter yawing slowly
2797
self.set_rc(4, 1550)
2798
2799
# roll left for timeout seconds
2800
self.progress("# rolling left from pilot's POV for %u seconds"
2801
% timeout)
2802
self.set_rc(1, 1300)
2803
tstart = self.get_sim_time()
2804
while self.get_sim_time_cached() < (tstart + timeout):
2805
self.assert_receive_message('VFR_HUD')
2806
2807
# stop rolling and yawing
2808
self.set_rc(1, 1500)
2809
self.set_rc(4, 1500)
2810
2811
# restore simple mode parameters to default
2812
self.set_parameter("SUPER_SIMPLE", 0)
2813
2814
# hover in place
2815
self.hover()
2816
2817
self.do_RTL()
2818
2819
# fly_circle - flies a circle with 20m radius
2820
def ModeCircle(self, holdtime=36):
2821
'''Fly CIRCLE mode'''
2822
# the following should not be required. But there appears to
2823
# be a physics failure in the simulation which is causing CI
2824
# to fall over a lot. -pb 202007021209
2825
self.reboot_sitl()
2826
2827
self.takeoff(10, mode="LOITER")
2828
2829
# face west
2830
self.progress("turn west")
2831
self.set_rc(4, 1580)
2832
self.wait_heading(270)
2833
self.set_rc(4, 1500)
2834
2835
# set CIRCLE radius
2836
self.set_parameter("CIRCLE_RADIUS_M", 30)
2837
2838
# fly forward (east) at least 100m
2839
self.set_rc(2, 1100)
2840
self.wait_distance(100)
2841
# return pitch stick back to middle
2842
self.set_rc(2, 1500)
2843
2844
# set CIRCLE mode
2845
self.change_mode('CIRCLE')
2846
2847
# wait
2848
m = self.assert_receive_message('VFR_HUD')
2849
start_altitude = m.alt
2850
tstart = self.get_sim_time()
2851
self.progress("Circle at %u meters for %u seconds" %
2852
(start_altitude, holdtime))
2853
while self.get_sim_time_cached() < tstart + holdtime:
2854
m = self.assert_receive_message('VFR_HUD')
2855
self.progress("heading %d" % m.heading)
2856
2857
self.progress("CIRCLE OK for %u seconds" % holdtime)
2858
2859
self.do_RTL()
2860
2861
def CompassMot(self):
2862
'''test code that adjust mag field for motor interference'''
2863
self.run_cmd(
2864
mavutil.mavlink.MAV_CMD_PREFLIGHT_CALIBRATION,
2865
0, # p1
2866
0, # p2
2867
0, # p3
2868
0, # p4
2869
0, # p5
2870
1, # p6
2871
0 # p7
2872
)
2873
self.context_collect("STATUSTEXT")
2874
self.wait_statustext("Starting calibration", check_context=True)
2875
self.wait_statustext("Current", check_context=True)
2876
rc3_min = self.get_parameter('RC3_MIN')
2877
rc3_max = self.get_parameter('RC3_MAX')
2878
rc3_dz = self.get_parameter('RC3_DZ')
2879
2880
def set_rc3_for_throttle_pct(thr_pct):
2881
value = int((rc3_min+rc3_dz) + (thr_pct/100.0) * (rc3_max-(rc3_min+rc3_dz)))
2882
self.progress("Setting rc3 to %u" % value)
2883
self.set_rc(3, value)
2884
2885
throttle_in_pct = 0
2886
set_rc3_for_throttle_pct(throttle_in_pct)
2887
self.assert_received_message_field_values("COMPASSMOT_STATUS", {
2888
"interference": 0,
2889
"throttle": throttle_in_pct
2890
}, verbose=True, very_verbose=True)
2891
tstart = self.get_sim_time()
2892
delta = 5
2893
while True:
2894
if self.get_sim_time_cached() - tstart > 60:
2895
raise NotAchievedException("did not run through entire range")
2896
throttle_in_pct += delta
2897
self.progress("Using throttle %f%%" % throttle_in_pct)
2898
set_rc3_for_throttle_pct(throttle_in_pct)
2899
self.wait_message_field_values("COMPASSMOT_STATUS", {
2900
"throttle": throttle_in_pct * 10.0,
2901
}, verbose=True, very_verbose=True, epsilon=1)
2902
if throttle_in_pct == 0:
2903
# finished counting down
2904
break
2905
if throttle_in_pct == 100:
2906
# start counting down
2907
delta = -delta
2908
2909
m = self.wait_message_field_values("COMPASSMOT_STATUS", {
2910
"throttle": 0,
2911
}, verbose=True)
2912
for axis in "X", "Y", "Z":
2913
fieldname = "Compensation" + axis
2914
if getattr(m, fieldname) <= 0:
2915
raise NotAchievedException("Expected non-zero %s" % fieldname)
2916
2917
# it's kind of crap - but any command-ack will stop the
2918
# calibration
2919
self.mav.mav.command_ack_send(0, 1)
2920
self.wait_statustext("Calibration successful")
2921
2922
def MagFail(self):
2923
'''test failover of compass in EKF'''
2924
# we want both EK2 and EK3
2925
self.set_parameters({
2926
"EK2_ENABLE": 1,
2927
"EK3_ENABLE": 1,
2928
})
2929
2930
self.takeoff(10, mode="LOITER")
2931
2932
self.change_mode('CIRCLE')
2933
2934
self.delay_sim_time(20)
2935
2936
self.context_collect("STATUSTEXT")
2937
2938
self.progress("Failing first compass")
2939
self.set_parameter("SIM_MAG1_FAIL", 1)
2940
2941
# we want for the message twice, one for EK2 and again for EK3
2942
self.wait_statustext("EKF2 IMU0 switching to compass 1", check_context=True)
2943
self.wait_statustext("EKF3 IMU0 switching to compass 1", check_context=True)
2944
self.progress("compass switch 1 OK")
2945
2946
self.delay_sim_time(2)
2947
2948
self.context_clear_collection("STATUSTEXT")
2949
2950
self.progress("Failing 2nd compass")
2951
self.set_parameter("SIM_MAG2_FAIL", 1)
2952
2953
self.wait_statustext("EKF2 IMU0 switching to compass 2", check_context=True)
2954
self.wait_statustext("EKF3 IMU0 switching to compass 2", check_context=True)
2955
2956
self.progress("compass switch 2 OK")
2957
2958
self.delay_sim_time(2)
2959
2960
self.context_clear_collection("STATUSTEXT")
2961
2962
self.progress("Failing 3rd compass")
2963
self.set_parameter("SIM_MAG3_FAIL", 1)
2964
self.delay_sim_time(2)
2965
self.set_parameter("SIM_MAG1_FAIL", 0)
2966
2967
self.wait_statustext("EKF2 IMU0 switching to compass 0", check_context=True)
2968
self.wait_statustext("EKF3 IMU0 switching to compass 0", check_context=True)
2969
self.progress("compass switch 0 OK")
2970
2971
self.do_RTL()
2972
2973
def TestEKF3CompassFailover(self):
2974
'''Test if changed compasses goes back to primary in EKF3 on disarm'''
2975
# Enable only EKF3
2976
self.set_parameters({
2977
"EK3_ENABLE": 1,
2978
})
2979
2980
self.takeoff(10, mode="LOITER")
2981
2982
self.context_collect("STATUSTEXT")
2983
2984
self.progress("Disabling compass")
2985
self.set_parameter("SIM_MAG1_FAIL", 1)
2986
2987
self.wait_statustext("EKF3 IMU0 switching to compass 1", check_context=True)
2988
self.wait_statustext("EKF3 IMU1 switching to compass 1", check_context=True)
2989
self.progress("Compass switch detected")
2990
2991
self.context_clear_collection("STATUSTEXT")
2992
self.delay_sim_time(2)
2993
2994
self.progress("Re-enabling compass")
2995
self.set_parameter("SIM_MAG1_FAIL", 0)
2996
2997
self.context_clear_collection("STATUSTEXT")
2998
self.progress("Landing and disarming")
2999
self.land_and_disarm()
3000
3001
self.wait_statustext("EKF3 IMU0 switching to compass 0", check_context=True)
3002
self.wait_statustext("EKF3 IMU1 switching to compass 0", check_context=True)
3003
self.progress("Compass restored")
3004
3005
self.progress("Landing and disarming")
3006
self.land_and_disarm()
3007
3008
def ModeFlip(self):
3009
'''Fly Flip Mode'''
3010
self.context_set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_ATTITUDE, 100)
3011
3012
self.takeoff(20)
3013
3014
self.progress("Flipping in roll")
3015
self.set_rc(1, 1700)
3016
self.send_cmd_do_set_mode('FLIP') # don't wait for success
3017
self.wait_attitude(despitch=0, desroll=45, tolerance=30)
3018
self.wait_attitude(despitch=0, desroll=90, tolerance=30)
3019
self.wait_attitude(despitch=0, desroll=-45, tolerance=30)
3020
self.progress("Waiting for level")
3021
self.set_rc(1, 1500) # can't change quickly enough!
3022
self.wait_attitude(despitch=0, desroll=0, tolerance=5)
3023
3024
self.progress("Regaining altitude")
3025
self.change_mode('ALT_HOLD')
3026
self.wait_altitude(19, 60, relative=True)
3027
3028
self.progress("Flipping in pitch")
3029
self.set_rc(2, 1700)
3030
self.send_cmd_do_set_mode('FLIP') # don't wait for success
3031
self.wait_attitude(despitch=45, desroll=0, tolerance=30)
3032
# can't check roll here as it flips from 0 to -180..
3033
self.wait_attitude(despitch=90, tolerance=30)
3034
self.wait_attitude(despitch=-45, tolerance=30)
3035
self.progress("Waiting for level")
3036
self.set_rc(2, 1500) # can't change quickly enough!
3037
self.wait_attitude(despitch=0, desroll=0, tolerance=5)
3038
3039
self.do_RTL()
3040
3041
def configure_EKFs_to_use_optical_flow_instead_of_GPS(self):
3042
'''configure EKF to use optical flow instead of GPS'''
3043
ahrs_ekf_type = self.get_parameter("AHRS_EKF_TYPE")
3044
if ahrs_ekf_type == 2:
3045
self.set_parameter("EK2_GPS_TYPE", 3)
3046
if ahrs_ekf_type == 3:
3047
self.set_parameters({
3048
"EK3_SRC1_POSXY": 0,
3049
"EK3_SRC1_VELXY": 5,
3050
"EK3_SRC1_VELZ": 0,
3051
})
3052
3053
def OpticalFlowLocation(self):
3054
'''test optical flow doesn't supply location'''
3055
3056
self.context_push()
3057
3058
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_OPTICAL_FLOW, False, False, False, verbose=True)
3059
3060
self.start_subtest("Make sure no crash if no rangefinder")
3061
self.set_parameter("SIM_FLOW_ENABLE", 1)
3062
self.set_parameter("FLOW_TYPE", 10)
3063
3064
self.configure_EKFs_to_use_optical_flow_instead_of_GPS()
3065
3066
self.reboot_sitl()
3067
3068
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_OPTICAL_FLOW, True, True, True, verbose=True)
3069
3070
self.change_mode('LOITER')
3071
self.delay_sim_time(5)
3072
self.wait_statustext("Need Position Estimate", timeout=300)
3073
3074
self.context_pop()
3075
3076
self.reboot_sitl()
3077
3078
def OpticalFlow(self):
3079
'''test OpticalFlow in flight'''
3080
self.start_subtest("Make sure no crash if no rangefinder")
3081
3082
self.set_parameters({
3083
"SIM_FLOW_ENABLE": 1,
3084
"FLOW_TYPE": 10,
3085
})
3086
3087
self.set_analog_rangefinder_parameters()
3088
3089
self.reboot_sitl()
3090
3091
self.change_mode('LOITER')
3092
3093
class CheckOpticalFlow(vehicle_test_suite.TestSuite.MessageHook):
3094
'''ensures OPTICAL_FLOW data matches other data'''
3095
def __init__(self, suite):
3096
super().__init__(suite)
3097
3098
self.flow_rate_rads = 0
3099
self.rangefinder_distance = 0
3100
self.gps_speed = 0
3101
self.last_debug_time = 0
3102
self.distance_sensor_distance = 0
3103
3104
def progress_prefix(self):
3105
return "COF: "
3106
3107
def process(self, mav, m):
3108
m_type = m.get_type()
3109
if m_type == "OPTICAL_FLOW":
3110
self.flow_rate_rads = math.sqrt(m.flow_comp_m_x**2+m.flow_comp_m_y**2)
3111
elif m_type == "RANGEFINDER":
3112
self.rangefinder_distance = m.distance
3113
elif m_type == "DISTANCE_SENSOR":
3114
self.distance_sensor_distance = m.current_distance * 0.01
3115
elif m_type == "GPS_RAW_INT":
3116
self.gps_speed = m.vel/100.0 # cm/s -> m/s
3117
3118
of_speed = self.flow_rate_rads * self.rangefinder_distance
3119
if abs(of_speed - self.gps_speed) > 3:
3120
raise NotAchievedException(f"gps={self.gps_speed} vs of={of_speed} mismatch")
3121
of_speed = self.flow_rate_rads * self.distance_sensor_distance
3122
if abs(of_speed - self.gps_speed) > 3:
3123
raise NotAchievedException(f"gps={self.gps_speed} vs of={of_speed} mismatch")
3124
3125
now = self.suite.get_sim_time_cached()
3126
if now - self.last_debug_time > 5:
3127
self.last_debug_time = now
3128
self.progress(f"gps={self.gps_speed} of={of_speed}")
3129
3130
self.context_set_message_rate_hz('RANGEFINDER', self.sitl_streamrate())
3131
3132
check_optical_flow = CheckOpticalFlow(self)
3133
self.install_message_hook_context(check_optical_flow)
3134
3135
self.fly_generic_mission("CMAC-copter-navtest.txt")
3136
3137
def OpticalFlowLimits(self):
3138
'''test EKF navigation limiting'''
3139
self.set_parameters({
3140
"SIM_FLOW_ENABLE": 1,
3141
"FLOW_TYPE": 10,
3142
"SIM_GPS1_ENABLE": 0,
3143
"SIM_TERRAIN": 0,
3144
})
3145
3146
self.configure_EKFs_to_use_optical_flow_instead_of_GPS()
3147
3148
self.set_analog_rangefinder_parameters()
3149
3150
self.reboot_sitl()
3151
3152
# we can't takeoff in loiter as we need flow healthy
3153
self.takeoff(alt_min=5, mode='ALT_HOLD', require_absolute=False, takeoff_throttle=1800)
3154
self.change_mode('LOITER')
3155
3156
# speed should be limited to <10m/s
3157
self.set_rc(2, 1000)
3158
3159
tstart = self.get_sim_time()
3160
timeout = 60
3161
started_climb = False
3162
while self.get_sim_time_cached() - tstart < timeout:
3163
m = self.assert_receive_message('GLOBAL_POSITION_INT')
3164
spd = math.sqrt(m.vx**2 + m.vy**2) * 0.01
3165
alt = m.relative_alt*0.001
3166
3167
# calculate max speed from altitude above the ground
3168
margin = 2.0
3169
max_speed = alt * 1.5 + margin
3170
self.progress("%0.1f: Low Speed: %f (want <= %u) alt=%.1f" %
3171
(self.get_sim_time_cached() - tstart,
3172
spd,
3173
max_speed, alt))
3174
if spd > max_speed:
3175
raise NotAchievedException(("Speed should be limited by"
3176
"EKF optical flow limits"))
3177
3178
# after 30 seconds start climbing
3179
if not started_climb and self.get_sim_time_cached() - tstart > 30:
3180
started_climb = True
3181
self.set_rc(3, 1900)
3182
self.progress("Moving higher")
3183
3184
# check altitude is not climbing above 35m
3185
if alt > 35:
3186
raise NotAchievedException("Alt should be limited by EKF optical flow limits")
3187
self.reboot_sitl(force=True)
3188
3189
def OpticalFlowCalibration(self):
3190
'''test optical flow calibration'''
3191
ex = None
3192
self.context_push()
3193
try:
3194
3195
self.set_parameter("SIM_FLOW_ENABLE", 1)
3196
self.set_parameter("FLOW_TYPE", 10)
3197
self.set_analog_rangefinder_parameters()
3198
3199
# RC9 starts/stops calibration
3200
self.set_parameter("RC9_OPTION", 158)
3201
3202
# initialise flow scaling parameters to incorrect values
3203
self.set_parameter("FLOW_FXSCALER", -200)
3204
self.set_parameter("FLOW_FYSCALER", 200)
3205
3206
self.reboot_sitl()
3207
3208
# ensure calibration is off
3209
self.set_rc(9, 1000)
3210
3211
# takeoff to 10m in loiter
3212
self.takeoff(10, mode="LOITER", require_absolute=True, timeout=720)
3213
3214
# start calibration
3215
self.set_rc(9, 2000)
3216
3217
tstart = self.get_sim_time()
3218
timeout = 90
3219
veh_dir_tstart = self.get_sim_time()
3220
veh_dir = 0
3221
while self.get_sim_time_cached() - tstart < timeout:
3222
# roll and pitch vehicle until samples collected
3223
# change direction of movement every 2 seconds
3224
if self.get_sim_time_cached() - veh_dir_tstart > 2:
3225
veh_dir_tstart = self.get_sim_time()
3226
veh_dir = veh_dir + 1
3227
if veh_dir > 3:
3228
veh_dir = 0
3229
if veh_dir == 0:
3230
# move right
3231
self.set_rc(1, 1800)
3232
self.set_rc(2, 1500)
3233
if veh_dir == 1:
3234
# move left
3235
self.set_rc(1, 1200)
3236
self.set_rc(2, 1500)
3237
if veh_dir == 2:
3238
# move forward
3239
self.set_rc(1, 1500)
3240
self.set_rc(2, 1200)
3241
if veh_dir == 3:
3242
# move back
3243
self.set_rc(1, 1500)
3244
self.set_rc(2, 1800)
3245
3246
# return sticks to center
3247
self.set_rc(1, 1500)
3248
self.set_rc(2, 1500)
3249
3250
# stop calibration (not actually necessary)
3251
self.set_rc(9, 1000)
3252
3253
# check scaling parameters have been restored to values near zero
3254
flow_scalar_x = self.get_parameter("FLOW_FXSCALER")
3255
flow_scalar_y = self.get_parameter("FLOW_FYSCALER")
3256
if ((flow_scalar_x > 30) or (flow_scalar_x < -30)):
3257
raise NotAchievedException("FlowCal failed to set FLOW_FXSCALER correctly")
3258
if ((flow_scalar_y > 30) or (flow_scalar_y < -30)):
3259
raise NotAchievedException("FlowCal failed to set FLOW_FYSCALER correctly")
3260
3261
except Exception as e:
3262
self.print_exception_caught(e)
3263
ex = e
3264
3265
self.disarm_vehicle(force=True)
3266
self.context_pop()
3267
self.reboot_sitl()
3268
3269
if ex is not None:
3270
raise ex
3271
3272
def AutoTune(self):
3273
"""Test autotune mode"""
3274
3275
# autotune changes a set of parameters on the vehicle which
3276
# are not in our context. That changes the flight
3277
# characteristics, which we can't afford between runs. So
3278
# completely reset the simulated vehicle after the run is
3279
# complete by "customising" the commandline here:
3280
self.customise_SITL_commandline([])
3281
3282
self.set_parameters({
3283
"AUTOTUNE_AGGR": 0.1,
3284
"AUTOTUNE_MIN_D": 0.0004,
3285
})
3286
3287
gain_names = [
3288
"ATC_RAT_RLL_D",
3289
"ATC_RAT_RLL_I",
3290
"ATC_RAT_RLL_P",
3291
]
3292
3293
ogains = self.get_parameters(gain_names)
3294
# set these parameters so they get reverted at the end of the test:
3295
self.set_parameters(ogains)
3296
3297
self.takeoff(10)
3298
3299
tstart = self.get_sim_time()
3300
3301
self.change_mode('AUTOTUNE')
3302
3303
self.wait_statustext("AutoTune: Success", timeout=5000)
3304
now = self.get_sim_time()
3305
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
3306
3307
# near enough for now:
3308
self.change_mode('LAND')
3309
self.wait_landed_and_disarmed()
3310
3311
self.progress("checking the original gains have been re-instated")
3312
ngains = self.get_parameters(gain_names)
3313
for g in ngains.keys():
3314
if ogains[g] != ngains[g]:
3315
raise NotAchievedException(f"AUTOTUNE gains not discarded {ogains=} {ngains=}")
3316
3317
def AutoTuneYawD(self):
3318
"""Test autotune mode"""
3319
3320
# autotune changes a set of parameters on the vehicle which
3321
# are not in our context. That changes the flight
3322
# characteristics, which we can't afford between runs. So
3323
# completely reset the simulated vehicle after the run is
3324
# complete by "customising" the commandline here:
3325
self.customise_SITL_commandline([])
3326
3327
self.set_parameters({
3328
"AUTOTUNE_AGGR": 0.1,
3329
"AUTOTUNE_AXES": 12,
3330
})
3331
3332
gain_names = [
3333
"ATC_RAT_RLL_D",
3334
"ATC_RAT_RLL_I",
3335
"ATC_RAT_RLL_P",
3336
"ATC_RAT_YAW_D",
3337
]
3338
ogains = self.get_parameters(gain_names)
3339
# set these parameters so they get reverted at the end of the test:
3340
self.set_parameters(ogains)
3341
3342
self.takeoff(10)
3343
3344
tstart = self.get_sim_time()
3345
3346
# hold position in loiter
3347
self.change_mode('AUTOTUNE')
3348
3349
# fail-fast if any determination fails:
3350
self.install_message_hook_context(vehicle_test_suite.TestSuite.FailFastStatusText(
3351
self, "determination failed"
3352
))
3353
self.wait_statustext("AutoTune: Success", timeout=5000)
3354
now = self.get_sim_time()
3355
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
3356
3357
# near enough for now:
3358
self.change_mode('LAND')
3359
self.wait_landed_and_disarmed()
3360
3361
self.progress("checking the original gains have been re-instated")
3362
ngains = self.get_parameters(gain_names)
3363
for g in ngains.keys():
3364
if ogains[g] != ngains[g]:
3365
raise NotAchievedException(f"AUTOTUNE gains not discarded {ogains=} {ngains=}")
3366
3367
def EKF3SRCPerCore(self):
3368
'''Check SP/SH values in XKF4 for GPS (core 0) vs VICON (core 1) with targeted glitches.'''
3369
self.set_parameters({
3370
"EK3_ENABLE": 1,
3371
"AHRS_EKF_TYPE": 3,
3372
"VISO_TYPE": 2,
3373
"SERIAL5_PROTOCOL": 2,
3374
"EK3_SRC2_POSXY": 6,
3375
"EK3_SRC2_VELXY": 6,
3376
"EK3_SRC2_POSZ": 6,
3377
"EK3_SRC2_VELZ": 6,
3378
"EK3_SRC2_YAW": 6,
3379
"EK3_SRC_OPTIONS": 8 # bitmask: enable multiple EKF cores
3380
})
3381
3382
self.customise_SITL_commandline(["--serial5=sim:vicon"])
3383
self.reboot_sitl()
3384
3385
self.wait_ready_to_arm()
3386
self.takeoff(3)
3387
self.delay_sim_time(5)
3388
3389
self.progress("Injecting VICON glitch")
3390
self.set_parameter("SIM_VICON_GLIT_X", 100)
3391
self.set_parameter("SIM_VICON_GLIT_Y", 100)
3392
self.delay_sim_time(5)
3393
self.set_parameter("SIM_VICON_GLIT_X", 0)
3394
self.set_parameter("SIM_VICON_GLIT_Y", 0)
3395
3396
self.progress("Injecting BARO glitch")
3397
self.set_parameter("SIM_BARO_GLITCH", 10)
3398
self.delay_sim_time(5)
3399
self.set_parameter("SIM_BARO_GLITCH", 0)
3400
3401
self.do_RTL()
3402
self.wait_disarmed(timeout=100)
3403
3404
dfreader = self.dfreader_for_current_onboard_log()
3405
3406
max_SP_core0 = 0
3407
max_SP_core1 = 0
3408
max_SH_core0 = 0
3409
max_SH_core1 = 0
3410
3411
while True:
3412
m = dfreader.recv_match(type="XKF4")
3413
if m is None:
3414
break
3415
if not hasattr(m, "C"):
3416
continue
3417
if m.C == 0:
3418
max_SP_core0 = max(max_SP_core0, m.SP)
3419
max_SH_core0 = max(max_SH_core0, m.SH)
3420
elif m.C == 1:
3421
max_SP_core1 = max(max_SP_core1, m.SP)
3422
max_SH_core1 = max(max_SH_core1, m.SH)
3423
3424
self.progress(f"Core 0 (GPS): SP={max_SP_core0:.2f}, SH={max_SH_core0:.2f}")
3425
self.progress(f"Core 1 (VICON): SP={max_SP_core1:.2f}, SH={max_SH_core1:.2f}")
3426
3427
# Validate: horizontal glitch only on core 1, vertical glitch only on core 0
3428
if max_SP_core0 > 0.1:
3429
raise NotAchievedException("Unexpected high SP in core 0 (GPS) during VICON glitch")
3430
if max_SP_core1 < 0.9:
3431
raise NotAchievedException("VICON glitch did not raise SP in core 1")
3432
3433
if max_SH_core1 > 0.5:
3434
raise NotAchievedException("Unexpected high SH in core 1 (VICON) during BARO glitch")
3435
if max_SH_core0 < 0.9:
3436
raise NotAchievedException("BARO glitch did not raise SH in core 0")
3437
3438
def AutoTuneSwitch(self):
3439
"""Test autotune on a switch with gains being saved"""
3440
3441
# autotune changes a set of parameters on the vehicle which
3442
# are not in our context. That changes the flight
3443
# characteristics, which we can't afford between runs. So
3444
# completely reset the simulated vehicle after the run is
3445
# complete by "customising" the commandline here:
3446
self.customise_SITL_commandline([])
3447
3448
self.set_parameters({
3449
"RC8_OPTION": 17,
3450
"AUTOTUNE_AGGR": 0.1,
3451
"AUTOTUNE_MIN_D": 0.0004,
3452
})
3453
3454
self.takeoff(10, mode='LOITER')
3455
3456
def print_gains(name, gains):
3457
self.progress(f"AUTOTUNE {name} gains are P:%f I:%f D:%f" % (
3458
gains["ATC_RAT_RLL_P"],
3459
gains["ATC_RAT_RLL_I"],
3460
gains["ATC_RAT_RLL_D"]
3461
))
3462
3463
def get_roll_gains(name):
3464
ret = self.get_parameters([
3465
"ATC_RAT_RLL_D",
3466
"ATC_RAT_RLL_I",
3467
"ATC_RAT_RLL_P",
3468
], verbose=False)
3469
print_gains(name, ret)
3470
return ret
3471
3472
def gains_same(gains1, gains2):
3473
for c in 'P', 'I', 'D':
3474
p_name = f"ATC_RAT_RLL_{c}"
3475
if abs(gains1[p_name] - gains2[p_name]) > 0.00001:
3476
return False
3477
return True
3478
3479
self.progress("Take a copy of original gains")
3480
original_gains = get_roll_gains("pre-tuning")
3481
scaled_original_gains = copy.copy(original_gains)
3482
scaled_original_gains["ATC_RAT_RLL_I"] *= 0.1
3483
3484
pre_rllt = self.get_parameter("ATC_RAT_RLL_FLTT")
3485
3486
# hold position in loiter and run autotune
3487
self.set_rc(8, 1850)
3488
self.wait_mode('AUTOTUNE')
3489
3490
tstart = self.get_sim_time()
3491
sim_time_expected = 5000
3492
deadline = tstart + sim_time_expected
3493
while self.get_sim_time_cached() < deadline:
3494
now = self.get_sim_time_cached()
3495
m = self.mav.recv_match(type='STATUSTEXT',
3496
blocking=True,
3497
timeout=1)
3498
if m is None:
3499
continue
3500
self.progress("STATUSTEXT (%u<%u): %s" % (now, deadline, m.text))
3501
if "Determination Failed" in m.text:
3502
break
3503
if "AutoTune: Success" in m.text:
3504
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
3505
post_gains = get_roll_gains("post")
3506
self.progress("Check original gains are used after tuning finished")
3507
if not gains_same(original_gains, post_gains):
3508
raise NotAchievedException("AUTOTUNE original gains not restored")
3509
3510
self.progress("Check original gains are re-instated by switch")
3511
self.set_rc(8, 1100)
3512
self.delay_sim_time(1)
3513
current_gains = get_roll_gains("set-original")
3514
if not gains_same(original_gains, current_gains):
3515
raise NotAchievedException("AUTOTUNE original gains not restored")
3516
3517
self.progress("Use autotuned gains")
3518
self.set_rc(8, 1850)
3519
self.delay_sim_time(1)
3520
tuned_gains = get_roll_gains("tuned")
3521
if gains_same(tuned_gains, original_gains):
3522
raise NotAchievedException("AUTOTUNE tuned gains same as pre gains")
3523
if gains_same(tuned_gains, scaled_original_gains):
3524
raise NotAchievedException("AUTOTUNE tuned gains same as scaled pre gains")
3525
3526
self.progress("land without changing mode")
3527
self.set_rc(3, 1000)
3528
self.wait_altitude(-1, 5, relative=True)
3529
self.wait_disarmed()
3530
self.progress("Check gains are still there after disarm")
3531
disarmed_gains = get_roll_gains("post-disarm")
3532
if not gains_same(tuned_gains, disarmed_gains):
3533
raise NotAchievedException("AUTOTUNE gains not present on disarm")
3534
3535
self.reboot_sitl()
3536
self.progress("Check gains are still there after reboot")
3537
reboot_gains = get_roll_gains("post-reboot")
3538
if not gains_same(tuned_gains, reboot_gains):
3539
raise NotAchievedException("AUTOTUNE gains not present on reboot")
3540
self.progress("Check FLTT is unchanged")
3541
if pre_rllt != self.get_parameter("ATC_RAT_RLL_FLTT"):
3542
raise NotAchievedException("AUTOTUNE FLTT was modified")
3543
return
3544
3545
raise NotAchievedException("AUTOTUNE failed (%u seconds)" %
3546
(self.get_sim_time() - tstart))
3547
3548
def AutoTuneAux(self):
3549
"""Test autotune with gains being tested using the Aux function"""
3550
3551
# autotune changes a set of parameters on the vehicle which
3552
# are not in our context. That changes the flight
3553
# characteristics, which we can't afford between runs. So
3554
# completely reset the simulated vehicle after the run is
3555
# complete by "customising" the commandline here:
3556
self.customise_SITL_commandline([])
3557
3558
self.set_parameters({
3559
"RC8_OPTION": 180,
3560
"AUTOTUNE_AGGR": 0.1,
3561
"AUTOTUNE_MIN_D": 0.0004,
3562
})
3563
3564
self.takeoff(10, mode='LOITER')
3565
3566
def print_gains(name, gains):
3567
self.progress(f"AUTOTUNE {name} gains are P:%f I:%f D:%f" % (
3568
gains["ATC_RAT_RLL_P"],
3569
gains["ATC_RAT_RLL_I"],
3570
gains["ATC_RAT_RLL_D"]
3571
))
3572
3573
def get_roll_gains(name):
3574
ret = self.get_parameters([
3575
"ATC_RAT_RLL_D",
3576
"ATC_RAT_RLL_I",
3577
"ATC_RAT_RLL_P",
3578
], verbose=False)
3579
print_gains(name, ret)
3580
return ret
3581
3582
def gains_same(gains1, gains2):
3583
for c in 'P', 'I', 'D':
3584
p_name = f"ATC_RAT_RLL_{c}"
3585
if abs(gains1[p_name] - gains2[p_name]) > 0.00001:
3586
return False
3587
return True
3588
3589
self.progress("Take a copy of original gains")
3590
original_gains = get_roll_gains("pre-tuning")
3591
scaled_original_gains = copy.copy(original_gains)
3592
scaled_original_gains["ATC_RAT_RLL_I"] *= 0.1
3593
3594
pre_rllt = self.get_parameter("ATC_RAT_RLL_FLTT")
3595
3596
# hold position in loiter and run autotune
3597
self.change_mode('AUTOTUNE')
3598
3599
tstart = self.get_sim_time()
3600
sim_time_expected = 5000
3601
deadline = tstart + sim_time_expected
3602
while self.get_sim_time_cached() < deadline:
3603
now = self.get_sim_time_cached()
3604
m = self.mav.recv_match(type='STATUSTEXT',
3605
blocking=True,
3606
timeout=1)
3607
if m is None:
3608
continue
3609
self.progress("STATUSTEXT (%u<%u): %s" % (now, deadline, m.text))
3610
if "Determination Failed" in m.text:
3611
break
3612
if "AutoTune: Success" in m.text:
3613
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
3614
post_gains = get_roll_gains("post")
3615
self.progress("Check original gains are used after tuning finished")
3616
if not gains_same(original_gains, post_gains):
3617
raise NotAchievedException("AUTOTUNE original gains not restored")
3618
3619
self.change_mode('LOITER')
3620
self.wait_mode('LOITER')
3621
3622
self.progress("Use autotuned gains")
3623
self.set_rc(8, 1850)
3624
self.delay_sim_time(1)
3625
tuned_gains = get_roll_gains("tuned")
3626
if gains_same(tuned_gains, original_gains):
3627
raise NotAchievedException("AUTOTUNE tuned gains same as pre gains")
3628
if gains_same(tuned_gains, scaled_original_gains):
3629
raise NotAchievedException("AUTOTUNE tuned gains same as scaled pre gains")
3630
3631
self.progress("Check original gains are re-instated by switch")
3632
self.set_rc(8, 1100)
3633
self.delay_sim_time(1)
3634
current_gains = get_roll_gains("set-original")
3635
if not gains_same(original_gains, current_gains):
3636
raise NotAchievedException("AUTOTUNE original gains not restored")
3637
3638
self.progress("land using Autotune Gains")
3639
self.set_rc(8, 1850)
3640
self.delay_sim_time(1)
3641
self.set_rc(3, 1000)
3642
self.wait_altitude(-1, 5, relative=True)
3643
self.wait_disarmed()
3644
self.progress("Check gains are still there after disarm")
3645
disarmed_gains = get_roll_gains("post-disarm")
3646
if not gains_same(tuned_gains, disarmed_gains):
3647
raise NotAchievedException("AUTOTUNE gains not present on disarm")
3648
3649
self.reboot_sitl()
3650
self.progress("Check gains are still there after reboot")
3651
reboot_gains = get_roll_gains("post-reboot")
3652
if not gains_same(tuned_gains, reboot_gains):
3653
raise NotAchievedException("AUTOTUNE gains not present on reboot")
3654
self.progress("Check FLTT is unchanged")
3655
if pre_rllt != self.get_parameter("ATC_RAT_RLL_FLTT"):
3656
raise NotAchievedException("AUTOTUNE FLTT was modified")
3657
return
3658
3659
raise NotAchievedException("AUTOTUNE failed (%u seconds)" %
3660
(self.get_sim_time() - tstart))
3661
3662
def EK3_RNG_USE_HGT(self):
3663
'''basic tests for using rangefinder when speed and height below thresholds'''
3664
# this takes advantage of some code in send_status_report
3665
# which only reports terrain variance when using switch-height
3666
# and using the rangefinder
3667
self.context_push()
3668
3669
self.set_analog_rangefinder_parameters()
3670
# set use-height to 20m (the parameter is a percentage of max range)
3671
self.set_parameters({
3672
'EK3_RNG_USE_HGT': (20 / self.get_parameter('RNGFND1_MAX')) * 100,
3673
})
3674
self.reboot_sitl()
3675
3676
# add a listener that verifies rangefinder innovations look good
3677
global alt
3678
alt = None
3679
3680
def verify_innov(mav, m):
3681
global alt
3682
if m.get_type() == 'GLOBAL_POSITION_INT':
3683
alt = m.relative_alt * 0.001 # mm -> m
3684
return
3685
if m.get_type() != 'EKF_STATUS_REPORT':
3686
return
3687
if alt is None:
3688
return
3689
if alt > 1 and alt < 8: # 8 is very low, but it takes a long time to start to use the rangefinder again
3690
zero_variance_wanted = False
3691
elif alt > 20:
3692
zero_variance_wanted = True
3693
else:
3694
return
3695
variance = m.terrain_alt_variance
3696
if zero_variance_wanted and variance > 0.00001:
3697
raise NotAchievedException("Wanted zero variance at height %f, got %f" % (alt, variance))
3698
elif not zero_variance_wanted and variance == 0:
3699
raise NotAchievedException("Wanted non-zero variance at alt=%f, got zero" % alt)
3700
3701
self.install_message_hook_context(verify_innov)
3702
3703
self.takeoff(50, mode='GUIDED')
3704
current_alt = self.mav.location().alt
3705
target_position = mavutil.location(
3706
-35.362938,
3707
149.165185,
3708
current_alt,
3709
0
3710
)
3711
3712
self.fly_guided_move_to(target_position, timeout=300)
3713
3714
self.change_mode('LAND')
3715
self.wait_disarmed()
3716
3717
self.context_pop()
3718
3719
self.reboot_sitl()
3720
3721
def TerrainDBPreArm(self):
3722
'''test that pre-arm checks are working correctly for terrain database'''
3723
self.context_push()
3724
3725
self.progress("# Load msission with terrain alt")
3726
# load the waypoint
3727
num_wp = self.load_mission("terrain_wp.txt", strict=False)
3728
if not num_wp:
3729
raise NotAchievedException("load terrain_wp failed")
3730
3731
self.set_analog_rangefinder_parameters()
3732
self.set_parameters({
3733
"WPNAV_RFND_USE": 1,
3734
"TERRAIN_ENABLE": 1,
3735
})
3736
self.reboot_sitl()
3737
self.wait_ready_to_arm()
3738
3739
# make sure we can still arm with valid rangefinder and terrain db disabled
3740
self.set_parameter("TERRAIN_ENABLE", 0)
3741
self.wait_ready_to_arm()
3742
self.progress("# Vehicle armed with terrain db disabled")
3743
3744
# make sure we can't arm with terrain db enabled and no rangefinder in us
3745
self.set_parameter("WPNAV_RFND_USE", 0)
3746
self.assert_prearm_failure("terrain disabled")
3747
3748
self.context_pop()
3749
3750
self.reboot_sitl()
3751
3752
def CopterMission(self):
3753
'''fly mission which tests a significant number of commands'''
3754
# Fly mission #1
3755
self.progress("# Load copter_mission")
3756
# load the waypoint count
3757
num_wp = self.load_mission("copter_mission.txt", strict=False)
3758
if not num_wp:
3759
raise NotAchievedException("load copter_mission failed")
3760
3761
self.fly_loaded_mission(num_wp)
3762
3763
self.progress("Auto mission completed: passed!")
3764
3765
def set_origin(self, loc, timeout=60):
3766
'''set the GPS global origin to loc'''
3767
tstart = self.get_sim_time()
3768
while True:
3769
if self.get_sim_time_cached() - tstart > timeout:
3770
raise AutoTestTimeoutException("Did not get non-zero lat")
3771
target_system = 1
3772
self.mav.mav.set_gps_global_origin_send(
3773
target_system,
3774
int(loc.lat * 1e7),
3775
int(loc.lng * 1e7),
3776
int(loc.alt * 1e3)
3777
)
3778
self.delay_sim_time(2)
3779
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
3780
self.progress("gpi=%s" % str(gpi))
3781
if gpi.lat != 0:
3782
break
3783
3784
def MAV_CMD_DO_SET_GLOBAL_ORIGIN(self):
3785
'''test MAV_CMD_DO_SET_GLOBAL_ORIGIN command'''
3786
# don't run this test if command not available:
3787
try:
3788
mavutil.mavlink.MAV_CMD_DO_SET_GLOBAL_ORIGIN
3789
except AttributeError:
3790
return
3791
3792
# disable the GPS so we don't get origin that way:
3793
self.set_parameters({
3794
"SIM_GPS1_ENABLE": 0,
3795
"GPS1_TYPE": 0,
3796
})
3797
self.context_collect('STATUSTEXT')
3798
self.reboot_sitl()
3799
# we must wait until we are using EKF3 as DCM does not allow
3800
# set_origin:
3801
self.wait_statustext("EKF3 active", check_context=True)
3802
self.run_cmd_int(
3803
mavutil.mavlink.MAV_CMD_DO_SET_GLOBAL_ORIGIN,
3804
p5=0,
3805
p6=0,
3806
p7=0,
3807
want_result=mavutil.mavlink.MAV_RESULT_DENIED
3808
)
3809
self.run_cmd_int(
3810
mavutil.mavlink.MAV_CMD_DO_SET_GLOBAL_ORIGIN,
3811
p5=0,
3812
p6=int(214*1e7),
3813
p7=0,
3814
want_result=mavutil.mavlink.MAV_RESULT_DENIED
3815
)
3816
origin_lat = -23.322332
3817
origin_lng = 123.45678
3818
origin_alt = 23.67 # metres
3819
self.run_cmd_int(
3820
mavutil.mavlink.MAV_CMD_DO_SET_GLOBAL_ORIGIN,
3821
p5=int(origin_lat*1e7),
3822
p6=int(origin_lng*1e7),
3823
p7=origin_alt
3824
)
3825
ggo = self.poll_message('GPS_GLOBAL_ORIGIN')
3826
self.assert_message_field_values(ggo, {
3827
"latitude": int(origin_lat*1e7),
3828
"longitude": int(origin_lng*1e7),
3829
"altitude": int(origin_alt*1000), # m -> mm
3830
})
3831
3832
def FarOrigin(self):
3833
'''fly a mission far from the vehicle origin'''
3834
# Fly mission #1
3835
self.set_parameters({
3836
"SIM_GPS1_ENABLE": 0,
3837
})
3838
self.reboot_sitl()
3839
nz = mavutil.location(-43.730171, 169.983118, 1466.3, 270)
3840
self.set_origin(nz)
3841
self.set_parameters({
3842
"SIM_GPS1_ENABLE": 1,
3843
})
3844
self.progress("# Load copter_mission")
3845
# load the waypoint count
3846
num_wp = self.load_mission("copter_mission.txt", strict=False)
3847
if not num_wp:
3848
raise NotAchievedException("load copter_mission failed")
3849
3850
self.fly_loaded_mission(num_wp)
3851
3852
self.progress("Auto mission completed: passed!")
3853
3854
def fly_loaded_mission(self, num_wp):
3855
'''fly mission loaded on vehicle. FIXME: get num_wp from vehicle'''
3856
self.progress("test: Fly a mission from 1 to %u" % num_wp)
3857
self.set_current_waypoint(1)
3858
3859
self.change_mode("LOITER")
3860
self.wait_ready_to_arm()
3861
self.arm_vehicle()
3862
3863
# switch into AUTO mode and raise throttle
3864
self.change_mode("AUTO")
3865
self.set_rc(3, 1500)
3866
3867
# fly the mission
3868
self.wait_waypoint(0, num_wp-1, timeout=500)
3869
3870
# set throttle to minimum
3871
self.zero_throttle()
3872
3873
# wait for disarm
3874
self.wait_disarmed()
3875
self.progress("MOTORS DISARMED OK")
3876
3877
def CANGPSCopterMission(self):
3878
'''fly mission which tests normal operation alongside CAN GPS'''
3879
self.set_parameters({
3880
"CAN_P1_DRIVER": 1,
3881
"GPS1_TYPE": 9,
3882
"GPS2_TYPE": 9,
3883
# disable simulated GPS, so only via DroneCAN
3884
"SIM_GPS1_ENABLE": 0,
3885
"SIM_GPS2_ENABLE": 0,
3886
# this ensures we use DroneCAN baro and compass
3887
"SIM_BARO_COUNT" : 0,
3888
"SIM_MAG1_DEVID" : 0,
3889
"SIM_MAG2_DEVID" : 0,
3890
"SIM_MAG3_DEVID" : 0,
3891
"COMPASS_USE2" : 0,
3892
"COMPASS_USE3" : 0,
3893
# use DroneCAN rangefinder
3894
"RNGFND1_TYPE" : 24,
3895
"RNGFND1_MAX" : 110.00,
3896
# use DroneCAN battery monitoring, and enforce with a arming voltage
3897
"BATT_MONITOR" : 8,
3898
"BATT_ARM_VOLT" : 12.0,
3899
"SIM_SPEEDUP": 2,
3900
})
3901
3902
self.context_push()
3903
# enable only GPS arming check during ordering test
3904
self.set_parameter("ARMING_SKIPCHK", ~(1 << 3))
3905
self.context_collect('STATUSTEXT')
3906
3907
self.reboot_sitl()
3908
# Test UAVCAN GPS ordering working
3909
gps1_det_text = self.wait_text("GPS 1: specified as DroneCAN.*", regex=True, check_context=True)
3910
gps2_det_text = self.wait_text("GPS 2: specified as DroneCAN.*", regex=True, check_context=True)
3911
gps1_nodeid = int(gps1_det_text.split('-')[1])
3912
gps2_nodeid = int(gps2_det_text.split('-')[1])
3913
if gps1_nodeid is None or gps2_nodeid is None:
3914
raise NotAchievedException("GPS not ordered per the order of Node IDs")
3915
3916
self.context_stop_collecting('STATUSTEXT')
3917
3918
GPS_Order_Tests = [[gps2_nodeid, gps2_nodeid, gps2_nodeid, 0,
3919
"PreArm: Same Node Id {} set for multiple GPS".format(gps2_nodeid)],
3920
[gps1_nodeid, int(gps2_nodeid/2), gps1_nodeid, 0,
3921
"Selected GPS Node {} not set as instance {}".format(int(gps2_nodeid/2), 2)],
3922
[int(gps1_nodeid/2), gps2_nodeid, 0, gps2_nodeid,
3923
"Selected GPS Node {} not set as instance {}".format(int(gps1_nodeid/2), 1)],
3924
[gps1_nodeid, gps2_nodeid, gps1_nodeid, gps2_nodeid, ""],
3925
[gps2_nodeid, gps1_nodeid, gps2_nodeid, gps1_nodeid, ""],
3926
[gps1_nodeid, 0, gps1_nodeid, gps2_nodeid, ""],
3927
[0, gps2_nodeid, gps1_nodeid, gps2_nodeid, ""]]
3928
for case in GPS_Order_Tests:
3929
self.progress("############################### Trying Case: " + str(case))
3930
self.set_parameters({
3931
"GPS1_CAN_OVRIDE": case[0],
3932
"GPS2_CAN_OVRIDE": case[1],
3933
})
3934
self.drain_mav()
3935
self.context_collect('STATUSTEXT')
3936
self.reboot_sitl()
3937
gps1_det_text = None
3938
gps2_det_text = None
3939
try:
3940
gps1_det_text = self.wait_text("GPS 1: specified as DroneCAN.*", regex=True, check_context=True)
3941
except AutoTestTimeoutException:
3942
pass
3943
try:
3944
gps2_det_text = self.wait_text("GPS 2: specified as DroneCAN.*", regex=True, check_context=True)
3945
except AutoTestTimeoutException:
3946
pass
3947
3948
self.context_stop_collecting('STATUSTEXT')
3949
self.change_mode('LOITER')
3950
if case[2] == 0 and case[3] == 0:
3951
if gps1_det_text or gps2_det_text:
3952
raise NotAchievedException("Failed ordering for requested CASE:", case)
3953
3954
if case[2] == 0 or case[3] == 0:
3955
if bool(gps1_det_text is not None) == bool(gps2_det_text is not None):
3956
print(gps1_det_text)
3957
print(gps2_det_text)
3958
raise NotAchievedException("Failed ordering for requested CASE:", case)
3959
3960
if gps1_det_text:
3961
if case[2] != int(gps1_det_text.split('-')[1]):
3962
raise NotAchievedException("Failed ordering for requested CASE:", case)
3963
if gps2_det_text:
3964
if case[3] != int(gps2_det_text.split('-')[1]):
3965
raise NotAchievedException("Failed ordering for requested CASE:", case)
3966
if len(case[4]):
3967
self.context_collect('STATUSTEXT')
3968
self.run_cmd(
3969
mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
3970
p1=1, # ARM
3971
timeout=10,
3972
want_result=mavutil.mavlink.MAV_RESULT_FAILED,
3973
)
3974
self.wait_statustext(case[4], check_context=True)
3975
self.context_stop_collecting('STATUSTEXT')
3976
self.progress("############################### All GPS Order Cases Tests Passed")
3977
self.progress("############################### Test Healthy Prearm check")
3978
self.set_parameter("ARMING_SKIPCHK", 0)
3979
self.stop_sup_program(instance=0)
3980
self.start_sup_program(instance=0, args="-M")
3981
self.stop_sup_program(instance=1)
3982
self.start_sup_program(instance=1, args="-M")
3983
self.delay_sim_time(2)
3984
self.context_collect('STATUSTEXT')
3985
self.run_cmd(
3986
mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
3987
p1=1, # ARM
3988
timeout=10,
3989
want_result=mavutil.mavlink.MAV_RESULT_FAILED,
3990
)
3991
self.wait_statustext(".*Node .* unhealthy", check_context=True, regex=True)
3992
self.stop_sup_program(instance=0)
3993
self.start_sup_program(instance=0)
3994
self.stop_sup_program(instance=1)
3995
self.start_sup_program(instance=1)
3996
self.context_stop_collecting('STATUSTEXT')
3997
self.context_pop()
3998
3999
self.set_parameters({
4000
# use DroneCAN ESCs for flight
4001
"CAN_D1_UC_ESC_BM" : 0x0f,
4002
# this stops us using local servo output, guaranteeing we are
4003
# flying on DroneCAN ESCs
4004
"SIM_CAN_SRV_MSK" : 0xFF,
4005
# we can do the flight faster
4006
"SIM_SPEEDUP" : 5,
4007
})
4008
4009
self.CopterMission()
4010
4011
def TakeoffAlt(self):
4012
'''Test Takeoff command altitude'''
4013
# Test case #1 (set target altitude to relative -10m from the ground, -10m is invalid, so it is set to 1m)
4014
self.progress("Testing relative alt from the ground")
4015
self.do_takeoff_alt("copter_takeoff.txt", 1, False)
4016
# Test case #2 (set target altitude to relative -10m during flight, -10m is invalid, so keeps current altitude)
4017
self.progress("Testing relative alt during flight")
4018
self.do_takeoff_alt("copter_takeoff.txt", 10, True)
4019
4020
self.progress("Takeoff mission completed: passed!")
4021
4022
def do_takeoff_alt(self, mission_file, target_alt, during_flight=False):
4023
self.progress("# Load %s" % mission_file)
4024
# load the waypoint count
4025
num_wp = self.load_mission(mission_file, strict=False)
4026
if not num_wp:
4027
raise NotAchievedException("load %s failed" % mission_file)
4028
4029
self.set_current_waypoint(1)
4030
4031
self.change_mode("GUIDED")
4032
self.wait_ready_to_arm()
4033
self.arm_vehicle()
4034
4035
if during_flight:
4036
self.user_takeoff(alt_min=target_alt)
4037
4038
# switch into AUTO mode and raise throttle
4039
self.change_mode("AUTO")
4040
self.set_rc(3, 1500)
4041
4042
# fly the mission
4043
self.wait_waypoint(0, num_wp-1, timeout=500)
4044
4045
# altitude check
4046
self.wait_altitude(target_alt - 1 , target_alt + 1, relative=True)
4047
4048
self.change_mode('LAND')
4049
4050
# set throttle to minimum
4051
self.zero_throttle()
4052
4053
# wait for disarm
4054
self.wait_disarmed()
4055
self.progress("MOTORS DISARMED OK")
4056
4057
def GuidedEKFLaneChange(self):
4058
'''test lane change with GPS diff on startup'''
4059
self.set_parameters({
4060
"EK3_SRC1_POSZ": 3,
4061
"EK3_AFFINITY" : 1,
4062
"GPS2_TYPE" : 1,
4063
"SIM_GPS2_ENABLE" : 1,
4064
"SIM_GPS2_GLTCH_Z" : -30
4065
})
4066
self.reboot_sitl()
4067
4068
self.change_mode("GUIDED")
4069
self.wait_ready_to_arm()
4070
4071
self.delay_sim_time(10, reason='"both EKF lanes to init"')
4072
4073
self.set_parameters({
4074
"SIM_GPS2_GLTCH_Z" : 0
4075
})
4076
4077
self.delay_sim_time(20, reason="EKF to do a position Z reset")
4078
4079
self.arm_vehicle()
4080
self.user_takeoff(alt_min=20)
4081
gps_alt = self.get_altitude(altitude_source='GPS_RAW_INT.alt')
4082
self.progress("Initial guided alt=%.1fm" % gps_alt)
4083
4084
self.context_collect('STATUSTEXT')
4085
self.progress("force a lane change")
4086
self.set_parameters({
4087
"INS_ACCOFFS_X" : 5
4088
})
4089
self.wait_statustext("EKF3 lane switch 1", timeout=10, check_context=True)
4090
4091
self.watch_altitude_maintained(
4092
altitude_min=gps_alt-2,
4093
altitude_max=gps_alt+2,
4094
altitude_source='GPS_RAW_INT.alt',
4095
minimum_duration=10,
4096
)
4097
4098
self.disarm_vehicle(force=True)
4099
self.reboot_sitl()
4100
4101
def test_EKF3_option_disable_lane_switch(self):
4102
'''Test that EK3_OPTION disables lane switching, and EK3_PRIMARY forces switch when re-enabled'''
4103
4104
self.set_parameters({
4105
"EK3_ENABLE": 1,
4106
"EK2_ENABLE": 0,
4107
"AHRS_EKF_TYPE": 3,
4108
"EK3_IMU_MASK": 3, # Use IMU0 and IMU1
4109
"EK3_OPTIONS": 2, # Disable lane switching
4110
"EK3_PRIMARY": 0, # Start with lane 0
4111
})
4112
4113
self.reboot_sitl()
4114
4115
self.lane_switches = []
4116
4117
# Hook to track STATUSTEXT messages for EKF lane switches
4118
def statustext_hook(mav, message):
4119
if message.get_type() != 'STATUSTEXT':
4120
return
4121
if message.text.startswith("EKF primary changed:"):
4122
try:
4123
lane = int(message.text.strip().split(":")[-1])
4124
self.lane_switches.append(lane)
4125
except ValueError:
4126
pass # ignore malformed messages
4127
4128
self.install_message_hook_context(statustext_hook)
4129
4130
self.takeoff(50, mode='ALT_HOLD')
4131
4132
self.delay_sim_time(5)
4133
4134
####################################################################################
4135
self.start_subtest("Ensure no lane switch occurs with EK3_OPTIONS = 2")
4136
self.context_collect("STATUSTEXT")
4137
self.set_parameters({
4138
"INS_ACCOFFS_X" : 5
4139
})
4140
4141
self.delay_sim_time(10) # Wait to confirm no switch
4142
if self.lane_switches:
4143
raise NotAchievedException(f"Unexpected lane switch occurred: {self.lane_switches}")
4144
self.progress("Success: No lane switch occurred with EK3_OPTIONS = 2")
4145
self.context_clear_collection("STATUSTEXT")
4146
self.set_parameters({
4147
"INS_ACCOFFS_X" : 0.01,
4148
})
4149
4150
####################################################################################
4151
self.start_subtest("EK3_PRIMARY = 1 (expect switch)")
4152
4153
self.context_collect("STATUSTEXT")
4154
self.set_parameters({
4155
"EK3_PRIMARY": 1,
4156
})
4157
4158
# Wait for automatic lane switch to occur
4159
self.wait_statustext(
4160
text="EKF primary changed:1",
4161
timeout=30,
4162
check_context=True
4163
)
4164
4165
self.context_clear_collection("STATUSTEXT")
4166
self.disarm_vehicle(force=True)
4167
4168
def MotorFail(self, ):
4169
"""Test flight with reduced motor efficiency"""
4170
# we only expect an octocopter to survive ATM:
4171
self.MotorFail_test_frame('octa', 8, frame_class=3)
4172
# self.MotorFail_test_frame('hexa', 6, frame_class=2)
4173
# self.MotorFail_test_frame('y6', 6, frame_class=5)
4174
4175
def MotorFail_test_frame(self, model, servo_count, frame_class, fail_servo=0, fail_mul=0.0, holdtime=30):
4176
self.set_parameters({
4177
'FRAME_CLASS': frame_class,
4178
})
4179
self.customise_SITL_commandline([], model=model)
4180
4181
self.takeoff(25, mode="LOITER")
4182
4183
# Get initial values
4184
start_hud = self.assert_receive_message('VFR_HUD')
4185
start_attitude = self.assert_receive_message('ATTITUDE')
4186
4187
hover_time = 5
4188
tstart = self.get_sim_time()
4189
int_error_alt = 0
4190
int_error_yaw_rate = 0
4191
int_error_yaw = 0
4192
self.progress("Hovering for %u seconds" % hover_time)
4193
failed = False
4194
while True:
4195
now = self.get_sim_time_cached()
4196
if now - tstart > holdtime + hover_time:
4197
break
4198
4199
servo = self.assert_receive_message('SERVO_OUTPUT_RAW')
4200
hud = self.assert_receive_message('VFR_HUD')
4201
attitude = self.assert_receive_message('ATTITUDE')
4202
4203
if not failed and now - tstart > hover_time:
4204
self.progress("Killing motor %u (%u%%)" %
4205
(fail_servo+1, fail_mul))
4206
self.set_parameters({
4207
"SIM_ENGINE_MUL": fail_mul,
4208
"SIM_ENGINE_FAIL": 1 << fail_servo,
4209
})
4210
failed = True
4211
4212
if failed:
4213
self.progress("Hold Time: %f/%f" % (now-tstart, holdtime))
4214
4215
servo_pwm = [
4216
servo.servo1_raw,
4217
servo.servo2_raw,
4218
servo.servo3_raw,
4219
servo.servo4_raw,
4220
servo.servo5_raw,
4221
servo.servo6_raw,
4222
servo.servo7_raw,
4223
servo.servo8_raw,
4224
]
4225
4226
self.progress("PWM output per motor")
4227
for i, pwm in enumerate(servo_pwm[0:servo_count]):
4228
if pwm > 1900:
4229
state = "oversaturated"
4230
elif pwm < 1200:
4231
state = "undersaturated"
4232
else:
4233
state = "OK"
4234
4235
if failed and i == fail_servo:
4236
state += " (failed)"
4237
4238
self.progress("servo %u [pwm=%u] [%s]" % (i+1, pwm, state))
4239
4240
alt_delta = hud.alt - start_hud.alt
4241
yawrate_delta = attitude.yawspeed - start_attitude.yawspeed
4242
yaw_delta = attitude.yaw - start_attitude.yaw
4243
4244
self.progress("Alt=%fm (delta=%fm)" % (hud.alt, alt_delta))
4245
self.progress("Yaw rate=%f (delta=%f) (rad/s)" %
4246
(attitude.yawspeed, yawrate_delta))
4247
self.progress("Yaw=%f (delta=%f) (deg)" %
4248
(attitude.yaw, yaw_delta))
4249
4250
dt = self.get_sim_time() - now
4251
int_error_alt += abs(alt_delta/dt)
4252
int_error_yaw_rate += abs(yawrate_delta/dt)
4253
int_error_yaw += abs(yaw_delta/dt)
4254
self.progress("## Error Integration ##")
4255
self.progress(" Altitude: %fm" % int_error_alt)
4256
self.progress(" Yaw rate: %f rad/s" % int_error_yaw_rate)
4257
self.progress(" Yaw: %f deg" % int_error_yaw)
4258
self.progress("----")
4259
4260
if int_error_yaw > 5:
4261
raise NotAchievedException("Vehicle is spinning")
4262
4263
if alt_delta < -20:
4264
raise NotAchievedException("Vehicle is descending")
4265
4266
self.progress("Fixing motors")
4267
self.set_parameter("SIM_ENGINE_FAIL", 0)
4268
4269
self.do_RTL()
4270
4271
def hover_for_interval(self, hover_time):
4272
'''hovers for an interval of hover_time seconds. Returns the bookend
4273
times for that interval (in time-since-boot frame), and the
4274
output throttle level at the end of the period.
4275
'''
4276
self.progress("Hovering for %u seconds" % hover_time)
4277
tstart = self.get_sim_time()
4278
self.delay_sim_time(hover_time, reason='data collection')
4279
vfr_hud = self.poll_message('VFR_HUD')
4280
tend = self.get_sim_time()
4281
return tstart, tend, vfr_hud.throttle
4282
4283
def MotorVibration(self):
4284
"""Test flight with motor vibration"""
4285
# magic tridge EKF type that dramatically speeds up the test
4286
self.set_parameters({
4287
"AHRS_EKF_TYPE": 10,
4288
"INS_LOG_BAT_MASK": 3,
4289
"INS_LOG_BAT_OPT": 0,
4290
"LOG_BITMASK": 958,
4291
"LOG_DISARMED": 0,
4292
"SIM_VIB_MOT_MAX": 350,
4293
# these are real values taken from a 180mm Quad:
4294
"SIM_GYR1_RND": 20,
4295
"SIM_ACC1_RND": 5,
4296
"SIM_ACC2_RND": 5,
4297
"SIM_INS_THR_MIN": 0.1,
4298
})
4299
self.reboot_sitl()
4300
4301
# do a simple up-and-down flight to gather data:
4302
self.takeoff(15, mode="ALT_HOLD")
4303
tstart, tend, hover_throttle = self.hover_for_interval(15)
4304
# if we don't reduce vibes here then the landing detector
4305
# may not trigger
4306
self.set_parameter("SIM_VIB_MOT_MAX", 0)
4307
self.do_RTL()
4308
4309
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
4310
# ignore the first 20Hz and look for a peak at -15dB or more
4311
# it should be at about 190Hz, each bin is 1000/1024Hz wide
4312
ignore_bins = int(100 * 1.024) # start at 100Hz to be safe
4313
freq = psd["F"][numpy.argmax(psd["X"][ignore_bins:]) + ignore_bins]
4314
if numpy.amax(psd["X"][ignore_bins:]) < -15 or freq < 100 or freq > 300:
4315
raise NotAchievedException(
4316
"Did not detect a motor peak, found %f at %f dB" %
4317
(freq, numpy.amax(psd["X"][ignore_bins:])))
4318
else:
4319
self.progress("Detected motor peak at %fHz" % freq)
4320
4321
# now add a notch and check that post-filter the peak is squashed below 40dB
4322
self.set_parameters({
4323
"INS_LOG_BAT_OPT": 2,
4324
"INS_HNTC2_ENABLE": 1,
4325
"INS_HNTC2_FREQ": freq,
4326
"INS_HNTC2_ATT": 50,
4327
"INS_HNTC2_BW": freq/2,
4328
"INS_HNTC2_MODE": 0,
4329
"SIM_VIB_MOT_MAX": 350,
4330
})
4331
self.reboot_sitl()
4332
4333
# do a simple up-and-down flight to gather data:
4334
self.takeoff(15, mode="ALT_HOLD")
4335
tstart, tend, hover_throttle = self.hover_for_interval(15)
4336
self.set_parameter("SIM_VIB_MOT_MAX", 0)
4337
self.do_RTL()
4338
4339
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
4340
freq = psd["F"][numpy.argmax(psd["X"][ignore_bins:]) + ignore_bins]
4341
peakdB = numpy.amax(psd["X"][ignore_bins:])
4342
if peakdB < -23:
4343
self.progress("Did not detect a motor peak, found %f at %f dB" % (freq, peakdB))
4344
else:
4345
raise NotAchievedException("Detected peak %.1f Hz %.2f dB" % (freq, peakdB))
4346
4347
def VisionPosition(self):
4348
"""Disable GPS navigation, enable Vicon input."""
4349
# scribble down a location we can set origin to:
4350
4351
self.customise_SITL_commandline(["--serial5=sim:vicon:"])
4352
self.progress("Waiting for location")
4353
self.change_mode('LOITER')
4354
self.wait_ready_to_arm()
4355
4356
old_pos = self.assert_receive_message('GLOBAL_POSITION_INT')
4357
print("old_pos=%s" % str(old_pos))
4358
4359
# configure EKF to use external nav instead of GPS
4360
ahrs_ekf_type = self.get_parameter("AHRS_EKF_TYPE")
4361
if ahrs_ekf_type == 2:
4362
self.set_parameter("EK2_GPS_TYPE", 3)
4363
if ahrs_ekf_type == 3:
4364
self.set_parameters({
4365
"EK3_SRC1_POSXY": 6,
4366
"EK3_SRC1_VELXY": 6,
4367
"EK3_SRC1_POSZ": 6,
4368
"EK3_SRC1_VELZ": 6,
4369
})
4370
self.set_parameters({
4371
"GPS1_TYPE": 0,
4372
"VISO_TYPE": 1,
4373
"SERIAL5_PROTOCOL": 1,
4374
})
4375
self.reboot_sitl()
4376
# without a GPS or some sort of external prompting, AP
4377
# doesn't send system_time messages. So prompt it:
4378
self.mav.mav.system_time_send(int(time.time() * 1000000), 0)
4379
self.progress("Waiting for non-zero-lat")
4380
tstart = self.get_sim_time()
4381
while True:
4382
if self.get_sim_time_cached() - tstart > 60:
4383
raise AutoTestTimeoutException("Did not get non-zero lat")
4384
self.mav.mav.set_gps_global_origin_send(1,
4385
old_pos.lat,
4386
old_pos.lon,
4387
old_pos.alt)
4388
self.delay_sim_time(2)
4389
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
4390
self.progress("gpi=%s" % str(gpi))
4391
if gpi.lat != 0:
4392
break
4393
4394
self.takeoff()
4395
self.set_rc(1, 1600)
4396
tstart = self.get_sim_time()
4397
while True:
4398
vicon_pos = self.assert_receive_message('VISION_POSITION_ESTIMATE')
4399
# print("vpe=%s" % str(vicon_pos))
4400
# gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
4401
# self.progress("gpi=%s" % str(gpi))
4402
if vicon_pos.x > 40:
4403
break
4404
4405
if self.get_sim_time_cached() - tstart > 100:
4406
raise AutoTestTimeoutException("Vicon showed no movement")
4407
4408
# recenter controls:
4409
self.set_rc(1, 1500)
4410
self.progress("# Enter RTL")
4411
self.change_mode('RTL')
4412
self.set_rc(3, 1500)
4413
tstart = self.get_sim_time()
4414
# self.install_messageprinter_handlers_context(['SIMSTATE', 'GLOBAL_POSITION_INT'])
4415
self.wait_disarmed(timeout=200)
4416
4417
def BodyFrameOdom(self):
4418
"""Disable GPS navigation, enable input of VISION_POSITION_DELTA."""
4419
4420
if self.get_parameter("AHRS_EKF_TYPE") != 3:
4421
# only tested on this EKF
4422
return
4423
4424
self.customise_SITL_commandline(["--serial5=sim:vicon:"])
4425
4426
if self.current_onboard_log_contains_message("XKFD"):
4427
raise NotAchievedException("Found unexpected XKFD message")
4428
4429
# scribble down a location we can set origin to:
4430
self.progress("Waiting for location")
4431
self.change_mode('LOITER')
4432
self.wait_ready_to_arm()
4433
4434
old_pos = self.assert_receive_message('GLOBAL_POSITION_INT')
4435
print("old_pos=%s" % str(old_pos))
4436
4437
# configure EKF to use external nav instead of GPS
4438
self.set_parameters({
4439
"EK3_SRC1_POSXY": 6,
4440
"EK3_SRC1_VELXY": 6,
4441
"EK3_SRC1_POSZ": 6,
4442
"EK3_SRC1_VELZ": 6,
4443
"GPS1_TYPE": 0,
4444
"VISO_TYPE": 1,
4445
"SERIAL5_PROTOCOL": 1,
4446
"SIM_VICON_TMASK": 8, # send VISION_POSITION_DELTA
4447
})
4448
self.reboot_sitl()
4449
# without a GPS or some sort of external prompting, AP
4450
# doesn't send system_time messages. So prompt it:
4451
self.mav.mav.system_time_send(int(time.time() * 1000000), 0)
4452
self.progress("Waiting for non-zero-lat")
4453
tstart = self.get_sim_time()
4454
while True:
4455
self.mav.mav.set_gps_global_origin_send(1,
4456
old_pos.lat,
4457
old_pos.lon,
4458
old_pos.alt)
4459
self.delay_sim_time(2)
4460
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
4461
self.progress("gpi=%s" % str(gpi))
4462
if gpi.lat != 0:
4463
break
4464
4465
if self.get_sim_time_cached() - tstart > 60:
4466
raise AutoTestTimeoutException("Did not get non-zero lat")
4467
4468
self.takeoff(alt_min=5, mode='ALT_HOLD', require_absolute=False, takeoff_throttle=1800)
4469
self.change_mode('LAND')
4470
# TODO: something more elaborate here - EKF will only aid
4471
# relative position
4472
self.wait_disarmed()
4473
if not self.current_onboard_log_contains_message("XKFD"):
4474
raise NotAchievedException("Did not find expected XKFD message")
4475
4476
def FlyMissionTwice(self):
4477
'''fly a mission twice in a row without changing modes in between.
4478
Seeks to show bugs in mission state machine'''
4479
4480
self.upload_simple_relhome_mission([
4481
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
4482
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 20),
4483
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
4484
])
4485
4486
num_wp = self.get_mission_count()
4487
self.set_parameter("AUTO_OPTIONS", 3)
4488
self.change_mode('AUTO')
4489
self.wait_ready_to_arm()
4490
for i in 1, 2:
4491
self.progress("run %u" % i)
4492
self.arm_vehicle()
4493
self.wait_waypoint(num_wp-1, num_wp-1)
4494
self.wait_disarmed()
4495
self.delay_sim_time(20)
4496
4497
def FlyMissionTwiceWithReset(self):
4498
'''Fly a mission twice in a row without changing modes in between.
4499
Allow the mission to complete, then reset the mission state machine and restart the mission.'''
4500
4501
self.upload_simple_relhome_mission([
4502
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
4503
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 20),
4504
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
4505
])
4506
4507
num_wp = self.get_mission_count()
4508
self.set_parameter("AUTO_OPTIONS", 3)
4509
self.change_mode('AUTO')
4510
self.wait_ready_to_arm()
4511
4512
for i in 1, 2:
4513
self.progress("run %u" % i)
4514
# Use the "Reset Mission" param of DO_SET_MISSION_CURRENT to reset mission state machine
4515
self.set_current_waypoint_using_mav_cmd_do_set_mission_current(seq=0, reset=1)
4516
self.arm_vehicle()
4517
self.wait_waypoint(num_wp-1, num_wp-1)
4518
self.wait_disarmed()
4519
self.delay_sim_time(20)
4520
4521
def MissionIndexValidity(self):
4522
'''Confirm that attempting to select an invalid mission item is rejected.'''
4523
4524
self.upload_simple_relhome_mission([
4525
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
4526
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 20),
4527
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
4528
])
4529
4530
num_wp = self.get_mission_count()
4531
accepted_indices = [0, 1, num_wp-1]
4532
denied_indices = [-1, num_wp]
4533
4534
for seq in accepted_indices:
4535
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_MISSION_CURRENT,
4536
p1=seq,
4537
timeout=1,
4538
want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
4539
4540
for seq in denied_indices:
4541
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_MISSION_CURRENT,
4542
p1=seq,
4543
timeout=1,
4544
want_result=mavutil.mavlink.MAV_RESULT_DENIED)
4545
4546
def InvalidJumpTags(self):
4547
'''Verify the behaviour when selecting invalid jump tags.'''
4548
4549
MAX_TAG_NUM = 65535
4550
# Jump tag is not present, so expect FAILED
4551
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_JUMP_TAG,
4552
p1=MAX_TAG_NUM,
4553
timeout=1,
4554
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
4555
4556
# Jump tag is too big, so expect DENIED
4557
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_JUMP_TAG,
4558
p1=MAX_TAG_NUM+1,
4559
timeout=1,
4560
want_result=mavutil.mavlink.MAV_RESULT_DENIED)
4561
4562
def GPSViconSwitching(self):
4563
"""Fly GPS and Vicon switching test"""
4564
"""Setup parameters including switching to EKF3"""
4565
self.set_parameters({
4566
"VISO_TYPE": 2, # enable vicon
4567
"SERIAL5_PROTOCOL": 2,
4568
"EK3_ENABLE": 1,
4569
"EK3_SRC2_POSXY": 6, # External Nav
4570
"EK3_SRC2_POSZ": 6, # External Nav
4571
"EK3_SRC2_VELXY": 6, # External Nav
4572
"EK3_SRC2_VELZ": 6, # External Nav
4573
"EK3_SRC2_YAW": 6, # External Nav
4574
"RC7_OPTION": 80, # RC aux switch 7 set to Viso Align
4575
"RC8_OPTION": 90, # RC aux switch 8 set to EKF source selector
4576
"EK2_ENABLE": 0,
4577
"AHRS_EKF_TYPE": 3,
4578
})
4579
self.customise_SITL_commandline(["--serial5=sim:vicon:"])
4580
4581
# switch to use GPS
4582
self.set_rc(8, 1000)
4583
4584
# ensure we can get a global position:
4585
self.poll_home_position(timeout=120)
4586
4587
# record starting position
4588
old_pos = self.get_global_position_int()
4589
print("old_pos=%s" % str(old_pos))
4590
4591
# align vicon yaw with ahrs heading
4592
self.set_rc(7, 2000)
4593
4594
# takeoff to 10m in Loiter
4595
self.progress("Moving to ensure location is tracked")
4596
self.takeoff(10, mode="LOITER", require_absolute=True, timeout=720)
4597
4598
# fly forward in Loiter
4599
self.set_rc(2, 1300)
4600
4601
# disable vicon
4602
self.set_parameter("SIM_VICON_FAIL", 1)
4603
4604
# ensure vehicle remain in Loiter for 15 seconds
4605
tstart = self.get_sim_time()
4606
while self.get_sim_time() - tstart < 15:
4607
if not self.mode_is('LOITER'):
4608
raise NotAchievedException("Expected to stay in loiter for >15 seconds")
4609
4610
# re-enable vicon
4611
self.set_parameter("SIM_VICON_FAIL", 0)
4612
4613
# switch to vicon, disable GPS and wait 10sec to ensure vehicle remains in Loiter
4614
self.set_rc(8, 1500)
4615
self.set_parameter("GPS1_TYPE", 0)
4616
4617
# ensure vehicle remain in Loiter for 15 seconds
4618
tstart = self.get_sim_time()
4619
while self.get_sim_time() - tstart < 15:
4620
if not self.mode_is('LOITER'):
4621
raise NotAchievedException("Expected to stay in loiter for >15 seconds")
4622
4623
# RTL and check vehicle arrives within 10m of home
4624
self.set_rc(2, 1500)
4625
self.do_RTL()
4626
4627
def RTLSpeed(self):
4628
"""Test RTL Speed parameters"""
4629
rtl_speed_ms = 7
4630
wpnav_speed_ms = 4
4631
wpnav_accel_mss = 3
4632
tolerance = 0.5
4633
self.load_mission("copter_rtl_speed.txt")
4634
self.set_parameters({
4635
'WPNAV_ACCEL': wpnav_accel_mss * 100,
4636
'RTL_SPEED_MS': rtl_speed_ms,
4637
'WPNAV_SPEED': wpnav_speed_ms * 100,
4638
})
4639
self.change_mode('LOITER')
4640
self.wait_ready_to_arm()
4641
self.arm_vehicle()
4642
self.change_mode('AUTO')
4643
self.set_rc(3, 1600)
4644
self.wait_altitude(19, 25, relative=True)
4645
self.wait_groundspeed(wpnav_speed_ms-tolerance, wpnav_speed_ms+tolerance)
4646
self.monitor_groundspeed(wpnav_speed_ms, timeout=20)
4647
self.change_mode('RTL')
4648
self.wait_groundspeed(rtl_speed_ms-tolerance, rtl_speed_ms+tolerance)
4649
self.monitor_groundspeed(rtl_speed_ms, timeout=5)
4650
self.change_mode('AUTO')
4651
self.wait_groundspeed(0-tolerance, 0+tolerance)
4652
self.wait_groundspeed(wpnav_speed_ms-tolerance, wpnav_speed_ms+tolerance)
4653
self.monitor_groundspeed(wpnav_speed_ms, tolerance=0.6, timeout=5)
4654
self.do_RTL()
4655
4656
def NavDelay(self):
4657
"""Fly a simple mission that has a delay in it."""
4658
4659
self.load_mission("copter_nav_delay.txt")
4660
4661
self.set_parameter("DISARM_DELAY", 0)
4662
4663
self.change_mode("LOITER")
4664
self.wait_ready_to_arm()
4665
4666
self.arm_vehicle()
4667
self.change_mode("AUTO")
4668
self.set_rc(3, 1600)
4669
count_start = -1
4670
count_stop = -1
4671
tstart = self.get_sim_time()
4672
last_mission_current_msg = 0
4673
last_seq = None
4674
while self.armed(): # we RTL at end of mission
4675
now = self.get_sim_time_cached()
4676
if now - tstart > 200:
4677
raise AutoTestTimeoutException("Did not disarm as expected")
4678
m = self.assert_receive_message('MISSION_CURRENT')
4679
at_delay_item = ""
4680
if m.seq == 3:
4681
at_delay_item = "(At delay item)"
4682
if count_start == -1:
4683
count_start = now
4684
if ((now - last_mission_current_msg) > 1 or m.seq != last_seq):
4685
dist = None
4686
x = self.mav.messages.get("NAV_CONTROLLER_OUTPUT", None)
4687
if x is not None:
4688
dist = x.wp_dist
4689
self.progress("MISSION_CURRENT.seq=%u dist=%s %s" %
4690
(m.seq, dist, at_delay_item))
4691
last_mission_current_msg = self.get_sim_time_cached()
4692
last_seq = m.seq
4693
if m.seq > 3:
4694
if count_stop == -1:
4695
count_stop = now
4696
calculated_delay = count_stop - count_start
4697
want_delay = 59 # should reflect what's in the mission file
4698
self.progress("Stopped for %u seconds (want >=%u seconds)" %
4699
(calculated_delay, want_delay))
4700
if calculated_delay < want_delay:
4701
raise NotAchievedException("Did not delay for long enough")
4702
4703
def RangeFinder(self):
4704
'''Test RangeFinder Basic Functionality'''
4705
self.assert_not_receiving_message('RANGEFINDER', timeout=5)
4706
self.assert_not_receiving_message('DISTANCE_SENSOR', timeout=5)
4707
4708
# may need to force a rotation if some other test has used the
4709
# rangefinder...
4710
self.progress("Ensure no RFND messages in log")
4711
self.set_parameter("LOG_DISARMED", 1)
4712
if self.current_onboard_log_contains_message("RFND"):
4713
raise NotAchievedException("Found unexpected RFND message")
4714
4715
self.set_analog_rangefinder_parameters()
4716
self.set_parameter("RC9_OPTION", 10) # rangefinder
4717
self.set_rc(9, 2000)
4718
4719
self.reboot_sitl()
4720
4721
self.progress("Making sure we now get RANGEFINDER messages")
4722
self.context_set_message_rate_hz('RANGEFINDER', self.sitl_streamrate())
4723
m = self.assert_receive_message('RANGEFINDER', timeout=10)
4724
4725
self.progress("Making sure we now get DISTANCE_SENSOR messages")
4726
self.assert_receive_message('DISTANCE_SENSOR', timeout=10)
4727
4728
self.progress("Checking RangeFinder is marked as enabled in mavlink")
4729
m = self.assert_receive_message('SYS_STATUS', timeout=10)
4730
flags = m.onboard_control_sensors_enabled
4731
if not flags & mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION:
4732
raise NotAchievedException("Laser not enabled in SYS_STATUS")
4733
self.progress("Disabling laser using switch")
4734
self.set_rc(9, 1000)
4735
self.delay_sim_time(1)
4736
self.progress("Checking RangeFinder is marked as disabled in mavlink")
4737
m = self.assert_receive_message('SYS_STATUS', timeout=10)
4738
flags = m.onboard_control_sensors_enabled
4739
if flags & mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION:
4740
raise NotAchievedException("Laser enabled in SYS_STATUS")
4741
4742
self.progress("Re-enabling rangefinder")
4743
self.set_rc(9, 2000)
4744
self.delay_sim_time(1)
4745
m = self.assert_receive_message('SYS_STATUS', timeout=10)
4746
flags = m.onboard_control_sensors_enabled
4747
if not flags & mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION:
4748
raise NotAchievedException("Laser not enabled in SYS_STATUS")
4749
4750
self.takeoff(10, mode="LOITER")
4751
4752
m_p = self.assert_receive_message('GLOBAL_POSITION_INT')
4753
4754
m_r = self.assert_receive_message('RANGEFINDER')
4755
if abs(m_r.distance - m_p.relative_alt/1000) > 1:
4756
raise NotAchievedException(
4757
"RANGEFINDER/GLOBAL_POSITION_INT mismatch %0.2f vs %0.2f" %
4758
(m_r.distance, m_p.relative_alt/1000))
4759
4760
m_ds = self.assert_receive_message('DISTANCE_SENSOR')
4761
if abs(m_ds.current_distance * 0.01 - m_p.relative_alt/1000) > 1:
4762
raise NotAchievedException(
4763
"DISTANCE_SENSOR/GLOBAL_POSITION_INT mismatch %0.2f vs %0.2f" %
4764
(m_ds.current_distance*0.01, m_p.relative_alt/1000))
4765
4766
self.land_and_disarm()
4767
4768
if not self.current_onboard_log_contains_message("RFND"):
4769
raise NotAchievedException("Did not see expected RFND message")
4770
4771
def SplineTerrain(self):
4772
'''Test Splines and Terrain'''
4773
self.set_parameter("TERRAIN_ENABLE", 0)
4774
self.fly_mission("wp.txt")
4775
4776
def WPNAV_SPEED(self):
4777
'''ensure resetting WPNAV_SPEED during a mission works'''
4778
4779
loc = self.poll_home_position()
4780
alt = 20
4781
loc.alt = alt
4782
items = []
4783
4784
# 100 waypoints in a line, 10m apart in a northerly direction
4785
# for i in range(1, 100):
4786
# items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, i*10, 0, alt))
4787
4788
# 1 waypoint a long way away
4789
items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 2000, 0, alt),)
4790
4791
items.append((mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0))
4792
4793
self.upload_simple_relhome_mission(items)
4794
4795
start_speed_ms = self.get_parameter('WPNAV_SPEED') / 100.0
4796
4797
self.takeoff(20)
4798
self.change_mode('AUTO')
4799
self.wait_groundspeed(start_speed_ms-1, start_speed_ms+1, minimum_duration=10)
4800
4801
for speed_ms in 7, 8, 7, 8, 9, 10, 11, 7:
4802
self.set_parameter('WPNAV_SPEED', speed_ms*100)
4803
self.wait_groundspeed(speed_ms-1, speed_ms+1, minimum_duration=10)
4804
self.do_RTL()
4805
4806
def WPNAV_SPEED_UP(self):
4807
'''Change speed (up) during mission'''
4808
4809
items = []
4810
4811
# 1 waypoint a long way up
4812
items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20000),)
4813
4814
items.append((mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0))
4815
4816
self.upload_simple_relhome_mission(items)
4817
4818
start_speed_ms = self.get_parameter('WPNAV_SPEED_UP') / 100.0
4819
4820
minimum_duration = 5
4821
4822
self.takeoff(20)
4823
self.change_mode('AUTO')
4824
self.wait_climbrate(start_speed_ms-1, start_speed_ms+1, minimum_duration=minimum_duration)
4825
4826
for speed_ms in 7, 8, 7, 8, 6, 2:
4827
self.set_parameter('WPNAV_SPEED_UP', speed_ms*100)
4828
self.wait_climbrate(speed_ms-1, speed_ms+1, minimum_duration=minimum_duration)
4829
self.do_RTL(timeout=240)
4830
4831
def WPNAV_SPEED_DN(self):
4832
'''Change speed (down) during mission'''
4833
4834
items = []
4835
4836
# 1 waypoint a long way back down
4837
items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 10),)
4838
4839
items.append((mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0))
4840
4841
self.upload_simple_relhome_mission(items)
4842
4843
minimum_duration = 5
4844
4845
self.takeoff(500, timeout=70)
4846
self.change_mode('AUTO')
4847
4848
start_speed_ms = self.get_parameter('WPNAV_SPEED_DN') / 100.0
4849
self.wait_climbrate(-start_speed_ms-1, -start_speed_ms+1, minimum_duration=minimum_duration)
4850
4851
for speed_ms in 7, 8, 7, 8, 6, 2:
4852
self.set_parameter('WPNAV_SPEED_DN', speed_ms*100)
4853
self.wait_climbrate(-speed_ms-1, -speed_ms+1, minimum_duration=minimum_duration)
4854
self.do_RTL()
4855
4856
def load_and_start_mission(self, filename, strict=True):
4857
num_wp = self.load_mission(filename, strict=strict)
4858
self.set_parameter("AUTO_OPTIONS", 3)
4859
self.change_mode('AUTO')
4860
self.wait_ready_to_arm()
4861
self.arm_vehicle()
4862
return num_wp
4863
4864
def fly_mission(self, filename, strict=True):
4865
num_wp = self.load_and_start_mission(filename, strict)
4866
self.wait_waypoint(num_wp-1, num_wp-1)
4867
self.wait_disarmed()
4868
4869
def fly_generic_mission(self, filename, strict=True):
4870
num_wp = self.load_generic_mission(filename, strict=strict)
4871
self.set_parameter("AUTO_OPTIONS", 3)
4872
self.change_mode('AUTO')
4873
self.wait_ready_to_arm()
4874
self.arm_vehicle()
4875
self.wait_waypoint(num_wp-1, num_wp-1)
4876
self.wait_disarmed()
4877
4878
def SurfaceTracking(self):
4879
'''Test Surface Tracking'''
4880
ex = None
4881
self.context_push()
4882
4883
self.install_terrain_handlers_context()
4884
4885
try:
4886
self.set_analog_rangefinder_parameters()
4887
self.set_parameter("RC9_OPTION", 10) # rangefinder
4888
self.set_rc(9, 2000)
4889
4890
self.reboot_sitl() # needed for both rangefinder and initial position
4891
self.assert_vehicle_location_is_at_startup_location()
4892
4893
self.takeoff(10, mode="LOITER")
4894
lower_surface_pos = mavutil.location(-35.362421, 149.164534, 584, 270)
4895
here = self.mav.location()
4896
bearing = self.get_bearing(here, lower_surface_pos)
4897
4898
self.change_mode("GUIDED")
4899
self.guided_achieve_heading(bearing)
4900
self.change_mode("LOITER")
4901
self.delay_sim_time(2)
4902
m = self.assert_receive_message('GLOBAL_POSITION_INT')
4903
orig_absolute_alt_mm = m.alt
4904
4905
self.progress("Original alt: absolute=%f" % orig_absolute_alt_mm)
4906
4907
self.progress("Flying somewhere which surface is known lower compared to takeoff point")
4908
self.set_rc(2, 1450)
4909
tstart = self.get_sim_time()
4910
while True:
4911
if self.get_sim_time() - tstart > 200:
4912
raise NotAchievedException("Did not reach lower point")
4913
m = self.assert_receive_message('GLOBAL_POSITION_INT')
4914
x = mavutil.location(m.lat/1e7, m.lon/1e7, m.alt/1e3, 0)
4915
dist = self.get_distance(x, lower_surface_pos)
4916
delta = (orig_absolute_alt_mm - m.alt)/1000.0
4917
4918
self.progress("Distance: %fm abs-alt-delta: %fm" %
4919
(dist, delta))
4920
if dist < 15:
4921
if delta < 0.8:
4922
raise NotAchievedException("Did not dip in altitude as expected")
4923
break
4924
4925
self.set_rc(2, 1500)
4926
self.do_RTL()
4927
4928
except Exception as e:
4929
self.print_exception_caught(e)
4930
self.disarm_vehicle(force=True)
4931
ex = e
4932
4933
self.context_pop()
4934
self.reboot_sitl()
4935
if ex is not None:
4936
raise ex
4937
4938
def test_rangefinder_switchover(self):
4939
"""test that the EKF correctly handles the switchover between baro and rangefinder"""
4940
self.set_analog_rangefinder_parameters()
4941
4942
self.set_parameters({
4943
"RNGFND1_MAX": 15.00
4944
})
4945
4946
# configure EKF to use rangefinder for altitude at low altitudes
4947
ahrs_ekf_type = self.get_parameter("AHRS_EKF_TYPE")
4948
if ahrs_ekf_type == 2:
4949
self.set_parameter("EK2_RNG_USE_HGT", 70)
4950
if ahrs_ekf_type == 3:
4951
self.set_parameter("EK3_RNG_USE_HGT", 70)
4952
4953
self.reboot_sitl() # needed for both rangefinder and initial position
4954
self.assert_vehicle_location_is_at_startup_location()
4955
4956
self.change_mode("LOITER")
4957
self.wait_ready_to_arm()
4958
self.arm_vehicle()
4959
self.set_rc(3, 1800)
4960
self.set_rc(2, 1200)
4961
# wait till we get to 50m
4962
self.wait_altitude(50, 52, True, 60)
4963
4964
self.change_mode("RTL")
4965
# wait till we get to 25m
4966
self.wait_altitude(25, 27, True, 120)
4967
4968
# level up
4969
self.set_rc(2, 1500)
4970
self.wait_altitude(14, 15, relative=True)
4971
4972
self.wait_rtl_complete()
4973
4974
def _Parachute(self, command):
4975
'''Test Parachute Functionality using specific mavlink command'''
4976
self.set_rc(9, 1000)
4977
self.set_parameters({
4978
"CHUTE_ENABLED": 1,
4979
"CHUTE_TYPE": 10,
4980
"SERVO9_FUNCTION": 27,
4981
"SIM_PARA_ENABLE": 1,
4982
"SIM_PARA_PIN": 9,
4983
})
4984
4985
self.progress("Test triggering parachute in mission")
4986
self.load_mission("copter_parachute_mission.txt")
4987
self.change_mode('LOITER')
4988
self.wait_ready_to_arm()
4989
self.arm_vehicle()
4990
self.change_mode('AUTO')
4991
self.set_rc(3, 1600)
4992
self.wait_statustext('BANG', timeout=60)
4993
self.disarm_vehicle(force=True)
4994
self.reboot_sitl()
4995
4996
self.progress("Test triggering with mavlink message")
4997
self.takeoff(20)
4998
command(
4999
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
5000
p1=2, # release
5001
)
5002
self.wait_statustext('BANG', timeout=60)
5003
self.disarm_vehicle(force=True)
5004
self.reboot_sitl()
5005
5006
self.progress("Testing three-position switch")
5007
self.set_parameter("RC9_OPTION", 23) # parachute 3pos
5008
5009
self.progress("Test manual triggering")
5010
self.takeoff(20)
5011
self.set_rc(9, 2000)
5012
self.wait_statustext('BANG', timeout=60)
5013
self.set_rc(9, 1000)
5014
self.disarm_vehicle(force=True)
5015
self.reboot_sitl()
5016
5017
self.progress("Test mavlink triggering")
5018
self.takeoff(20)
5019
command(
5020
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
5021
p1=mavutil.mavlink.PARACHUTE_DISABLE,
5022
)
5023
ok = False
5024
try:
5025
self.wait_statustext('BANG', timeout=2)
5026
except AutoTestTimeoutException:
5027
ok = True
5028
if not ok:
5029
raise NotAchievedException("Disabled parachute fired")
5030
command(
5031
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
5032
p1=mavutil.mavlink.PARACHUTE_ENABLE,
5033
)
5034
ok = False
5035
try:
5036
self.wait_statustext('BANG', timeout=2)
5037
except AutoTestTimeoutException:
5038
ok = True
5039
if not ok:
5040
raise NotAchievedException("Enabled parachute fired")
5041
5042
self.set_rc(9, 1000)
5043
self.disarm_vehicle(force=True)
5044
self.reboot_sitl()
5045
5046
# parachute should not fire if you go from disabled to release:
5047
self.takeoff(20)
5048
command(
5049
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
5050
p1=mavutil.mavlink.PARACHUTE_RELEASE,
5051
)
5052
ok = False
5053
try:
5054
self.wait_statustext('BANG', timeout=2)
5055
except AutoTestTimeoutException:
5056
ok = True
5057
if not ok:
5058
raise NotAchievedException("Parachute fired when going straight from disabled to release")
5059
5060
# now enable then release parachute:
5061
command(
5062
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
5063
p1=mavutil.mavlink.PARACHUTE_ENABLE,
5064
)
5065
command(
5066
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
5067
p1=mavutil.mavlink.PARACHUTE_RELEASE,
5068
)
5069
self.wait_statustext('BANG! Parachute deployed', timeout=2)
5070
self.disarm_vehicle(force=True)
5071
self.reboot_sitl()
5072
5073
self.context_push()
5074
self.progress("Crashing with 3pos switch in enable position")
5075
self.takeoff(40)
5076
self.set_rc(9, 1500)
5077
self.set_parameters({
5078
"SIM_ENGINE_FAIL": 1 << 1, # motor 2
5079
})
5080
self.wait_statustext('BANG! Parachute deployed', timeout=60)
5081
self.set_rc(9, 1000)
5082
self.disarm_vehicle(force=True)
5083
self.reboot_sitl()
5084
self.context_pop()
5085
5086
self.progress("Crashing with 3pos switch in disable position")
5087
loiter_alt = 10
5088
self.takeoff(loiter_alt, mode='LOITER')
5089
self.set_rc(9, 1100)
5090
self.set_parameters({
5091
"SIM_ENGINE_FAIL": 1 << 1, # motor 2
5092
})
5093
tstart = self.get_sim_time()
5094
while self.get_sim_time_cached() < tstart + 5:
5095
m = self.assert_receive_message('STATUSTEXT')
5096
if m is None:
5097
continue
5098
if "BANG" in m.text:
5099
self.set_rc(9, 1000)
5100
self.reboot_sitl()
5101
raise NotAchievedException("Parachute deployed when disabled")
5102
self.set_rc(9, 1000)
5103
self.disarm_vehicle(force=True)
5104
self.reboot_sitl()
5105
5106
def Parachute(self):
5107
'''Test Parachute Functionality'''
5108
self._Parachute(self.run_cmd)
5109
self._Parachute(self.run_cmd_int)
5110
5111
def PrecisionLanding(self):
5112
"""Use PrecLand backends precision messages to land aircraft."""
5113
5114
self.context_push()
5115
5116
for backend in [4, 2]: # SITL, SITL-IRLOCK
5117
ex = None
5118
try:
5119
self.set_parameters({
5120
"PLND_ENABLED": 1,
5121
"PLND_TYPE": backend,
5122
})
5123
5124
self.set_analog_rangefinder_parameters()
5125
self.set_parameter("SIM_SONAR_SCALE", 12)
5126
5127
start = self.mav.location()
5128
target = start
5129
(target.lat, target.lng) = mavextra.gps_offset(start.lat, start.lng, 4, -4)
5130
self.progress("Setting target to %f %f" % (target.lat, target.lng))
5131
5132
self.set_parameters({
5133
"SIM_PLD_ENABLE": 1,
5134
"SIM_PLD_LAT": target.lat,
5135
"SIM_PLD_LON": target.lng,
5136
"SIM_PLD_HEIGHT": 0,
5137
"SIM_PLD_ALT_LMT": 15,
5138
"SIM_PLD_DIST_LMT": 10,
5139
})
5140
5141
self.reboot_sitl()
5142
5143
self.progress("Waiting for location")
5144
self.zero_throttle()
5145
self.takeoff(10, 1800, mode="LOITER")
5146
self.change_mode("LAND")
5147
self.zero_throttle()
5148
self.wait_landed_and_disarmed()
5149
self.assert_receive_message('GLOBAL_POSITION_INT')
5150
new_pos = self.mav.location()
5151
delta = self.get_distance(target, new_pos)
5152
self.progress("Landed %f metres from target position" % delta)
5153
max_delta = 1.5
5154
if delta > max_delta:
5155
raise NotAchievedException("Did not land close enough to target position (%fm > %fm" % (delta, max_delta))
5156
5157
if not self.current_onboard_log_contains_message("PL"):
5158
raise NotAchievedException("Did not see expected PL message")
5159
5160
except Exception as e:
5161
self.print_exception_caught(e)
5162
ex = e
5163
self.reboot_sitl()
5164
self.zero_throttle()
5165
self.context_pop()
5166
self.reboot_sitl()
5167
self.progress("All done")
5168
5169
if ex is not None:
5170
raise ex
5171
5172
def Landing(self):
5173
"""Test landing the aircraft."""
5174
5175
def check_landing_speeds(land_speed_high, land_speed_low, land_alt_low, land_speed_high_accuracy=0.1):
5176
self.progress("Checking landing speeds (speed_high=%f speed_low=%f alt_low=%f" %
5177
(land_speed_high, land_speed_low, land_alt_low))
5178
land_high_maintain = 5
5179
land_low_maintain = land_alt_low / land_speed_low / 2
5180
5181
takeoff_alt = (land_high_maintain * land_speed_high + land_alt_low) + 20
5182
# this is pretty rough, but takes *so much longer* in LOITER
5183
self.takeoff(takeoff_alt, mode='STABILIZE', timeout=200, takeoff_throttle=2000)
5184
# check default landing speeds:
5185
self.change_mode('LAND')
5186
# ensure higher-alt descent rate:
5187
self.wait_descent_rate(land_speed_high,
5188
minimum_duration=land_high_maintain,
5189
accuracy=land_speed_high_accuracy)
5190
self.wait_descent_rate(land_speed_low)
5191
# ensure we transition to low descent rate at correct height:
5192
self.assert_altitude(land_alt_low, relative=True)
5193
# now make sure we maintain that descent rate:
5194
self.wait_descent_rate(land_speed_low, minimum_duration=land_low_maintain)
5195
self.wait_disarmed()
5196
5197
# test the defaults. By default LAND_SPD_HIGH_MS is 0 so
5198
# WPNAV_SPEED_DN is used
5199
check_landing_speeds(
5200
self.get_parameter("WPNAV_SPEED_DN") / 100, # cm/s -> m/s
5201
self.get_parameter("LAND_SPD_MS"),
5202
self.get_parameter("LAND_ALT_LOW_M")
5203
)
5204
5205
def test_landing_speeds(land_speed_high, land_speed_low, land_alt_low, **kwargs):
5206
self.set_parameters({
5207
"LAND_SPD_HIGH_MS": land_speed_high,
5208
"LAND_SPD_MS": land_speed_low,
5209
"LAND_ALT_LOW_M": land_alt_low
5210
})
5211
check_landing_speeds(land_speed_high, land_speed_low, land_alt_low, **kwargs)
5212
5213
test_landing_speeds(
5214
5, # descent speed high
5215
1, # descent speed low
5216
30, # transition altitude
5217
land_speed_high_accuracy=0.5
5218
)
5219
5220
def get_system_clock_utc(self, time_seconds):
5221
# this is a copy of ArduPilot's AP_RTC function!
5222
# separate time into ms, sec, min, hour and days but all expressed
5223
# in milliseconds
5224
time_ms = time_seconds * 1000
5225
ms = time_ms % 1000
5226
sec_ms = (time_ms % (60 * 1000)) - ms
5227
min_ms = (time_ms % (60 * 60 * 1000)) - sec_ms - ms
5228
hour_ms = (time_ms % (24 * 60 * 60 * 1000)) - min_ms - sec_ms - ms
5229
5230
# convert times as milliseconds into appropriate units
5231
secs = sec_ms / 1000
5232
mins = min_ms / (60 * 1000)
5233
hours = hour_ms / (60 * 60 * 1000)
5234
return (hours, mins, secs, 0)
5235
5236
def calc_delay(self, seconds, delay_for_seconds):
5237
# delay-for-seconds has to be long enough that we're at the
5238
# waypoint before that time. Otherwise we'll try to wait a
5239
# day....
5240
if delay_for_seconds >= 3600:
5241
raise ValueError("Won't handle large delays")
5242
(hours,
5243
mins,
5244
secs,
5245
ms) = self.get_system_clock_utc(seconds)
5246
self.progress("Now is %uh %um %us" % (hours, mins, secs))
5247
secs += delay_for_seconds # add seventeen seconds
5248
mins += int(secs/60)
5249
secs %= 60
5250
5251
hours += int(mins / 60)
5252
mins %= 60
5253
5254
if hours > 24:
5255
raise ValueError("Way too big a delay")
5256
self.progress("Delay until %uh %um %us" %
5257
(hours, mins, secs))
5258
return (hours, mins, secs, 0)
5259
5260
def reset_delay_item(self, seq, seconds_in_future):
5261
frame = mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT
5262
command = mavutil.mavlink.MAV_CMD_NAV_DELAY
5263
# retrieve mission item and check it:
5264
tried_set = False
5265
hours = None
5266
mins = None
5267
secs = None
5268
while True:
5269
self.progress("Requesting item")
5270
self.mav.mav.mission_request_send(1,
5271
1,
5272
seq)
5273
st = self.mav.recv_match(type='MISSION_ITEM',
5274
blocking=True,
5275
timeout=1)
5276
if st is None:
5277
continue
5278
5279
print("Item: %s" % str(st))
5280
have_match = (tried_set and
5281
st.seq == seq and
5282
st.command == command and
5283
st.param2 == hours and
5284
st.param3 == mins and
5285
st.param4 == secs)
5286
if have_match:
5287
return
5288
5289
self.progress("Mission mismatch")
5290
5291
m = None
5292
tstart = self.get_sim_time()
5293
while True:
5294
if self.get_sim_time_cached() - tstart > 3:
5295
raise NotAchievedException(
5296
"Did not receive MISSION_REQUEST")
5297
self.mav.mav.mission_write_partial_list_send(1,
5298
1,
5299
seq,
5300
seq)
5301
m = self.mav.recv_match(type='MISSION_REQUEST',
5302
blocking=True,
5303
timeout=1)
5304
if m is None:
5305
continue
5306
if m.seq != st.seq:
5307
continue
5308
break
5309
5310
self.progress("Sending absolute-time mission item")
5311
5312
# we have to change out the delay time...
5313
now = self.mav.messages["SYSTEM_TIME"]
5314
if now is None:
5315
raise PreconditionFailedException("Never got SYSTEM_TIME")
5316
if now.time_unix_usec == 0:
5317
raise PreconditionFailedException("system time is zero")
5318
(hours, mins, secs, ms) = self.calc_delay(now.time_unix_usec/1000000, seconds_in_future)
5319
5320
self.mav.mav.mission_item_send(
5321
1, # target system
5322
1, # target component
5323
seq, # seq
5324
frame, # frame
5325
command, # command
5326
0, # current
5327
1, # autocontinue
5328
0, # p1 (relative seconds)
5329
hours, # p2
5330
mins, # p3
5331
secs, # p4
5332
0, # p5
5333
0, # p6
5334
0) # p7
5335
tried_set = True
5336
ack = self.mav.recv_match(type='MISSION_ACK',
5337
blocking=True,
5338
timeout=1)
5339
self.progress("Received ack: %s" % str(ack))
5340
5341
def NavDelayAbsTime(self):
5342
"""fly a simple mission that has a delay in it"""
5343
self.fly_nav_delay_abstime_x(87)
5344
5345
def fly_nav_delay_abstime_x(self, delay_for, expected_delay=None):
5346
"""fly a simple mission that has a delay in it, expect a delay"""
5347
5348
if expected_delay is None:
5349
expected_delay = delay_for
5350
5351
self.load_mission("copter_nav_delay.txt")
5352
5353
self.change_mode("LOITER")
5354
5355
self.wait_ready_to_arm()
5356
5357
delay_item_seq = 3
5358
self.reset_delay_item(delay_item_seq, delay_for)
5359
delay_for_seconds = delay_for
5360
reset_at_m = self.assert_receive_message('SYSTEM_TIME')
5361
reset_at = reset_at_m.time_unix_usec/1000000
5362
5363
self.arm_vehicle()
5364
self.change_mode("AUTO")
5365
self.set_rc(3, 1600)
5366
count_stop = -1
5367
tstart = self.get_sim_time()
5368
while self.armed(): # we RTL at end of mission
5369
now = self.get_sim_time_cached()
5370
if now - tstart > 240:
5371
raise AutoTestTimeoutException("Did not disarm as expected")
5372
m = self.assert_receive_message('MISSION_CURRENT')
5373
at_delay_item = ""
5374
if m.seq == delay_item_seq:
5375
at_delay_item = "(delay item)"
5376
self.progress("MISSION_CURRENT.seq=%u %s" % (m.seq, at_delay_item))
5377
if m.seq > delay_item_seq:
5378
if count_stop == -1:
5379
count_stop_m = self.assert_receive_message('SYSTEM_TIME')
5380
count_stop = count_stop_m.time_unix_usec/1000000
5381
calculated_delay = count_stop - reset_at
5382
error = abs(calculated_delay - expected_delay)
5383
self.progress("Stopped for %u seconds (want >=%u seconds)" %
5384
(calculated_delay, delay_for_seconds))
5385
if error > 2:
5386
raise NotAchievedException("delay outside expectations")
5387
5388
def NavDelayTakeoffAbsTime(self):
5389
"""make sure taking off at a specific time works"""
5390
self.load_mission("copter_nav_delay_takeoff.txt")
5391
5392
self.change_mode("LOITER")
5393
self.wait_ready_to_arm()
5394
5395
delay_item_seq = 2
5396
delay_for_seconds = 77
5397
self.reset_delay_item(delay_item_seq, delay_for_seconds)
5398
reset_at = self.get_sim_time_cached()
5399
5400
self.arm_vehicle()
5401
self.change_mode("AUTO")
5402
5403
self.set_rc(3, 1600)
5404
5405
# should not take off for about least 77 seconds
5406
tstart = self.get_sim_time()
5407
took_off = False
5408
while self.armed():
5409
now = self.get_sim_time_cached()
5410
if now - tstart > 200:
5411
# timeout
5412
break
5413
m = self.assert_receive_message('MISSION_CURRENT')
5414
now = self.get_sim_time_cached()
5415
self.progress("%s" % str(m))
5416
if m.seq > delay_item_seq:
5417
if not took_off:
5418
took_off = True
5419
delta_time = now - reset_at
5420
if abs(delta_time - delay_for_seconds) > 2:
5421
raise NotAchievedException((
5422
"Did not take off on time "
5423
"measured=%f want=%f" %
5424
(delta_time, delay_for_seconds)))
5425
5426
if not took_off:
5427
raise NotAchievedException("Did not take off")
5428
5429
def ModeZigZag(self):
5430
'''test zigzag mode'''
5431
# set channel 8 for zigzag savewp and recentre it
5432
self.set_parameter("RC8_OPTION", 61)
5433
self.set_parameter("LOIT_OPTIONS", 0)
5434
5435
self.takeoff(alt_min=5, mode='LOITER')
5436
5437
ZIGZAG = 24
5438
j = 0
5439
slowdown_speed = 0.3 # because Copter takes a long time to actually stop
5440
self.start_subtest("Conduct ZigZag test for all 4 directions")
5441
while j < 4:
5442
self.progress("## Align heading with the run-way (j=%d)##" % j)
5443
self.set_rc(8, 1500)
5444
self.set_rc(4, 1420)
5445
self.wait_heading(352-j*90)
5446
self.set_rc(4, 1500)
5447
self.change_mode(ZIGZAG)
5448
self.progress("## Record Point A ##")
5449
self.set_rc(8, 1100) # record point A
5450
self.set_rc(1, 1700) # fly side-way for 20m
5451
self.wait_distance(20)
5452
self.set_rc(1, 1500)
5453
self.wait_groundspeed(0, slowdown_speed) # wait until the copter slows down
5454
self.progress("## Record Point A ##")
5455
self.set_rc(8, 1500) # pilot always have to cross mid position when changing for low to high position
5456
self.set_rc(8, 1900) # record point B
5457
5458
i = 1
5459
while i < 2:
5460
self.start_subtest("Run zigzag A->B and B->A (i=%d)" % i)
5461
self.progress("## fly forward for 10 meter ##")
5462
self.set_rc(2, 1300)
5463
self.wait_distance(10)
5464
self.set_rc(2, 1500) # re-centre pitch rc control
5465
self.wait_groundspeed(0, slowdown_speed) # wait until the copter slows down
5466
self.set_rc(8, 1500) # switch to mid position
5467
self.progress("## auto execute vector BA ##")
5468
self.set_rc(8, 1100)
5469
self.wait_distance(17) # wait for it to finish
5470
self.wait_groundspeed(0, slowdown_speed) # wait until the copter slows down
5471
5472
self.progress("## fly forward for 10 meter ##")
5473
self.set_rc(2, 1300) # fly forward for 10 meter
5474
self.wait_distance(10)
5475
self.set_rc(2, 1500) # re-centre pitch rc control
5476
self.wait_groundspeed(0, slowdown_speed) # wait until the copter slows down
5477
self.set_rc(8, 1500) # switch to mid position
5478
self.progress("## auto execute vector AB ##")
5479
self.set_rc(8, 1900)
5480
self.wait_distance(17) # wait for it to finish
5481
self.wait_groundspeed(0, slowdown_speed) # wait until the copter slows down
5482
i = i + 1
5483
# test the case when pilot switch to manual control during the auto flight
5484
self.start_subtest("test the case when pilot switch to manual control during the auto flight")
5485
self.progress("## fly forward for 10 meter ##")
5486
self.set_rc(2, 1300) # fly forward for 10 meter
5487
self.wait_distance(10)
5488
self.set_rc(2, 1500) # re-centre pitch rc control
5489
self.wait_groundspeed(0, 0.3) # wait until the copter slows down
5490
self.set_rc(8, 1500) # switch to mid position
5491
self.progress("## auto execute vector BA ##")
5492
self.set_rc(8, 1100) # switch to low position, auto execute vector BA
5493
self.wait_distance(8) # purposely switch to manual halfway
5494
self.set_rc(8, 1500)
5495
self.wait_groundspeed(0, slowdown_speed) # copter should slow down here
5496
self.progress("## Manual control to fly forward ##")
5497
self.set_rc(2, 1300) # manual control to fly forward
5498
self.wait_distance(8)
5499
self.set_rc(2, 1500) # re-centre pitch rc control
5500
self.wait_groundspeed(0, slowdown_speed) # wait until the copter slows down
5501
self.progress("## continue vector BA ##")
5502
self.set_rc(8, 1100) # copter should continue mission here
5503
self.wait_distance(8) # wait for it to finish rest of BA
5504
self.wait_groundspeed(0, slowdown_speed) # wait until the copter slows down
5505
self.set_rc(8, 1500) # switch to mid position
5506
self.progress("## auto execute vector AB ##")
5507
self.set_rc(8, 1900) # switch to execute AB again
5508
self.wait_distance(17) # wait for it to finish
5509
self.wait_groundspeed(0, slowdown_speed) # wait until the copter slows down
5510
self.change_mode('LOITER')
5511
j = j + 1
5512
5513
self.do_RTL()
5514
5515
def SetModesViaModeSwitch(self):
5516
'''Set modes via modeswitch'''
5517
fltmode_ch = 5
5518
self.set_parameter("FLTMODE_CH", fltmode_ch)
5519
self.set_rc(fltmode_ch, 1000) # PWM for mode1
5520
testmodes = [("FLTMODE1", 4, "GUIDED", 1165),
5521
("FLTMODE2", 2, "ALT_HOLD", 1295),
5522
("FLTMODE3", 6, "RTL", 1425),
5523
("FLTMODE4", 7, "CIRCLE", 1555),
5524
("FLTMODE5", 1, "ACRO", 1685),
5525
("FLTMODE6", 17, "BRAKE", 1815),
5526
]
5527
for mode in testmodes:
5528
(parm, parm_value, name, pwm) = mode
5529
self.set_parameter(parm, parm_value)
5530
5531
for mode in reversed(testmodes):
5532
(parm, parm_value, name, pwm) = mode
5533
self.set_rc(fltmode_ch, pwm)
5534
self.wait_mode(name)
5535
5536
for mode in testmodes:
5537
(parm, parm_value, name, pwm) = mode
5538
self.set_rc(fltmode_ch, pwm)
5539
self.wait_mode(name)
5540
5541
for mode in reversed(testmodes):
5542
(parm, parm_value, name, pwm) = mode
5543
self.set_rc(fltmode_ch, pwm)
5544
self.wait_mode(name)
5545
5546
def SetModesViaAuxSwitch(self):
5547
'''"Set modes via auxswitch"'''
5548
fltmode_ch = int(self.get_parameter("FLTMODE_CH"))
5549
self.set_rc(fltmode_ch, 1000)
5550
self.wait_mode("CIRCLE")
5551
self.set_rc(9, 1000)
5552
self.set_rc(10, 1000)
5553
self.set_parameters({
5554
"RC9_OPTION": 18, # land
5555
"RC10_OPTION": 55, # guided
5556
})
5557
self.set_rc(9, 1900)
5558
self.wait_mode("LAND")
5559
self.set_rc(10, 1900)
5560
self.wait_mode("GUIDED")
5561
self.set_rc(10, 1000) # this re-polls the mode switch
5562
self.wait_mode("CIRCLE")
5563
5564
def fly_guided_stop(self,
5565
timeout=20,
5566
groundspeed_tolerance=0.05,
5567
climb_tolerance=0.01):
5568
"""stop the vehicle moving in guided mode"""
5569
self.progress("Stopping vehicle")
5570
tstart = self.get_sim_time()
5571
# send a position-control command
5572
self.mav.mav.set_position_target_local_ned_send(
5573
0, # timestamp
5574
1, # target system_id
5575
1, # target component id
5576
mavutil.mavlink.MAV_FRAME_BODY_NED,
5577
MAV_POS_TARGET_TYPE_MASK.POS_ONLY | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE, # mask specifying use-only-x-y-z
5578
0, # x
5579
0, # y
5580
0, # z
5581
0, # vx
5582
0, # vy
5583
0, # vz
5584
0, # afx
5585
0, # afy
5586
0, # afz
5587
0, # yaw
5588
0, # yawrate
5589
)
5590
while True:
5591
if self.get_sim_time_cached() - tstart > timeout:
5592
raise NotAchievedException("Vehicle did not stop")
5593
m = self.assert_receive_message('VFR_HUD')
5594
print("%s" % str(m))
5595
if (m.groundspeed < groundspeed_tolerance and
5596
m.climb < climb_tolerance):
5597
break
5598
5599
def send_set_position_target_global_int(self, lat, lon, alt):
5600
self.mav.mav.set_position_target_global_int_send(
5601
0, # timestamp
5602
1, # target system_id
5603
1, # target component id
5604
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
5605
MAV_POS_TARGET_TYPE_MASK.POS_ONLY, # mask specifying use-only-lat-lon-alt
5606
lat, # lat
5607
lon, # lon
5608
alt, # alt
5609
0, # vx
5610
0, # vy
5611
0, # vz
5612
0, # afx
5613
0, # afy
5614
0, # afz
5615
0, # yaw
5616
0, # yawrate
5617
)
5618
5619
def fly_guided_move_global_relative_alt(self, lat, lon, alt):
5620
startpos = self.assert_receive_message('GLOBAL_POSITION_INT')
5621
5622
self.send_set_position_target_global_int(lat, lon, alt)
5623
5624
tstart = self.get_sim_time()
5625
while True:
5626
if self.get_sim_time_cached() - tstart > 200:
5627
raise NotAchievedException("Did not move far enough")
5628
# send a position-control command
5629
pos = self.assert_receive_message('GLOBAL_POSITION_INT')
5630
delta = self.get_distance_int(startpos, pos)
5631
self.progress("delta=%f (want >10)" % delta)
5632
if delta > 10:
5633
break
5634
5635
def fly_guided_move_local(self, x, y, z_up, timeout=100):
5636
"""move the vehicle using MAVLINK_MSG_ID_SET_POSITION_TARGET_LOCAL_NED"""
5637
startpos = self.assert_receive_message('LOCAL_POSITION_NED')
5638
self.progress("startpos=%s" % str(startpos))
5639
5640
tstart = self.get_sim_time()
5641
# send a position-control command
5642
self.mav.mav.set_position_target_local_ned_send(
5643
0, # timestamp
5644
1, # target system_id
5645
1, # target component id
5646
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
5647
MAV_POS_TARGET_TYPE_MASK.POS_ONLY | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE, # mask specifying use-only-x-y-z
5648
x, # x
5649
y, # y
5650
-z_up, # z
5651
0, # vx
5652
0, # vy
5653
0, # vz
5654
0, # afx
5655
0, # afy
5656
0, # afz
5657
0, # yaw
5658
0, # yawrate
5659
)
5660
while True:
5661
if self.get_sim_time_cached() - tstart > timeout:
5662
raise NotAchievedException("Did not reach destination")
5663
dist = self.distance_to_local_position((x, y, -z_up))
5664
if dist < 1:
5665
self.progress(f"Reach distance ({dist})")
5666
self.progress("endpos=%s" % str(startpos))
5667
break
5668
5669
def test_guided_local_position_target(self, x, y, z_up):
5670
""" Check target position being received by vehicle """
5671
# set POSITION_TARGET_LOCAL_NED message rate using SET_MESSAGE_INTERVAL
5672
self.progress("Setting local target in NED: (%f, %f, %f)" % (x, y, -z_up))
5673
self.progress("Setting rate to 1 Hz")
5674
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_POSITION_TARGET_LOCAL_NED, 1)
5675
5676
# mask specifying use only xyz
5677
target_typemask = MAV_POS_TARGET_TYPE_MASK.POS_ONLY
5678
5679
# set position target
5680
self.mav.mav.set_position_target_local_ned_send(
5681
0, # timestamp
5682
1, # target system_id
5683
1, # target component id
5684
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
5685
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
5686
x, # x
5687
y, # y
5688
-z_up, # z
5689
0, # vx
5690
0, # vy
5691
0, # vz
5692
0, # afx
5693
0, # afy
5694
0, # afz
5695
0, # yaw
5696
0, # yawrate
5697
)
5698
m = self.assert_receive_message('POSITION_TARGET_LOCAL_NED', timeout=2)
5699
self.progress("Received local target: %s" % str(m))
5700
5701
if not (m.type_mask == (target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE) or m.type_mask == target_typemask):
5702
raise NotAchievedException("Did not receive proper mask: expected=%u or %u, got=%u" %
5703
((target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE), target_typemask, m.type_mask))
5704
5705
if x - m.x > 0.1:
5706
raise NotAchievedException("Did not receive proper target position x: wanted=%f got=%f" % (x, m.x))
5707
5708
if y - m.y > 0.1:
5709
raise NotAchievedException("Did not receive proper target position y: wanted=%f got=%f" % (y, m.y))
5710
5711
if z_up - (-m.z) > 0.1:
5712
raise NotAchievedException("Did not receive proper target position z: wanted=%f got=%f" % (z_up, -m.z))
5713
5714
def test_guided_local_velocity_target(self, vx, vy, vz_up, timeout=3):
5715
" Check local target velocity being received by vehicle "
5716
self.progress("Setting local NED velocity target: (%f, %f, %f)" % (vx, vy, -vz_up))
5717
self.progress("Setting POSITION_TARGET_LOCAL_NED message rate to 10Hz")
5718
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_POSITION_TARGET_LOCAL_NED, 10)
5719
5720
# mask specifying use only vx,vy,vz & accel. Even though we don't test acceltargets below currently
5721
# a velocity only mask returns a velocity & accel mask
5722
target_typemask = (MAV_POS_TARGET_TYPE_MASK.POS_IGNORE |
5723
MAV_POS_TARGET_TYPE_MASK.YAW_IGNORE | MAV_POS_TARGET_TYPE_MASK.YAW_RATE_IGNORE)
5724
5725
# Drain old messages and ignore the ramp-up to the required target velocity
5726
tstart = self.get_sim_time()
5727
while self.get_sim_time_cached() - tstart < timeout:
5728
# send velocity-control command
5729
self.mav.mav.set_position_target_local_ned_send(
5730
0, # timestamp
5731
1, # target system_id
5732
1, # target component id
5733
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
5734
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
5735
0, # x
5736
0, # y
5737
0, # z
5738
vx, # vx
5739
vy, # vy
5740
-vz_up, # vz
5741
0, # afx
5742
0, # afy
5743
0, # afz
5744
0, # yaw
5745
0, # yawrate
5746
)
5747
m = self.assert_receive_message('POSITION_TARGET_LOCAL_NED')
5748
5749
self.progress("Received local target: %s" % str(m))
5750
5751
# Check the last received message
5752
if not (m.type_mask == (target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE) or m.type_mask == target_typemask):
5753
raise NotAchievedException("Did not receive proper mask: expected=%u or %u, got=%u" %
5754
((target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE), target_typemask, m.type_mask))
5755
5756
if vx - m.vx > 0.1:
5757
raise NotAchievedException("Did not receive proper target velocity vx: wanted=%f got=%f" % (vx, m.vx))
5758
5759
if vy - m.vy > 0.1:
5760
raise NotAchievedException("Did not receive proper target velocity vy: wanted=%f got=%f" % (vy, m.vy))
5761
5762
if vz_up - (-m.vz) > 0.1:
5763
raise NotAchievedException("Did not receive proper target velocity vz: wanted=%f got=%f" % (vz_up, -m.vz))
5764
5765
self.progress("Received proper target velocity commands")
5766
5767
def wait_for_local_velocity(self, vx, vy, vz_up, timeout=10):
5768
""" Wait for local target velocity"""
5769
5770
# debug messages
5771
self.progress("Waiting for local NED velocity target: (%f, %f, %f)" % (vx, vy, -vz_up))
5772
self.progress("Setting LOCAL_POSITION_NED message rate to 10Hz")
5773
5774
# set position local ned message stream rate
5775
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_LOCAL_POSITION_NED, 10)
5776
5777
# wait for position local ned message
5778
tstart = self.get_sim_time()
5779
while self.get_sim_time_cached() - tstart < timeout:
5780
5781
# get position target local ned message
5782
m = self.mav.recv_match(type="LOCAL_POSITION_NED", blocking=True, timeout=1)
5783
5784
# could not be able to get a valid target local ned message within given time
5785
if m is None:
5786
5787
# raise an error that did not receive a valid target local ned message within given time
5788
raise NotAchievedException("Did not receive any position local ned message for 1 second!")
5789
5790
# got a valid target local ned message within given time
5791
else:
5792
5793
# debug message
5794
self.progress("Received local position ned message: %s" % str(m))
5795
5796
# check if velocity values are in range
5797
if vx - m.vx <= 0.1 and vy - m.vy <= 0.1 and vz_up - (-m.vz) <= 0.1:
5798
5799
# get out of function
5800
self.progress("Vehicle successfully reached to target velocity!")
5801
return
5802
5803
# raise an exception
5804
error_message = "Did not receive target velocities vx, vy, vz_up, wanted=(%f, %f, %f) got=(%f, %f, %f)"
5805
error_message = error_message % (vx, vy, vz_up, m.vx, m.vy, -m.vz)
5806
raise NotAchievedException(error_message)
5807
5808
def test_position_target_message_mode(self):
5809
" Ensure that POSITION_TARGET_LOCAL_NED messages are sent in Guided Mode only "
5810
self.hover()
5811
self.change_mode('LOITER')
5812
self.progress("Setting POSITION_TARGET_LOCAL_NED message rate to 10Hz")
5813
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_POSITION_TARGET_LOCAL_NED, 10)
5814
5815
self.assert_not_receiving_message('POSITION_TARGET_LOCAL_NED')
5816
5817
self.progress("Did not receive any POSITION_TARGET_LOCAL_NED message in LOITER mode. Success")
5818
5819
def earth_to_body(self, vector):
5820
r = mavextra.rotation(self.mav.messages["ATTITUDE"]).invert()
5821
# print("r=%s" % str(r))
5822
return r * vector
5823
5824
def precision_loiter_to_pos(self, x, y, z, send_bf=True, timeout=40):
5825
'''send landing_target messages at vehicle until it arrives at
5826
location to x, y, z from origin (in metres), z is *up*
5827
if send_bf is True then targets sent in body-frame, if False then sent in local FRD frame'''
5828
dest_ned = rotmat.Vector3(x, y, -z)
5829
tstart = self.get_sim_time()
5830
success_start = -1
5831
while True:
5832
now = self.get_sim_time_cached()
5833
if now - tstart > timeout:
5834
raise NotAchievedException("Did not loiter to position!")
5835
m_pos = self.assert_receive_message('LOCAL_POSITION_NED')
5836
pos_ned = rotmat.Vector3(m_pos.x, m_pos.y, m_pos.z)
5837
# print("dest_ned=%s" % str(dest_ned))
5838
# print("pos_ned=%s" % str(pos_ned))
5839
delta_ef = dest_ned - pos_ned
5840
# print("delta_ef=%s" % str(delta_ef))
5841
5842
# determine if we've successfully navigated to close to
5843
# where we should be:
5844
dist = math.sqrt(delta_ef.x * delta_ef.x + delta_ef.y * delta_ef.y)
5845
dist_max = 1
5846
self.progress("dist=%f want <%f" % (dist, dist_max))
5847
if dist < dist_max:
5848
# success! We've gotten within our target distance
5849
if success_start == -1:
5850
success_start = now
5851
elif now - success_start > 10:
5852
self.progress("Yay!")
5853
break
5854
else:
5855
success_start = -1
5856
5857
mav_frame = None
5858
if send_bf:
5859
# rotate delta_ef from NED to body frame FRD
5860
delta_bf = self.earth_to_body(delta_ef)
5861
print("delta_bf=%s" % str(delta_bf))
5862
angle_x = math.atan2(delta_bf.y, delta_bf.z)
5863
angle_y = -math.atan2(delta_bf.x, delta_bf.z)
5864
distance = math.sqrt(delta_bf.x * delta_bf.x +
5865
delta_bf.y * delta_bf.y +
5866
delta_bf.z * delta_bf.z)
5867
mav_frame = mavutil.mavlink.MAV_FRAME_BODY_FRD
5868
else:
5869
# rotate delta_ef from NED to Local FRD
5870
rotmat_yaw = Matrix3()
5871
rotmat_yaw.rotate_yaw(self.mav.messages["ATTITUDE"].yaw)
5872
delta_local_frd = rotmat_yaw * delta_ef
5873
angle_x = math.atan2(delta_local_frd.y, delta_local_frd.z)
5874
angle_y = -math.atan2(delta_local_frd.x, delta_local_frd.z)
5875
distance = math.sqrt(delta_local_frd.x * delta_local_frd.x +
5876
delta_local_frd.y * delta_local_frd.y +
5877
delta_local_frd.z * delta_local_frd.z)
5878
mav_frame = mavutil.mavlink.MAV_FRAME_LOCAL_FRD
5879
5880
# att = self.mav.messages["ATTITUDE"]
5881
# print("r=%f p=%f y=%f" % (math.degrees(att.roll), math.degrees(att.pitch), math.degrees(att.yaw)))
5882
# print("angle_x=%s angle_y=%s" % (str(math.degrees(angle_x)), str(math.degrees(angle_y))))
5883
# print("distance=%s" % str(distance))
5884
5885
self.mav.mav.landing_target_send(
5886
0, # time_usec
5887
1, # target_num
5888
mav_frame, # frame
5889
angle_x, # angle x (radians)
5890
angle_y, # angle y (radians)
5891
distance, # distance to target
5892
0.01, # size of target in radians, X-axis
5893
0.01 # size of target in radians, Y-axis
5894
)
5895
5896
def set_servo_gripper_parameters(self):
5897
self.set_parameters({
5898
"GRIP_ENABLE": 1,
5899
"GRIP_TYPE": 1,
5900
"SIM_GRPS_ENABLE": 1,
5901
"SIM_GRPS_PIN": 8,
5902
"SERVO8_FUNCTION": 28,
5903
})
5904
5905
def PayloadPlaceMission(self):
5906
"""Test payload placing in auto."""
5907
self.set_analog_rangefinder_parameters()
5908
self.set_servo_gripper_parameters()
5909
self.reboot_sitl()
5910
5911
num_wp = self.load_and_start_mission("copter_payload_place.txt")
5912
5913
self.wait_text("Gripper load releas(ed|ing)", timeout=90, regex=True)
5914
dist_limit = 1
5915
# this is a copy of the point in the mission file:
5916
target_loc = mavutil.location(-35.363106,
5917
149.165436,
5918
0,
5919
0)
5920
dist = self.get_distance(target_loc, self.mav.location())
5921
self.progress("dist=%f" % (dist,))
5922
if dist > dist_limit:
5923
raise NotAchievedException("Did not honour target lat/lng (dist=%f want <%f" %
5924
(dist, dist_limit))
5925
5926
self.wait_waypoint(num_wp-1, num_wp-1)
5927
self.do_RTL()
5928
5929
def PayloadPlaceMissionOpenGripper(self):
5930
'''test running the mission when the gripper is open'''
5931
self.set_analog_rangefinder_parameters()
5932
self.set_servo_gripper_parameters()
5933
self.reboot_sitl()
5934
5935
self.load_mission("copter_payload_place.txt")
5936
5937
self.set_parameter("AUTO_OPTIONS", 3)
5938
self.change_mode('AUTO')
5939
self.wait_ready_to_arm()
5940
5941
self.arm_vehicle()
5942
5943
self.progress("Opening the gripper before time")
5944
self.run_auxfunc(19, 0)
5945
5946
self.wait_text("Abort: Gripper Open", timeout=90)
5947
5948
self.wait_disarmed()
5949
5950
def Weathervane(self):
5951
'''Test copter weathervaning'''
5952
# We test nose into wind code paths and yaw direction here and test side into wind
5953
# yaw direction in QuadPlane tests to reduce repetition.
5954
self.set_parameters({
5955
"SIM_WIND_SPD": 10,
5956
"SIM_WIND_DIR": 100,
5957
"GUID_OPTIONS": 129, # allow weathervaning and arming from tx in guided
5958
"AUTO_OPTIONS": 131, # allow arming in auto, take off without raising the stick, and weathervaning
5959
"WVANE_ENABLE": 1,
5960
"WVANE_GAIN": 3,
5961
"WVANE_VELZ_MAX": 1,
5962
"WVANE_SPD_MAX": 2
5963
})
5964
5965
self.progress("Test weathervaning in auto")
5966
self.load_mission("weathervane_mission.txt", strict=False)
5967
5968
self.change_mode("AUTO")
5969
self.wait_ready_to_arm()
5970
self.arm_vehicle()
5971
5972
self.wait_statustext("Weathervane Active", timeout=60)
5973
self.do_RTL()
5974
self.wait_disarmed()
5975
self.change_mode("GUIDED")
5976
5977
# After take off command in guided we enter the velaccl sub mode
5978
self.progress("Test weathervaning in guided vel-accel")
5979
self.set_rc(3, 1000)
5980
self.wait_ready_to_arm()
5981
5982
self.arm_vehicle()
5983
self.user_takeoff(alt_min=15)
5984
# Wait for heading to match wind direction.
5985
self.wait_heading(100, accuracy=8, timeout=100)
5986
5987
self.progress("Test weathervaning in guided pos only")
5988
# Travel directly north to align heading north and build some airspeed.
5989
self.fly_guided_move_local(x=40, y=0, z_up=15)
5990
# Wait for heading to match wind direction.
5991
self.wait_heading(100, accuracy=8, timeout=100)
5992
self.do_RTL()
5993
5994
def _DO_WINCH(self, command):
5995
self.context_push()
5996
self.load_default_params_file("copter-winch.parm")
5997
self.reboot_sitl()
5998
self.wait_ready_to_arm()
5999
6000
self.start_subtest("starts relaxed")
6001
self.wait_servo_channel_value(9, 0)
6002
6003
self.start_subtest("rate control")
6004
command(
6005
mavutil.mavlink.MAV_CMD_DO_WINCH,
6006
p1=1, # instance number
6007
p2=mavutil.mavlink.WINCH_RATE_CONTROL, # command
6008
p3=0, # length to release
6009
p4=1, # rate in m/s
6010
)
6011
self.wait_servo_channel_value(9, 1900)
6012
6013
self.start_subtest("relax")
6014
command(
6015
mavutil.mavlink.MAV_CMD_DO_WINCH,
6016
p1=1, # instance number
6017
p2=mavutil.mavlink.WINCH_RELAXED, # command
6018
p3=0, # length to release
6019
p4=1, # rate in m/s
6020
)
6021
self.wait_servo_channel_value(9, 0)
6022
6023
self.start_subtest("hold but zero output")
6024
command(
6025
mavutil.mavlink.MAV_CMD_DO_WINCH,
6026
p1=1, # instance number
6027
p2=mavutil.mavlink.WINCH_RATE_CONTROL, # command
6028
p3=0, # length to release
6029
p4=0, # rate in m/s
6030
)
6031
self.wait_servo_channel_value(9, 1500)
6032
6033
self.start_subtest("relax")
6034
command(
6035
mavutil.mavlink.MAV_CMD_DO_WINCH,
6036
p1=1, # instance number
6037
p2=mavutil.mavlink.WINCH_RELAXED, # command
6038
p3=0, # length to release
6039
p4=1, # rate in m/s
6040
)
6041
self.wait_servo_channel_value(9, 0)
6042
6043
self.start_subtest("position")
6044
command(
6045
mavutil.mavlink.MAV_CMD_DO_WINCH,
6046
p1=1, # instance number
6047
p2=mavutil.mavlink.WINCH_RELATIVE_LENGTH_CONTROL, # command
6048
p3=2, # length to release
6049
p4=1, # rate in m/s
6050
)
6051
self.wait_servo_channel_value(9, 1900)
6052
self.wait_servo_channel_value(9, 1500, timeout=60)
6053
6054
self.context_pop()
6055
self.reboot_sitl()
6056
6057
def DO_WINCH(self):
6058
'''test mavlink DO_WINCH command'''
6059
self._DO_WINCH(self.run_cmd_int)
6060
self._DO_WINCH(self.run_cmd)
6061
6062
def GuidedSubModeChange(self):
6063
""""Ensure we can move around in guided after a takeoff command."""
6064
6065
'''start by disabling GCS failsafe, otherwise we immediately disarm
6066
due to (apparently) not receiving traffic from the GCS for
6067
too long. This is probably a function of --speedup'''
6068
self.set_parameters({
6069
"FS_GCS_ENABLE": 0,
6070
"DISARM_DELAY": 0, # until traffic problems are fixed
6071
})
6072
self.change_mode("GUIDED")
6073
self.wait_ready_to_arm()
6074
self.arm_vehicle()
6075
6076
self.user_takeoff(alt_min=10)
6077
6078
self.start_subtest("yaw through absolute angles using MAV_CMD_CONDITION_YAW")
6079
self.guided_achieve_heading(45)
6080
self.guided_achieve_heading(135)
6081
6082
self.start_subtest("move the vehicle using set_position_target_global_int")
6083
# the following numbers are 5-degree-latitude and 5-degrees
6084
# longitude - just so that we start to really move a lot.
6085
self.fly_guided_move_global_relative_alt(5, 5, 10)
6086
6087
self.start_subtest("move the vehicle using MAVLINK_MSG_ID_SET_POSITION_TARGET_LOCAL_NED")
6088
self.fly_guided_stop(groundspeed_tolerance=0.1)
6089
self.fly_guided_move_local(5, 5, 10)
6090
6091
self.start_subtest("Checking that WP_YAW_BEHAVIOUR 0 works")
6092
self.set_parameter('WP_YAW_BEHAVIOR', 0)
6093
self.delay_sim_time(2)
6094
orig_heading = self.get_heading()
6095
self.fly_guided_move_local(5, 0, 10)
6096
# ensure our heading hasn't changed:
6097
self.assert_heading(orig_heading, accuracy=2)
6098
self.fly_guided_move_local(0, 5, 10)
6099
# ensure our heading hasn't changed:
6100
self.assert_heading(orig_heading, accuracy=2)
6101
6102
self.start_subtest("Check target position received by vehicle using SET_MESSAGE_INTERVAL")
6103
self.test_guided_local_position_target(5, 5, 10)
6104
self.test_guided_local_velocity_target(2, 2, 1)
6105
self.test_position_target_message_mode()
6106
6107
self.do_RTL()
6108
6109
def WPYawBehaviour1RTL(self):
6110
'''ensure behaviour 1 (face home) works in RTL'''
6111
self.start_subtest("moving off in guided mode and checking return yaw")
6112
self.change_mode('GUIDED')
6113
self.wait_ready_to_arm()
6114
self.wait_heading(272, timeout=1) # verify initial heading"
6115
self.takeoff(2, mode='GUIDED')
6116
self.set_parameter('WP_YAW_BEHAVIOR', 1) # 1 is face next waypoint
6117
self.fly_guided_move_local(100, 100, z_up=20)
6118
self.wait_heading(45, timeout=1)
6119
self.change_mode('RTL')
6120
self.wait_heading(225, minimum_duration=10, timeout=20)
6121
self.wait_disarmed()
6122
6123
self.reboot_sitl()
6124
self.start_subtest("now the same but in auto mode")
6125
self.upload_simple_relhome_mission([
6126
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 2),
6127
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 100, 100, 20),
6128
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
6129
])
6130
self.set_parameter('AUTO_OPTIONS', 3)
6131
self.change_mode('AUTO')
6132
self.set_rc(3, 1000)
6133
self.wait_ready_to_arm()
6134
self.wait_heading(273, timeout=1) # verify initial heading"
6135
self.arm_vehicle()
6136
self.wait_heading(45, minimum_duration=10, timeout=20)
6137
self.wait_current_waypoint(3)
6138
self.wait_heading(225, minimum_duration=10, timeout=20)
6139
self.wait_disarmed()
6140
6141
def TestGripperMission(self):
6142
'''Test Gripper mission items'''
6143
num_wp = self.load_mission("copter-gripper-mission.txt")
6144
self.change_mode('LOITER')
6145
self.wait_ready_to_arm()
6146
self.assert_vehicle_location_is_at_startup_location()
6147
self.arm_vehicle()
6148
self.change_mode('AUTO')
6149
self.set_rc(3, 1500)
6150
self.wait_statustext("Gripper Grabbed", timeout=60)
6151
self.wait_statustext("Gripper Released", timeout=60)
6152
self.wait_waypoint(num_wp-1, num_wp-1)
6153
self.wait_disarmed()
6154
6155
def SplineLastWaypoint(self):
6156
'''Test Spline as last waypoint'''
6157
self.load_mission("copter-spline-last-waypoint.txt")
6158
self.change_mode('LOITER')
6159
self.wait_ready_to_arm()
6160
self.arm_vehicle()
6161
self.change_mode('AUTO')
6162
self.set_rc(3, 1500)
6163
self.wait_altitude(10, 3000, relative=True)
6164
self.do_RTL()
6165
6166
def ManualThrottleModeChange(self):
6167
'''Check manual throttle mode changes denied on high throttle'''
6168
self.set_parameter("FS_GCS_ENABLE", 0) # avoid GUIDED instant disarm
6169
self.change_mode("STABILIZE")
6170
self.wait_ready_to_arm()
6171
self.arm_vehicle()
6172
self.change_mode("ACRO")
6173
self.change_mode("STABILIZE")
6174
self.change_mode("GUIDED")
6175
self.set_rc(3, 1700)
6176
self.watch_altitude_maintained(altitude_min=-1, altitude_max=0.2) # should not take off in guided
6177
self.run_cmd_do_set_mode(
6178
"ACRO",
6179
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
6180
self.run_cmd_do_set_mode(
6181
"STABILIZE",
6182
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
6183
self.run_cmd_do_set_mode(
6184
"DRIFT",
6185
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
6186
self.progress("Check setting an invalid mode")
6187
self.run_cmd(
6188
mavutil.mavlink.MAV_CMD_DO_SET_MODE,
6189
p1=mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED,
6190
p2=126,
6191
want_result=mavutil.mavlink.MAV_RESULT_FAILED,
6192
timeout=1,
6193
)
6194
self.set_rc(3, 1000)
6195
self.run_cmd_do_set_mode("ACRO")
6196
self.wait_disarmed()
6197
6198
def constrained_mount_pitch(self, pitch_angle_deg, mount_instance=1):
6199
PITCH_MIN = self.get_parameter("MNT%u_PITCH_MIN" % mount_instance)
6200
PITCH_MAX = self.get_parameter("MNT%u_PITCH_MAX" % mount_instance)
6201
return min(max(pitch_angle_deg, PITCH_MIN), PITCH_MAX)
6202
6203
def test_mount_pitch(self, despitch, despitch_tolerance, mount_mode, timeout=10, hold=0, constrained=True):
6204
self.set_mount_mode(mount_mode)
6205
tstart = self.get_sim_time()
6206
success_start = 0
6207
6208
while True:
6209
now = self.get_sim_time_cached()
6210
if now - tstart > timeout:
6211
raise NotAchievedException("Mount pitch not achieved")
6212
6213
# We expect to achieve the desired pitch angle unless constrained by mount limits
6214
if constrained:
6215
despitch = self.constrained_mount_pitch(despitch)
6216
6217
'''retrieve latest angles from GIMBAL_DEVICE_ATTITUDE_STATUS'''
6218
mount_roll, mount_pitch, mount_yaw, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
6219
6220
# self.progress("despitch=%f roll=%f pitch=%f yaw=%f" % (despitch, mount_roll, mount_pitch, mount_yaw))
6221
if abs(despitch - mount_pitch) > despitch_tolerance:
6222
self.progress("Mount pitch incorrect: got=%f want=%f (+/- %f)" %
6223
(mount_pitch, despitch, despitch_tolerance))
6224
success_start = 0
6225
continue
6226
self.progress("Mount pitch correct: %f degrees == %f" %
6227
(mount_pitch, despitch))
6228
if success_start == 0:
6229
success_start = now
6230
if now - success_start >= hold:
6231
self.progress("Mount pitch achieved")
6232
return
6233
6234
def do_pitch(self, pitch):
6235
'''pitch aircraft in guided/angle mode'''
6236
self.mav.mav.set_attitude_target_send(
6237
0, # time_boot_ms
6238
1, # target sysid
6239
1, # target compid
6240
0, # bitmask of things to ignore
6241
mavextra.euler_to_quat([0, math.radians(pitch), 0]), # att
6242
0, # roll rate (rad/s)
6243
0, # pitch rate (rad/s)
6244
0, # yaw rate (rad/s)
6245
0.5) # thrust, 0 to 1, translated to a climb/descent rate
6246
6247
def do_yaw_rate(self, yaw_rate):
6248
'''yaw aircraft in guided/rate mode'''
6249
self.run_cmd(
6250
mavutil.mavlink.MAV_CMD_CONDITION_YAW,
6251
p1=60, # target angle
6252
p2=0, # degrees/second
6253
p3=1, # -1 is counter-clockwise, 1 clockwise
6254
p4=1, # 1 for relative, 0 for absolute
6255
quiet=True,
6256
)
6257
6258
def get_mount_roll_pitch_yaw_deg(self):
6259
'''return mount (aka gimbal) roll, pitch and yaw angles in degrees'''
6260
# wait for gimbal attitude message
6261
m = self.assert_receive_message('GIMBAL_DEVICE_ATTITUDE_STATUS', timeout=5)
6262
6263
yaw_is_absolute = m.flags & mavutil.mavlink.GIMBAL_DEVICE_FLAGS_YAW_LOCK
6264
# convert quaternion to euler angles and return
6265
q = quaternion.Quaternion(m.q)
6266
euler = q.euler
6267
return math.degrees(euler[0]), math.degrees(euler[1]), math.degrees(euler[2]), yaw_is_absolute
6268
6269
def set_mount_mode(self, mount_mode):
6270
'''set mount mode'''
6271
self.run_cmd_int(
6272
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONFIGURE,
6273
p1=mount_mode,
6274
p2=0, # stabilize roll (unsupported)
6275
p3=0, # stabilize pitch (unsupported)
6276
)
6277
self.run_cmd(
6278
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONFIGURE,
6279
p1=mount_mode,
6280
p2=0, # stabilize roll (unsupported)
6281
p3=0, # stabilize pitch (unsupported)
6282
)
6283
6284
def test_mount_rc_targetting(self, pitch_rc_neutral=1500, do_rate_tests=True):
6285
'''called in multipleplaces to make sure that mount RC targeting works'''
6286
if True:
6287
self.context_push()
6288
self.set_parameters({
6289
'RC6_OPTION': 0,
6290
'RC11_OPTION': 212, # MOUNT1_ROLL
6291
'RC12_OPTION': 213, # MOUNT1_PITCH
6292
'RC13_OPTION': 214, # MOUNT1_YAW
6293
'RC12_MIN': 1100,
6294
'RC12_MAX': 1900,
6295
'RC12_TRIM': 1500,
6296
'MNT1_PITCH_MIN': -45,
6297
'MNT1_PITCH_MAX': 45,
6298
})
6299
self.progress("Testing RC angular control")
6300
# default RC min=1100 max=1900
6301
self.set_rc_from_map({
6302
11: 1500,
6303
12: 1500,
6304
13: 1500,
6305
})
6306
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6307
self.progress("Testing RC input down 1/4 of its range in the output, should be down 1/4 range in output")
6308
rc12_in = 1400
6309
rc12_min = 1100 # default
6310
rc12_max = 1900 # default
6311
mpitch_min = -45.0
6312
mpitch_max = 45.0
6313
expected_pitch = (float(rc12_in-rc12_min)/float(rc12_max-rc12_min) * (mpitch_max-mpitch_min)) + mpitch_min
6314
self.progress("expected mount pitch: %f" % expected_pitch)
6315
if expected_pitch != -11.25:
6316
raise NotAchievedException("Calculation wrong - defaults changed?!")
6317
self.set_rc(12, rc12_in)
6318
self.test_mount_pitch(-11.25, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6319
self.set_rc(12, 1800)
6320
self.test_mount_pitch(33.75, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6321
self.set_rc_from_map({
6322
11: 1500,
6323
12: 1500,
6324
13: 1500,
6325
})
6326
6327
try:
6328
self.context_push()
6329
self.set_parameters({
6330
"RC12_MIN": 1000,
6331
"RC12_MAX": 2000,
6332
"MNT1_PITCH_MIN": -90,
6333
"MNT1_PITCH_MAX": 10,
6334
})
6335
self.set_rc(12, 1000)
6336
self.test_mount_pitch(-90.00, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6337
self.set_rc(12, 2000)
6338
self.test_mount_pitch(10.00, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6339
self.set_rc(12, 1500)
6340
self.test_mount_pitch(-40.00, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6341
finally:
6342
self.context_pop()
6343
6344
self.set_rc(12, 1500)
6345
6346
if do_rate_tests:
6347
self.test_mount_rc_targetting_rate_control()
6348
6349
self.context_pop()
6350
6351
def test_mount_rc_targetting_rate_control(self, pitch_rc_neutral=1500):
6352
if True:
6353
self.progress("Testing RC rate control")
6354
self.set_parameter('MNT1_RC_RATE', 10)
6355
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6356
# Note that we don't constrain the desired angle in the following so that we don't
6357
# timeout due to fetching Mount pitch limit params.
6358
self.set_rc(12, 1300)
6359
self.test_mount_pitch(-5, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6360
self.test_mount_pitch(-10, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6361
self.test_mount_pitch(-15, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6362
self.test_mount_pitch(-20, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6363
self.set_rc(12, 1700)
6364
self.test_mount_pitch(-15, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6365
self.test_mount_pitch(-10, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6366
self.test_mount_pitch(-5, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6367
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6368
self.test_mount_pitch(5, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
6369
6370
self.progress("Reverting to angle mode")
6371
self.set_parameter('MNT1_RC_RATE', 0)
6372
self.set_rc(12, 1500)
6373
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6374
6375
def mount_test_body(self, pitch_rc_neutral=1500, do_rate_tests=True, constrain_sysid_target=True):
6376
'''Test Camera/Antenna Mount - assumes a camera is set up and ready to go'''
6377
if True:
6378
# make sure we're getting gimbal device attitude status
6379
self.assert_receive_message('GIMBAL_DEVICE_ATTITUDE_STATUS', timeout=5, very_verbose=True)
6380
6381
# change mount to neutral mode (point forward, not stabilising)
6382
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
6383
6384
# test pitch is not neutral to start with
6385
mount_roll_deg, mount_pitch_deg, mount_yaw_deg, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
6386
if mount_roll_deg != 0 or mount_pitch_deg != 0 or mount_yaw_deg != 0:
6387
raise NotAchievedException("Mount not neutral")
6388
6389
self.takeoff(30, mode='GUIDED')
6390
6391
# pitch vehicle back and confirm gimbal is still not stabilising
6392
despitch = 10
6393
despitch_tolerance = 3
6394
6395
self.progress("Pitching vehicle")
6396
self.do_pitch(despitch) # will time out!
6397
6398
self.wait_pitch(despitch, despitch_tolerance)
6399
6400
# check gimbal is still not stabilising
6401
mount_roll_deg, mount_pitch_deg, mount_yaw_deg, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
6402
if mount_roll_deg != 0 or mount_pitch_deg != 0 or mount_yaw_deg != 0:
6403
raise NotAchievedException("Mount stabilising when not requested")
6404
6405
# center RC tilt control and change mount to RC_TARGETING mode
6406
self.progress("Gimbal to RC Targeting mode")
6407
self.set_rc(6, pitch_rc_neutral)
6408
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6409
6410
# pitch vehicle back and confirm gimbal is stabilising
6411
self.progress("Pitching vehicle")
6412
self.do_pitch(despitch)
6413
self.wait_pitch(despitch, despitch_tolerance)
6414
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6415
6416
# point gimbal at specified angle
6417
self.progress("Point gimbal using GIMBAL_MANAGER_PITCHYAW (ANGLE)")
6418
self.do_pitch(0) # level vehicle
6419
self.wait_pitch(0, despitch_tolerance)
6420
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING)
6421
for (method, angle) in (self.run_cmd, -20), (self.run_cmd_int, -30):
6422
method(
6423
mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
6424
p1=angle, # pitch angle in degrees
6425
p2=0, # yaw angle in degrees
6426
p3=0, # pitch rate in degrees (NaN to ignore)
6427
p4=0, # yaw rate in degrees (NaN to ignore)
6428
p5=0, # flags (0=Body-frame, 16/GIMBAL_MANAGER_FLAGS_YAW_LOCK=Earth Frame)
6429
p6=0, # unused
6430
p7=0, # gimbal id
6431
)
6432
self.test_mount_pitch(angle, 1, mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING)
6433
6434
# this is a one-off; ArduCopter *will* time out this directive!
6435
self.progress("Levelling aircraft")
6436
self.mav.mav.set_attitude_target_send(
6437
0, # time_boot_ms
6438
1, # target sysid
6439
1, # target compid
6440
0, # bitmask of things to ignore
6441
mavextra.euler_to_quat([0, 0, 0]), # att
6442
0, # roll rate (rad/s)
6443
0, # pitch rate (rad/s)
6444
0, # yaw rate (rad/s)
6445
0.5) # thrust, 0 to 1, translated to a climb/descent rate
6446
6447
self.wait_groundspeed(0, 1)
6448
6449
# now test RC targeting
6450
self.progress("Testing mount RC targeting")
6451
6452
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6453
self.test_mount_rc_targetting(
6454
pitch_rc_neutral=pitch_rc_neutral,
6455
do_rate_tests=do_rate_tests,
6456
)
6457
6458
self.progress("Testing mount ROI behaviour")
6459
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6460
start = self.mav.location()
6461
self.progress("start=%s" % str(start))
6462
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
6463
start.lng,
6464
10,
6465
20)
6466
roi_alt = 0
6467
self.progress("Using MAV_CMD_DO_SET_ROI_LOCATION")
6468
self.run_cmd(
6469
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
6470
p5=roi_lat,
6471
p6=roi_lon,
6472
p7=roi_alt,
6473
)
6474
self.test_mount_pitch(-52, 5, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
6475
self.progress("Using MAV_CMD_DO_SET_ROI_LOCATION")
6476
# start by pointing the gimbal elsewhere with a
6477
# known-working command:
6478
self.run_cmd(
6479
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
6480
p5=roi_lat + 1,
6481
p6=roi_lon + 1,
6482
p7=roi_alt,
6483
)
6484
# now point it with command_int:
6485
self.run_cmd_int(
6486
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
6487
p5=int(roi_lat * 1e7),
6488
p6=int(roi_lon * 1e7),
6489
p7=roi_alt,
6490
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
6491
)
6492
self.test_mount_pitch(-52, 5, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
6493
6494
self.progress("Using MAV_CMD_DO_SET_ROI_NONE")
6495
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_ROI_NONE)
6496
self.run_cmd_int(mavutil.mavlink.MAV_CMD_DO_SET_ROI_NONE)
6497
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
6498
6499
start = self.mav.location()
6500
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
6501
start.lng,
6502
-100,
6503
-200)
6504
roi_alt = 0
6505
self.progress("Using MAV_CMD_DO_SET_ROI")
6506
self.run_cmd(
6507
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
6508
p5=roi_lat,
6509
p6=roi_lon,
6510
p7=roi_alt,
6511
)
6512
self.test_mount_pitch(-7.5, 1, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
6513
6514
start = self.mav.location()
6515
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
6516
start.lng,
6517
-100,
6518
-200)
6519
roi_alt = 0
6520
self.progress("Using MAV_CMD_DO_SET_ROI (COMMAND_INT)")
6521
self.run_cmd_int(
6522
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
6523
0,
6524
0,
6525
0,
6526
0,
6527
int(roi_lat*1e7),
6528
int(roi_lon*1e7),
6529
roi_alt,
6530
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
6531
)
6532
self.test_mount_pitch(-7.5, 1, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
6533
self.progress("Using MAV_CMD_DO_SET_ROI (COMMAND_INT), absolute-alt-frame")
6534
# this is pointing essentially straight down
6535
self.run_cmd_int(
6536
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
6537
0,
6538
0,
6539
0,
6540
0,
6541
int(roi_lat*1e7),
6542
int(roi_lon*1e7),
6543
roi_alt,
6544
frame=mavutil.mavlink.MAV_FRAME_GLOBAL,
6545
)
6546
self.test_mount_pitch(-70, 1, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT, hold=2)
6547
6548
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
6549
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
6550
6551
self.progress("Testing mount roi-sysid behaviour")
6552
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
6553
start = self.mav.location()
6554
self.progress("start=%s" % str(start))
6555
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
6556
start.lng,
6557
10,
6558
20)
6559
roi_alt = 0
6560
self.progress("Using MAV_CMD_DO_SET_ROI_SYSID")
6561
self.run_cmd(
6562
mavutil.mavlink.MAV_CMD_DO_SET_ROI_SYSID,
6563
p1=self.mav.source_system,
6564
)
6565
self.mav.mav.global_position_int_send(
6566
0, # time boot ms
6567
int(roi_lat * 1e7),
6568
int(roi_lon * 1e7),
6569
0 * 1000, # mm alt amsl
6570
0 * 1000, # relalt mm UP!
6571
0, # vx
6572
0, # vy
6573
0, # vz
6574
0 # heading
6575
)
6576
self.test_mount_pitch(-89, 5, mavutil.mavlink.MAV_MOUNT_MODE_SYSID_TARGET, hold=2)
6577
6578
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_ROI_NONE)
6579
self.run_cmd_int(
6580
mavutil.mavlink.MAV_CMD_DO_SET_ROI_SYSID,
6581
p1=self.mav.source_system,
6582
)
6583
self.mav.mav.global_position_int_send(
6584
0, # time boot ms
6585
int(roi_lat * 1e7),
6586
int(roi_lon * 1e7),
6587
670 * 1000, # mm alt amsl
6588
100 * 1000, # mm UP!
6589
0, # vx
6590
0, # vy
6591
0, # vz
6592
0 # heading
6593
)
6594
self.test_mount_pitch(
6595
68,
6596
5,
6597
mavutil.mavlink.MAV_MOUNT_MODE_SYSID_TARGET,
6598
hold=2,
6599
constrained=constrain_sysid_target,
6600
)
6601
6602
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
6603
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
6604
6605
self.disarm_vehicle(force=True)
6606
6607
self.test_mount_body_yaw()
6608
6609
def test_mount_body_yaw(self):
6610
'''check reporting of yaw'''
6611
# change mount to neutral mode (point forward, not stabilising)
6612
self.takeoff(10, mode='GUIDED')
6613
6614
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
6615
6616
for heading in 30, 45, 150:
6617
self.guided_achieve_heading(heading)
6618
6619
r, p , y, yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
6620
6621
if yaw_is_absolute:
6622
raise NotAchievedException("Expected a relative yaw")
6623
6624
if y > 1:
6625
raise NotAchievedException("Bad yaw (y=%f)")
6626
6627
self.do_RTL()
6628
6629
def Mount(self):
6630
'''test servo mount'''
6631
self.setup_servo_mount()
6632
self.reboot_sitl() # to handle MNT_TYPE changing
6633
self.mount_test_body()
6634
6635
def MountPOIFromAuxFunction(self):
6636
'''test we can lock onto a lat/lng/alt with the flick of a switch'''
6637
self.install_terrain_handlers_context()
6638
self.MountPOIFromAuxFunction_Main(enable=1)
6639
self.MountPOIFromAuxFunction_Main(enable=0)
6640
6641
def MountPOIFromAuxFunction_Main(self, enable):
6642
self.setup_servo_mount()
6643
self.progress(f">>>>>>> Testing with TERRAIN_ENABLE = {enable} >>>>>>>>>>>>")
6644
self.set_parameters({
6645
"RC10_OPTION": 186,
6646
"MNT1_RC_RATE": 0,
6647
"TERRAIN_ENABLE": enable,
6648
"SIM_TERRAIN": enable,
6649
})
6650
self.reboot_sitl() # to handle MNT1_TYPE changing # to handle MNT1_TYPE changing
6651
self.wait_ready_to_arm()
6652
6653
self.takeoff(10, mode='GUIDED')
6654
self.set_rc(6, 1000) # tilt mount down 45 degs
6655
self.delay_sim_time(.1) # allow mount to move
6656
6657
self.progress("checking mount angles")
6658
mount_roll, mount_pitch, mount_yaw, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
6659
assert -46 <= mount_pitch <= -44, f"Initial Mount Pitch is out of range: {mount_pitch}"
6660
6661
self.set_rc(10, 1900) # engage poi lock
6662
self.delay_sim_time(5) # allow time to compute POI
6663
self.set_rc(10, 1500) # revert mode, keep POI
6664
6665
self.fly_guided_move_local(100, 100, 70) # move to new position
6666
WaitAndMaintainAttitude(self, 0, 0, epsilon=1, minimum_duration=1, timeout=5).run()
6667
6668
self.set_rc(10, 1900) # re-engage poi lock and check angles again
6669
self.delay_sim_time(.1) # allow mount to move
6670
6671
mount_roll, mount_pitch, mount_yaw, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
6672
assert (-178 <= mount_yaw <= -176), f"Mount Yaw2 is out of range: {mount_yaw}"
6673
assert -26 <= mount_pitch <= -24, f"Mount Pitch2 is out of range: {mount_pitch}"
6674
self.progress(f"Mount Pitch2 = {mount_pitch}. Mount Yaw2 = {mount_yaw}")
6675
6676
self.set_rc(10, 1500) # return to RC target mode and check that mount reverts to initial angles
6677
self.delay_sim_time(.1) # allow mount to move
6678
6679
mount_roll, mount_pitch, mount_yaw, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
6680
assert -46 <= mount_pitch <= -44, f"Mount Pitch3 is out of range: {mount_pitch}"
6681
assert -1 <= mount_yaw <= 1, f"Mount Yaw3 is out of range: {mount_yaw}"
6682
self.progress(f"Mount Pitch3 = {mount_pitch}. Mount Yaw3 = {mount_yaw}")
6683
6684
self.change_mode('RTL')
6685
self.wait_disarmed()
6686
self.assert_at_home()
6687
6688
def MountSolo(self):
6689
'''test type=2, a "Solo" mount'''
6690
self.set_parameters({
6691
"MNT1_TYPE": 2,
6692
"RC6_OPTION": 213, # MOUNT1_PITCH
6693
})
6694
self.customise_SITL_commandline([
6695
"--gimbal" # connects on port 5762
6696
])
6697
self.mount_test_body(
6698
pitch_rc_neutral=1818,
6699
do_rate_tests=False, # solo can't do rate control (yet?)
6700
constrain_sysid_target=False, # not everything constrains all angles
6701
)
6702
6703
def assert_mount_rpy(self, r, p, y, tolerance=1):
6704
'''assert mount atttiude in degrees'''
6705
got_r, got_p, got_y, yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
6706
for (want, got, name) in (r, got_r, "roll"), (p, got_p, "pitch"), (y, got_y, "yaw"):
6707
if abs(want - got) > tolerance:
6708
raise NotAchievedException("%s incorrect; want=%f got=%f" %
6709
(name, want, got))
6710
6711
def neutralise_gimbal(self):
6712
'''put mount into neutralise mode, assert it is at zero angles'''
6713
self.run_cmd(
6714
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6715
p7=mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL,
6716
)
6717
self.test_mount_pitch(0, 0, mavutil.mavlink.MAV_MOUNT_MODE_RETRACT)
6718
6719
def MAV_CMD_DO_MOUNT_CONTROL(self):
6720
'''test MAV_CMD_DO_MOUNT_CONTROL mavlink command'''
6721
6722
# setup mount parameters
6723
self.context_push()
6724
self.setup_servo_mount()
6725
self.reboot_sitl() # to handle MNT_TYPE changing
6726
6727
takeoff_loc = self.mav.location()
6728
6729
self.takeoff(20, mode='GUIDED')
6730
self.guided_achieve_heading(315)
6731
6732
self.run_cmd(
6733
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6734
p7=mavutil.mavlink.MAV_MOUNT_MODE_RETRACT,
6735
)
6736
self.run_cmd_int(
6737
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6738
p7=mavutil.mavlink.MAV_MOUNT_MODE_RETRACT,
6739
)
6740
6741
for method in self.run_cmd, self.run_cmd_int:
6742
self.start_subtest("MAV_MOUNT_MODE_GPS_POINT")
6743
6744
self.progress("start=%s" % str(takeoff_loc))
6745
t = self.offset_location_ne(takeoff_loc, 20, 0)
6746
self.progress("targeting=%s" % str(t))
6747
6748
# this command is *weird* as the lat/lng is *always* 1e7,
6749
# even when transported via COMMAND_LONG!
6750
x = int(t.lat * 1e7)
6751
y = int(t.lng * 1e7)
6752
method(
6753
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6754
p4=0, # this is a relative altitude!
6755
p5=x,
6756
p6=y,
6757
p7=mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT,
6758
)
6759
self.test_mount_pitch(-45, 5, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
6760
self.neutralise_gimbal()
6761
6762
self.start_subtest("MAV_MOUNT_MODE_HOME_LOCATION")
6763
method(
6764
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6765
p7=mavutil.mavlink.MAV_MOUNT_MODE_HOME_LOCATION,
6766
)
6767
self.test_mount_pitch(-90, 5, mavutil.mavlink.MAV_MOUNT_MODE_HOME_LOCATION)
6768
self.neutralise_gimbal()
6769
6770
# try an invalid mount mode. Note that this is asserting we
6771
# are receiving a result code which is actually incorrect;
6772
# this should be MAV_RESULT_DENIED
6773
self.start_subtest("Invalid mode")
6774
method(
6775
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6776
p7=87,
6777
want_result=mavutil.mavlink.MAV_RESULT_FAILED,
6778
)
6779
6780
self.start_subtest("MAV_MOUNT_MODE_MAVLINK_TARGETING")
6781
r = 15
6782
p = 20
6783
y = 30
6784
method(
6785
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6786
p1=p,
6787
p2=r,
6788
p3=y,
6789
p7=mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING,
6790
)
6791
self.delay_sim_time(2)
6792
self.assert_mount_rpy(r, p, y)
6793
self.neutralise_gimbal()
6794
6795
self.start_subtest("MAV_MOUNT_MODE_RC_TARGETING")
6796
method(
6797
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6798
p7=mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING,
6799
)
6800
self.test_mount_rc_targetting()
6801
6802
self.start_subtest("MAV_MOUNT_MODE_RETRACT")
6803
self.context_push()
6804
retract_r = 13
6805
retract_p = 23
6806
retract_y = 33
6807
self.set_parameters({
6808
"MNT1_RETRACT_X": retract_r,
6809
"MNT1_RETRACT_Y": retract_p,
6810
"MNT1_RETRACT_Z": retract_y,
6811
})
6812
method(
6813
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6814
p7=mavutil.mavlink.MAV_MOUNT_MODE_RETRACT,
6815
)
6816
self.delay_sim_time(3)
6817
self.assert_mount_rpy(retract_r, retract_p, retract_y)
6818
self.context_pop()
6819
6820
self.do_RTL()
6821
6822
self.context_pop()
6823
self.reboot_sitl()
6824
6825
def AutoYawDO_MOUNT_CONTROL(self):
6826
'''test AutoYaw behaviour when MAV_CMD_DO_MOUNT_CONTROL sent to Mount without Yaw control'''
6827
6828
# setup mount parameters
6829
self.context_push()
6830
6831
yaw_servo = 7
6832
self.setup_servo_mount(roll_servo=5, pitch_servo=6, yaw_servo=yaw_servo)
6833
# Disable Mount Yaw servo
6834
self.set_parameters({
6835
"SERVO%u_FUNCTION" % yaw_servo: 0,
6836
})
6837
self.reboot_sitl() # to handle MNT_TYPE changing
6838
6839
self.takeoff(20, mode='GUIDED')
6840
6841
for mount_yaw in [-45, 0, 45]:
6842
heading = 330
6843
self.guided_achieve_heading(heading)
6844
self.assert_heading(heading)
6845
6846
self.neutralise_gimbal()
6847
6848
r = 15
6849
p = 20
6850
self.run_cmd_int(
6851
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
6852
p1=p,
6853
p2=r,
6854
p3=mount_yaw,
6855
p7=mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING,
6856
)
6857
self.delay_sim_time(5)
6858
# We have disabled yaw servo, so expect mount yaw to be zero
6859
self.assert_mount_rpy(r, p, 0)
6860
# But we expect the copter to yaw instead
6861
self.assert_heading(heading + mount_yaw)
6862
6863
self.do_RTL()
6864
6865
self.context_pop()
6866
self.reboot_sitl()
6867
6868
def MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE(self):
6869
'''test MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE mavlink command'''
6870
# setup mount parameters
6871
self.context_push()
6872
self.setup_servo_mount()
6873
self.reboot_sitl() # to handle MNT_TYPE changing
6874
6875
self.context_set_message_rate_hz('GIMBAL_MANAGER_STATUS', 10)
6876
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
6877
"gimbal_device_id": 1,
6878
"primary_control_sysid": 0,
6879
"primary_control_compid": 0,
6880
})
6881
6882
for method in self.run_cmd, self.run_cmd_int:
6883
self.start_subtest("set_sysid-compid")
6884
method(
6885
mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
6886
p1=37,
6887
p2=38,
6888
)
6889
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
6890
"gimbal_device_id": 1,
6891
"primary_control_sysid": 37,
6892
"primary_control_compid": 38,
6893
})
6894
6895
self.start_subtest("leave unchanged")
6896
method(mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE, p1=-1)
6897
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
6898
"gimbal_device_id": 1,
6899
"primary_control_sysid": 37,
6900
"primary_control_compid": 38,
6901
})
6902
6903
# ardupilot currently handles this incorrectly:
6904
# self.start_subtest("self-controlled")
6905
# method(mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE, p1=-2)
6906
# self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
6907
# "gimbal_device_id": 1,
6908
# "primary_control_sysid": 1,
6909
# "primary_control_compid": 1,
6910
# })
6911
6912
self.start_subtest("release control")
6913
method(
6914
mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
6915
p1=self.mav.source_system,
6916
p2=self.mav.source_component,
6917
)
6918
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
6919
"gimbal_device_id": 1,
6920
"primary_control_sysid": self.mav.source_system,
6921
"primary_control_compid": self.mav.source_component,
6922
})
6923
method(mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE, p1=-3)
6924
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
6925
"gimbal_device_id": 1,
6926
"primary_control_sysid": 0,
6927
"primary_control_compid": 0,
6928
})
6929
6930
self.context_pop()
6931
self.reboot_sitl()
6932
6933
def MountYawVehicleForMountROI(self):
6934
'''Test Camera/Antenna Mount vehicle yawing for ROI'''
6935
self.set_parameter("MAV_GCS_SYSID", self.mav.source_system)
6936
yaw_servo = 7
6937
self.setup_servo_mount(yaw_servo=yaw_servo)
6938
self.reboot_sitl() # to handle MNT1_TYPE changing
6939
6940
self.progress("checking ArduCopter yaw-aircraft-for-roi")
6941
self.takeoff(20, mode='GUIDED')
6942
6943
m = self.assert_receive_message('VFR_HUD')
6944
self.progress("current heading %u" % m.heading)
6945
self.set_parameter("SERVO%u_FUNCTION" % yaw_servo, 0) # yaw
6946
self.progress("Waiting for check_servo_map to do its job")
6947
self.delay_sim_time(5)
6948
self.progress("Pointing North")
6949
self.guided_achieve_heading(0)
6950
self.delay_sim_time(5)
6951
start = self.mav.location()
6952
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
6953
start.lng,
6954
-100,
6955
-100)
6956
roi_alt = 0
6957
self.progress("Using MAV_CMD_DO_SET_ROI")
6958
self.run_cmd(
6959
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
6960
p5=roi_lat,
6961
p6=roi_lon,
6962
p7=roi_alt,
6963
)
6964
6965
self.progress("Waiting for vehicle to point towards ROI")
6966
self.wait_heading(225, timeout=600, minimum_duration=2)
6967
6968
# the following numbers are 1-degree-latitude and
6969
# 0-degrees longitude - just so that we start to
6970
# really move a lot.
6971
there = mavutil.location(1, 0, 0, 0)
6972
6973
self.progress("Starting to move")
6974
self.mav.mav.set_position_target_global_int_send(
6975
0, # timestamp
6976
1, # target system_id
6977
1, # target component id
6978
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
6979
MAV_POS_TARGET_TYPE_MASK.POS_ONLY | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE, # mask specifying use-only-lat-lon-alt
6980
there.lat, # lat
6981
there.lng, # lon
6982
there.alt, # alt
6983
0, # vx
6984
0, # vy
6985
0, # vz
6986
0, # afx
6987
0, # afy
6988
0, # afz
6989
0, # yaw
6990
0, # yawrate
6991
)
6992
6993
self.progress("Starting to move changes the target")
6994
bearing = self.bearing_to(there)
6995
self.wait_heading(bearing, timeout=600, minimum_duration=2)
6996
6997
self.run_cmd(
6998
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
6999
p5=roi_lat,
7000
p6=roi_lon,
7001
p7=roi_alt,
7002
)
7003
7004
self.progress("Wait for vehicle to point sssse due to moving")
7005
self.wait_heading(170, timeout=600, minimum_duration=1)
7006
7007
self.do_RTL()
7008
7009
def ThrowMode(self):
7010
'''Fly Throw Mode'''
7011
# test boomerang mode:
7012
self.progress("Throwing vehicle away")
7013
self.set_parameters({
7014
"THROW_NEXTMODE": 6,
7015
"SIM_SHOVE_Z": -30,
7016
"SIM_SHOVE_X": -20,
7017
})
7018
self.change_mode('THROW')
7019
self.wait_ready_to_arm()
7020
self.arm_vehicle()
7021
try:
7022
self.set_parameter("SIM_SHOVE_TIME", 500)
7023
except ValueError:
7024
# the shove resets this to zero
7025
pass
7026
7027
tstart = self.get_sim_time()
7028
self.wait_mode('RTL')
7029
max_good_tdelta = 15
7030
tdelta = self.get_sim_time() - tstart
7031
self.progress("Vehicle in RTL")
7032
self.wait_rtl_complete()
7033
self.progress("Vehicle disarmed")
7034
if tdelta > max_good_tdelta:
7035
raise NotAchievedException("Took too long to enter RTL: %fs > %fs" %
7036
(tdelta, max_good_tdelta))
7037
self.progress("Vehicle returned")
7038
7039
def hover_and_check_matched_frequency_with_fft_and_psd(self, dblevel=-15, minhz=200, maxhz=300, peakhz=None,
7040
reverse=None, takeoff=True, instance=0):
7041
'''Takeoff and hover, checking the noise against the provided db level and returning psd'''
7042
# find a motor peak
7043
if takeoff:
7044
self.takeoff(10, mode="ALT_HOLD")
7045
7046
tstart, tend, hover_throttle = self.hover_for_interval(15)
7047
self.do_RTL()
7048
7049
psd = self.mavfft_fttd(1, instance, tstart * 1.0e6, tend * 1.0e6)
7050
7051
# batch sampler defaults give 1024 fft and sample rate of 1kz so roughly 1hz/bin
7052
freq = psd["F"][numpy.argmax(psd["X"][minhz:maxhz]) + minhz] * (1000. / 1024.)
7053
peakdb = numpy.amax(psd["X"][minhz:maxhz])
7054
if peakdb < dblevel or (peakhz is not None and abs(freq - peakhz) / peakhz > 0.05):
7055
if reverse is not None:
7056
self.progress("Did not detect a motor peak, found %fHz at %fdB" % (freq, peakdb))
7057
else:
7058
raise NotAchievedException("Did not detect a motor peak, found %fHz at %fdB" % (freq, peakdb))
7059
else:
7060
if reverse is not None:
7061
raise NotAchievedException(
7062
"Detected motor peak at %fHz, throttle %f%%, %fdB" %
7063
(freq, hover_throttle, peakdb))
7064
else:
7065
self.progress("Detected motor peak at %fHz, throttle %f%%, %fdB" %
7066
(freq, hover_throttle, peakdb))
7067
7068
return freq, hover_throttle, peakdb, psd
7069
7070
def hover_and_check_matched_frequency_with_fft(self, dblevel=-15, minhz=200, maxhz=300, peakhz=None,
7071
reverse=None, takeoff=True, instance=0):
7072
'''Takeoff and hover, checking the noise against the provided db level and returning peak db'''
7073
freq, hover_throttle, peakdb, psd = \
7074
self.hover_and_check_matched_frequency_with_fft_and_psd(dblevel, minhz,
7075
maxhz, peakhz, reverse, takeoff, instance)
7076
return freq, hover_throttle, peakdb
7077
7078
def get_average_esc_frequency(self):
7079
mlog = self.dfreader_for_current_onboard_log()
7080
rpm_total = 0
7081
rpm_count = 0
7082
tho = 0
7083
while True:
7084
m = mlog.recv_match()
7085
if m is None:
7086
break
7087
msg_type = m.get_type()
7088
if msg_type == "CTUN":
7089
tho = m.ThO
7090
elif msg_type == "ESC" and tho > 0.1:
7091
rpm_total += m.RPM
7092
rpm_count += 1
7093
7094
esc_hz = rpm_total / (rpm_count * 60)
7095
return esc_hz
7096
7097
def DynamicNotches(self):
7098
"""Use dynamic harmonic notch to control motor noise."""
7099
self.progress("Flying with dynamic notches")
7100
7101
self.set_parameters({
7102
"AHRS_EKF_TYPE": 10,
7103
"INS_LOG_BAT_MASK": 3,
7104
"INS_LOG_BAT_OPT": 0,
7105
"INS_GYRO_FILTER": 100, # set the gyro filter high so we can observe behaviour
7106
"LOG_BITMASK": 958,
7107
"LOG_DISARMED": 0,
7108
"SIM_VIB_MOT_MAX": 350,
7109
"SIM_GYR1_RND": 20,
7110
})
7111
self.reboot_sitl()
7112
7113
self.takeoff(10, mode="ALT_HOLD")
7114
7115
# find a motor peak
7116
freq, hover_throttle, peakdb = self.hover_and_check_matched_frequency_with_fft(-15, 200, 300)
7117
7118
# now add a dynamic notch and check that the peak is squashed
7119
self.set_parameters({
7120
"INS_LOG_BAT_OPT": 2,
7121
"INS_HNTCH_ENABLE": 1,
7122
"INS_HNTCH_FREQ": freq,
7123
"INS_HNTCH_REF": hover_throttle/100.,
7124
"INS_HNTCH_HMNCS": 5, # first and third harmonic
7125
"INS_HNTCH_ATT": 50,
7126
"INS_HNTCH_BW": freq/2,
7127
})
7128
self.reboot_sitl()
7129
7130
freq, hover_throttle, peakdb1 = \
7131
self.hover_and_check_matched_frequency_with_fft(-10, 20, 350, reverse=True)
7132
7133
# now add double dynamic notches and check that the peak is squashed
7134
self.set_parameter("INS_HNTCH_OPTS", 1)
7135
self.reboot_sitl()
7136
7137
freq, hover_throttle, peakdb2 = \
7138
self.hover_and_check_matched_frequency_with_fft(-15, 20, 350, reverse=True)
7139
7140
# double-notch should do better, but check for within 5%
7141
if peakdb2 * 1.05 > peakdb1:
7142
raise NotAchievedException(
7143
"Double-notch peak was higher than single-notch peak %fdB > %fdB" %
7144
(peakdb2, peakdb1))
7145
7146
# now add triple dynamic notches and check that the peak is squashed
7147
self.set_parameter("INS_HNTCH_OPTS", 16)
7148
self.reboot_sitl()
7149
7150
freq, hover_throttle, peakdb2 = \
7151
self.hover_and_check_matched_frequency_with_fft(-15, 20, 350, reverse=True)
7152
7153
# triple-notch should do better, but check for within 5%
7154
if peakdb2 * 1.05 > peakdb1:
7155
raise NotAchievedException(
7156
"Triple-notch peak was higher than single-notch peak %fdB > %fdB" %
7157
(peakdb2, peakdb1))
7158
7159
# now add quintuple dynamic notches and check that the peak is squashed
7160
self.set_parameter("INS_HNTCH_OPTS", 64)
7161
self.reboot_sitl()
7162
7163
freq, hover_throttle, peakdb2 = \
7164
self.hover_and_check_matched_frequency_with_fft(-15, 20, 350, reverse=True)
7165
7166
# triple-notch should do better, but check for within 5%
7167
if peakdb2 * 1.05 > peakdb1:
7168
raise NotAchievedException(
7169
"Quintuple-notch peak was higher than single-notch peak %fdB > %fdB" %
7170
(peakdb2, peakdb1))
7171
7172
def DynamicRpmNotches(self):
7173
"""Use dynamic harmonic notch to control motor noise via ESC telemetry."""
7174
self.progress("Flying with ESC telemetry driven dynamic notches")
7175
7176
self.set_rc_default()
7177
self.set_parameters({
7178
"AHRS_EKF_TYPE": 10,
7179
"INS_LOG_BAT_MASK": 3,
7180
"INS_LOG_BAT_OPT": 0,
7181
"INS_GYRO_FILTER": 300, # set gyro filter high so we can observe behaviour
7182
"LOG_BITMASK": 958,
7183
"LOG_DISARMED": 0,
7184
"SIM_VIB_MOT_MAX": 350,
7185
"SIM_GYR1_RND": 20,
7186
"SIM_ESC_TELEM": 1
7187
})
7188
self.reboot_sitl()
7189
7190
self.takeoff(10, mode="ALT_HOLD")
7191
7192
# find a motor peak, the peak is at about 190Hz, so checking between 50 and 320Hz should be safe.
7193
# there is a second harmonic at 380Hz which should be avoided to make the test reliable
7194
# detect at -5dB so we don't pick some random noise as the peak. The actual peak is about +15dB
7195
freq, hover_throttle, peakdb = self.hover_and_check_matched_frequency_with_fft(-5, 50, 320)
7196
7197
# now add a dynamic notch and check that the peak is squashed
7198
self.set_parameters({
7199
"INS_LOG_BAT_OPT": 4,
7200
"INS_HNTCH_ENABLE": 1,
7201
"INS_HNTCH_FREQ": 80,
7202
"INS_HNTCH_REF": 1.0,
7203
"INS_HNTCH_HMNCS": 5, # first and third harmonic
7204
"INS_HNTCH_ATT": 50,
7205
"INS_HNTCH_BW": 40,
7206
"INS_HNTCH_MODE": 3,
7207
})
7208
self.reboot_sitl()
7209
7210
# -10dB is pretty conservative - actual is about -25dB
7211
freq, hover_throttle, peakdb1, psd = \
7212
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
7213
# find the noise at the motor frequency
7214
esc_hz = self.get_average_esc_frequency()
7215
esc_peakdb1 = psd["X"][int(esc_hz)]
7216
7217
# now add notch-per motor and check that the peak is squashed
7218
self.set_parameter("INS_HNTCH_OPTS", 2)
7219
self.reboot_sitl()
7220
7221
freq, hover_throttle, peakdb2, psd = \
7222
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
7223
# find the noise at the motor frequency
7224
esc_hz = self.get_average_esc_frequency()
7225
esc_peakdb2 = psd["X"][int(esc_hz)]
7226
7227
# notch-per-motor will be better at the average ESC frequency
7228
if esc_peakdb2 > esc_peakdb1:
7229
raise NotAchievedException(
7230
"Notch-per-motor peak was higher than single-notch peak %fdB > %fdB" %
7231
(esc_peakdb2, esc_peakdb1))
7232
7233
# check that the noise is being squashed at all. this needs to be an aggressive check so that failure happens easily
7234
# testing shows this to be -58dB on average
7235
if esc_peakdb2 > -25:
7236
raise NotAchievedException(
7237
"Notch-per-motor had a peak of %fdB there should be none" % esc_peakdb2)
7238
7239
# Now do it again for an octacopter
7240
self.progress("Flying Octacopter with ESC telemetry driven dynamic notches")
7241
self.set_parameter("INS_HNTCH_OPTS", 0)
7242
self.customise_SITL_commandline(
7243
[],
7244
defaults_filepath=','.join(self.model_defaults_filepath("octa")),
7245
model="octa"
7246
)
7247
freq, hover_throttle, peakdb1, psd = \
7248
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
7249
# find the noise at the motor frequency
7250
esc_hz = self.get_average_esc_frequency()
7251
esc_peakdb1 = psd["X"][int(esc_hz)]
7252
7253
# now add notch-per motor and check that the peak is squashed
7254
self.set_parameter("INS_HNTCH_HMNCS", 1)
7255
self.set_parameter("INS_HNTCH_OPTS", 2)
7256
self.reboot_sitl()
7257
7258
freq, hover_throttle, peakdb2, psd = \
7259
self.hover_and_check_matched_frequency_with_fft_and_psd(-15, 50, 320, reverse=True, instance=2)
7260
# find the noise at the motor frequency
7261
esc_hz = self.get_average_esc_frequency()
7262
esc_peakdb2 = psd["X"][int(esc_hz)]
7263
7264
# notch-per-motor will be better at the average ESC frequency
7265
if esc_peakdb2 > esc_peakdb1:
7266
raise NotAchievedException(
7267
"Notch-per-motor peak was higher than single-notch peak %fdB > %fdB" %
7268
(esc_peakdb2, esc_peakdb1))
7269
7270
def DynamicRpmNotchesRateThread(self):
7271
"""Use dynamic harmonic notch to control motor noise via ESC telemetry."""
7272
self.progress("Flying with ESC telemetry driven dynamic notches")
7273
self.context_push()
7274
self.set_rc_default()
7275
self.set_parameters({
7276
"AHRS_EKF_TYPE": 10,
7277
"INS_LOG_BAT_MASK": 3,
7278
"INS_LOG_BAT_OPT": 0,
7279
"INS_GYRO_FILTER": 300, # set gyro filter high so we can observe behaviour
7280
"LOG_BITMASK": 959,
7281
"LOG_DISARMED": 0,
7282
"SIM_VIB_MOT_MAX": 350,
7283
"SIM_GYR1_RND": 20,
7284
"SIM_ESC_TELEM": 1,
7285
"FSTRATE_ENABLE": 1
7286
})
7287
self.reboot_sitl()
7288
7289
self.takeoff(10, mode="ALT_HOLD")
7290
7291
# find a motor peak, the peak is at about 190Hz, so checking between 50 and 320Hz should be safe.
7292
# there is a second harmonic at 380Hz which should be avoided to make the test reliable
7293
# detect at -5dB so we don't pick some random noise as the peak. The actual peak is about +15dB
7294
freq, hover_throttle, peakdb = self.hover_and_check_matched_frequency_with_fft(-5, 50, 320)
7295
7296
# now add a dynamic notch and check that the peak is squashed
7297
self.set_parameters({
7298
"INS_LOG_BAT_OPT": 4,
7299
"INS_HNTCH_ENABLE": 1,
7300
"INS_HNTCH_FREQ": 80,
7301
"INS_HNTCH_REF": 1.0,
7302
"INS_HNTCH_HMNCS": 5, # first and third harmonic
7303
"INS_HNTCH_ATT": 50,
7304
"INS_HNTCH_BW": 40,
7305
"INS_HNTCH_MODE": 3,
7306
"FSTRATE_ENABLE": 1
7307
})
7308
self.reboot_sitl()
7309
7310
# -10dB is pretty conservative - actual is about -25dB
7311
freq, hover_throttle, peakdb1, psd = \
7312
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
7313
# find the noise at the motor frequency
7314
esc_hz = self.get_average_esc_frequency()
7315
esc_peakdb1 = psd["X"][int(esc_hz)]
7316
7317
# now add notch-per motor and check that the peak is squashed
7318
self.set_parameter("INS_HNTCH_OPTS", 2)
7319
self.reboot_sitl()
7320
7321
freq, hover_throttle, peakdb2, psd = \
7322
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
7323
# find the noise at the motor frequency
7324
esc_hz = self.get_average_esc_frequency()
7325
esc_peakdb2 = psd["X"][int(esc_hz)]
7326
7327
# notch-per-motor will be better at the average ESC frequency
7328
if esc_peakdb2 > esc_peakdb1:
7329
raise NotAchievedException(
7330
"Notch-per-motor peak was higher than single-notch peak %fdB > %fdB" %
7331
(esc_peakdb2, esc_peakdb1))
7332
7333
# check that the noise is being squashed at all. this needs to be an aggressive check so that failure happens easily
7334
# testing shows this to be -58dB on average
7335
if esc_peakdb2 > -25:
7336
raise NotAchievedException(
7337
"Notch-per-motor had a peak of %fdB there should be none" % esc_peakdb2)
7338
self.context_pop()
7339
self.reboot_sitl()
7340
7341
def hover_and_check_matched_frequency(self, dblevel=-15, minhz=200, maxhz=300, fftLength=32, peakhz=None):
7342
'''do a simple up-and-down test flight with current vehicle state.
7343
Check that the onboard filter comes up with the same peak-frequency that
7344
post-processing does.'''
7345
self.takeoff(10, mode="ALT_HOLD")
7346
tstart, tend, hover_throttle = self.hover_for_interval(15)
7347
self.do_RTL()
7348
7349
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
7350
7351
# batch sampler defaults give 1024 fft and sample rate of 1kz so roughly 1hz/bin
7352
scale = 1000. / 1024.
7353
sminhz = int(minhz * scale)
7354
smaxhz = int(maxhz * scale)
7355
freq = psd["F"][numpy.argmax(psd["X"][sminhz:smaxhz]) + sminhz]
7356
peakdb = numpy.amax(psd["X"][sminhz:smaxhz])
7357
7358
self.progress("Post-processing FFT detected motor peak at %fHz/%fdB, throttle %f%%" %
7359
(freq, peakdb, hover_throttle))
7360
7361
if peakdb < dblevel:
7362
raise NotAchievedException(
7363
"Detected motor peak not strong enough; want=%fdB got=%fdB" %
7364
(peakdb, dblevel))
7365
7366
# caller can supply an expected frequency:
7367
if peakhz is not None and abs(freq - peakhz) / peakhz > 0.05:
7368
raise NotAchievedException(
7369
"Post-processing detected motor peak at wrong frequency; want=%fHz got=%fHz" %
7370
(peakhz, freq))
7371
7372
# we have a peak make sure that the onboard filter detected
7373
# something close logging is at 10Hz
7374
7375
# peak within resolution of FFT length
7376
pkAvg, nmessages = self.extract_median_FTN1_PkAvg_from_current_onboard_log(tstart, tend)
7377
self.progress("Onboard-FFT detected motor peak at %fHz (processed %d FTN1 messages)" % (pkAvg, nmessages))
7378
7379
# accuracy is determined by sample rate and fft length, given
7380
# our use of quinn we could probably use half of this
7381
freqDelta = 1000. / fftLength
7382
if abs(pkAvg - freq) > freqDelta:
7383
raise NotAchievedException(
7384
"post-processed FFT does not agree with onboard filter on peak frequency; onboard=%fHz post-processed=%fHz/%fdB" % # noqa
7385
(pkAvg, freq, dblevel)
7386
)
7387
return freq
7388
7389
def extract_median_FTN1_PkAvg_from_current_onboard_log(self, tstart, tend):
7390
'''extracts FTN1 messages from log, returns median of pkAvg values and
7391
the number of samples'''
7392
mlog = self.dfreader_for_current_onboard_log()
7393
freqs = []
7394
while True:
7395
m = mlog.recv_match(
7396
type='FTN1',
7397
blocking=False,
7398
condition="FTN1.TimeUS>%u and FTN1.TimeUS<%u" % (tstart * 1.0e6, tend * 1.0e6))
7399
if m is None:
7400
break
7401
freqs.append(m.PkAvg)
7402
return numpy.median(numpy.asarray(freqs)), len(freqs)
7403
7404
def PIDNotches(self):
7405
"""Use dynamic harmonic notch to control motor noise."""
7406
self.progress("Flying with PID notches")
7407
self.set_parameters({
7408
"FILT1_TYPE": 1,
7409
"AHRS_EKF_TYPE": 10,
7410
"INS_LOG_BAT_MASK": 3,
7411
"INS_LOG_BAT_OPT": 0,
7412
"INS_GYRO_FILTER": 100, # set the gyro filter high so we can observe behaviour
7413
"LOG_BITMASK": 65535,
7414
"LOG_DISARMED": 0,
7415
"SIM_VIB_FREQ_X": 120, # roll
7416
"SIM_VIB_FREQ_Y": 120, # pitch
7417
"SIM_VIB_FREQ_Z": 180, # yaw
7418
"FILT1_NOTCH_FREQ": 120,
7419
"ATC_RAT_RLL_NEF": 1,
7420
"ATC_RAT_PIT_NEF": 1,
7421
"ATC_RAT_YAW_NEF": 1,
7422
"SIM_GYR1_RND": 5,
7423
})
7424
self.reboot_sitl()
7425
7426
self.hover_and_check_matched_frequency_with_fft(dblevel=5, minhz=20, maxhz=350, reverse=True)
7427
7428
def StaticNotches(self):
7429
"""Use static harmonic notch to control motor noise."""
7430
self.progress("Flying with Static notches")
7431
self.set_parameters({
7432
"AHRS_EKF_TYPE": 10,
7433
"INS_LOG_BAT_MASK": 3,
7434
"INS_LOG_BAT_OPT": 4,
7435
"INS_GYRO_FILTER": 100, # set the gyro filter high so we can observe behaviour
7436
"LOG_BITMASK": 65535,
7437
"LOG_DISARMED": 0,
7438
"SIM_VIB_FREQ_X": 120, # roll
7439
"SIM_VIB_FREQ_Y": 120, # pitch
7440
"SIM_VIB_FREQ_Z": 120, # yaw
7441
"SIM_VIB_MOT_MULT": 0,
7442
"SIM_GYR1_RND": 5,
7443
"INS_HNTCH_ENABLE": 1,
7444
"INS_HNTCH_FREQ": 120,
7445
"INS_HNTCH_REF": 1.0,
7446
"INS_HNTCH_HMNCS": 3, # first and second harmonic
7447
"INS_HNTCH_ATT": 50,
7448
"INS_HNTCH_BW": 40,
7449
"INS_HNTCH_MODE": 0, # static notch
7450
})
7451
self.reboot_sitl()
7452
7453
self.hover_and_check_matched_frequency_with_fft(dblevel=-15, minhz=20, maxhz=350, reverse=True, instance=2)
7454
7455
def ThrottleGainBoost(self):
7456
"""Use PD and Angle P boost for anti-gravity."""
7457
# basic gyro sample rate test
7458
self.progress("Flying with Throttle-Gain Boost")
7459
7460
# magic tridge EKF type that dramatically speeds up the test
7461
self.set_parameters({
7462
"AHRS_EKF_TYPE": 10,
7463
"EK2_ENABLE": 0,
7464
"EK3_ENABLE": 0,
7465
"INS_FAST_SAMPLE": 0,
7466
"LOG_BITMASK": 959,
7467
"LOG_DISARMED": 0,
7468
"ATC_THR_G_BOOST": 5.0,
7469
})
7470
7471
self.reboot_sitl()
7472
7473
self.takeoff(10, mode="ALT_HOLD")
7474
hover_time = 15
7475
self.progress("Hovering for %u seconds" % hover_time)
7476
tstart = self.get_sim_time()
7477
while self.get_sim_time_cached() < tstart + hover_time:
7478
self.assert_receive_message('ATTITUDE')
7479
7480
# fly fast forrest!
7481
self.set_rc(3, 1900)
7482
self.set_rc(2, 1200)
7483
self.wait_groundspeed(5, 1000)
7484
self.set_rc(3, 1500)
7485
self.set_rc(2, 1500)
7486
7487
self.do_RTL()
7488
7489
def test_gyro_fft_harmonic(self, averaging):
7490
"""Use dynamic harmonic notch to control motor noise with harmonic matching of the first harmonic."""
7491
# basic gyro sample rate test
7492
self.progress("Flying with gyro FFT harmonic - Gyro sample rate")
7493
self.start_subtest("Hover to calculate approximate hover frequency")
7494
# magic tridge EKF type that dramatically speeds up the test
7495
self.set_parameters({
7496
"AHRS_EKF_TYPE": 10,
7497
"EK2_ENABLE": 0,
7498
"EK3_ENABLE": 0,
7499
"INS_LOG_BAT_MASK": 3,
7500
"INS_LOG_BAT_OPT": 0,
7501
"INS_GYRO_FILTER": 100,
7502
"INS_FAST_SAMPLE": 0,
7503
"LOG_BITMASK": 958,
7504
"LOG_DISARMED": 0,
7505
"SIM_DRIFT_SPEED": 0,
7506
"SIM_DRIFT_TIME": 0,
7507
"FFT_THR_REF": self.get_parameter("MOT_THST_HOVER"),
7508
"SIM_GYR1_RND": 20, # enable a noisy gyro
7509
})
7510
7511
# motor peak enabling FFT will also enable the arming
7512
# check, self-testing the functionality
7513
self.set_parameters({
7514
"FFT_ENABLE": 1,
7515
"FFT_MINHZ": 50,
7516
"FFT_MAXHZ": 450,
7517
"FFT_SNR_REF": 10,
7518
})
7519
if averaging:
7520
self.set_parameter("FFT_NUM_FRAMES", 8)
7521
7522
# Step 1: inject actual motor noise and use the FFT to track it
7523
self.set_parameters({
7524
"SIM_VIB_MOT_MAX": 250, # gives a motor peak at about 175Hz
7525
"FFT_WINDOW_SIZE": 64,
7526
"FFT_WINDOW_OLAP": 0.75,
7527
})
7528
7529
self.reboot_sitl()
7530
freq = self.hover_and_check_matched_frequency(-15, 100, 250, 64)
7531
7532
# Step 2: add a second harmonic and check the first is still tracked
7533
self.start_subtest("Add a fixed frequency harmonic at twice the hover frequency "
7534
"and check the right harmonic is found")
7535
self.set_parameters({
7536
"SIM_VIB_FREQ_X": freq * 2,
7537
"SIM_VIB_FREQ_Y": freq * 2,
7538
"SIM_VIB_FREQ_Z": freq * 2,
7539
"SIM_VIB_MOT_MULT": 0.25, # halve the motor noise so that the higher harmonic dominates
7540
})
7541
self.reboot_sitl()
7542
7543
self.hover_and_check_matched_frequency(-15, 100, 250, 64, None)
7544
7545
# Step 3: switch harmonics mid flight and check for tracking
7546
self.start_subtest("Switch harmonics mid flight and check the right harmonic is found")
7547
self.set_parameter("FFT_HMNC_PEAK", 0)
7548
self.reboot_sitl()
7549
7550
self.takeoff(10, mode="ALT_HOLD")
7551
7552
hover_time = 10
7553
tstart, tend_unused, hover_throttle = self.hover_for_interval(hover_time)
7554
7555
self.progress("Switching motor vibration multiplier")
7556
self.set_parameter("SIM_VIB_MOT_MULT", 5.0)
7557
7558
tstart_unused, tend, hover_throttle = self.hover_for_interval(hover_time)
7559
7560
self.do_RTL()
7561
7562
# peak within resolution of FFT length, the highest energy peak switched but our detection should not
7563
pkAvg, nmessages = self.extract_median_FTN1_PkAvg_from_current_onboard_log(tstart, tend)
7564
7565
freqDelta = 1000. / self.get_parameter("FFT_WINDOW_SIZE")
7566
7567
if abs(pkAvg - freq) > freqDelta:
7568
raise NotAchievedException("FFT did not detect a harmonic motor peak, found %f, wanted %f" % (pkAvg, freq))
7569
7570
# Step 4: dynamic harmonic
7571
self.start_subtest("Enable dynamic harmonics and make sure both frequency peaks are attenuated")
7572
# find a motor peak
7573
freq, hover_throttle, peakdb = self.hover_and_check_matched_frequency_with_fft(-15, 100, 350)
7574
7575
# now add a dynamic notch and check that the peak is squashed
7576
self.set_parameters({
7577
"INS_LOG_BAT_OPT": 2,
7578
"INS_HNTCH_ENABLE": 1,
7579
"INS_HNTCH_HMNCS": 1,
7580
"INS_HNTCH_MODE": 4,
7581
"INS_HNTCH_FREQ": freq,
7582
"INS_HNTCH_REF": hover_throttle/100.0,
7583
"INS_HNTCH_ATT": 100,
7584
"INS_HNTCH_BW": freq/2,
7585
"INS_HNTCH_OPTS": 3,
7586
})
7587
self.reboot_sitl()
7588
7589
# 5db is far in excess of the attenuation that the double dynamic-harmonic notch is able
7590
# to provide (-7dB on average), but without the notch the peak is around 20dB so still a safe test
7591
self.hover_and_check_matched_frequency_with_fft(5, 100, 350, reverse=True)
7592
7593
self.set_parameters({
7594
"SIM_VIB_FREQ_X": 0,
7595
"SIM_VIB_FREQ_Y": 0,
7596
"SIM_VIB_FREQ_Z": 0,
7597
"SIM_VIB_MOT_MULT": 1.0,
7598
})
7599
# prevent update parameters from messing with the settings when we pop the context
7600
self.set_parameter("FFT_ENABLE", 0)
7601
7602
def GyroFFTHarmonic(self):
7603
"""Use dynamic harmonic notch to control motor noise with harmonic matching of the first harmonic."""
7604
self.test_gyro_fft_harmonic(False)
7605
7606
def GyroFFTContinuousAveraging(self):
7607
"""Use dynamic harmonic notch with FFT averaging to control motor noise
7608
with harmonic matching of the first harmonic."""
7609
self.test_gyro_fft_harmonic(True)
7610
7611
def GyroFFT(self):
7612
"""Use dynamic harmonic notch to control motor noise."""
7613
# basic gyro sample rate test
7614
self.progress("Flying with gyro FFT - Gyro sample rate")
7615
7616
# magic tridge EKF type that dramatically speeds up the test
7617
self.set_parameters({
7618
"AHRS_EKF_TYPE": 10,
7619
"EK2_ENABLE": 0,
7620
"EK3_ENABLE": 0,
7621
"INS_LOG_BAT_MASK": 3,
7622
"INS_LOG_BAT_OPT": 4,
7623
"INS_GYRO_FILTER": 100,
7624
"INS_FAST_SAMPLE": 0,
7625
"LOG_BITMASK": 958,
7626
"LOG_DISARMED": 0,
7627
"SIM_DRIFT_SPEED": 0,
7628
"SIM_DRIFT_TIME": 0,
7629
"SIM_GYR1_RND": 20, # enable a noisy motor peak
7630
})
7631
# enabling FFT will also enable the arming check,
7632
# self-testing the functionality
7633
self.set_parameters({
7634
"FFT_ENABLE": 1,
7635
"FFT_MINHZ": 50,
7636
"FFT_MAXHZ": 450,
7637
"FFT_SNR_REF": 10,
7638
"FFT_WINDOW_SIZE": 128,
7639
"FFT_WINDOW_OLAP": 0.75,
7640
"FFT_SAMPLE_MODE": 0,
7641
})
7642
7643
# Step 1: inject a very precise noise peak at 250hz and make sure the in-flight fft
7644
# can detect it really accurately. For a 128 FFT the frequency resolution is 8Hz so
7645
# a 250Hz peak should be detectable within 5%
7646
self.start_subtest("Inject noise at 250Hz and check the FFT can find the noise")
7647
self.set_parameters({
7648
"SIM_VIB_FREQ_X": 250,
7649
"SIM_VIB_FREQ_Y": 250,
7650
"SIM_VIB_FREQ_Z": 250,
7651
})
7652
7653
self.reboot_sitl()
7654
7655
# find a motor peak
7656
self.hover_and_check_matched_frequency(-15, 100, 350, 128, 250)
7657
7658
# Step 1b: run the same test with an FFT length of 256 which is needed to flush out a
7659
# whole host of bugs related to uint8_t. This also tests very accurately the frequency resolution
7660
self.set_parameter("FFT_WINDOW_SIZE", 256)
7661
self.start_subtest("Inject noise at 250Hz and check the FFT can find the noise")
7662
7663
self.reboot_sitl()
7664
7665
# find a motor peak
7666
self.hover_and_check_matched_frequency(-15, 100, 350, 256, 250)
7667
self.set_parameter("FFT_WINDOW_SIZE", 128)
7668
7669
# Step 2: inject actual motor noise and use the standard length FFT to track it
7670
self.start_subtest("Hover and check that the FFT can find the motor noise")
7671
self.set_parameters({
7672
"SIM_VIB_FREQ_X": 0,
7673
"SIM_VIB_FREQ_Y": 0,
7674
"SIM_VIB_FREQ_Z": 0,
7675
"SIM_VIB_MOT_MAX": 250, # gives a motor peak at about 175Hz
7676
"FFT_WINDOW_SIZE": 32,
7677
"FFT_WINDOW_OLAP": 0.5,
7678
})
7679
7680
self.reboot_sitl()
7681
freq = self.hover_and_check_matched_frequency(-15, 100, 250, 32)
7682
7683
self.set_parameter("SIM_VIB_MOT_MULT", 1.)
7684
7685
# Step 3: add a FFT dynamic notch and check that the peak is squashed
7686
self.start_subtest("Add a dynamic notch, hover and check that the noise peak is now gone")
7687
self.set_parameters({
7688
"INS_LOG_BAT_OPT": 2,
7689
"INS_HNTCH_ENABLE": 1,
7690
"INS_HNTCH_FREQ": freq,
7691
"INS_HNTCH_REF": 1.0,
7692
"INS_HNTCH_ATT": 50,
7693
"INS_HNTCH_BW": freq/2,
7694
"INS_HNTCH_MODE": 4,
7695
})
7696
self.reboot_sitl()
7697
7698
# do test flight:
7699
self.takeoff(10, mode="ALT_HOLD")
7700
tstart, tend, hover_throttle = self.hover_for_interval(15)
7701
# fly fast forrest!
7702
self.set_rc(3, 1900)
7703
self.set_rc(2, 1200)
7704
self.wait_groundspeed(5, 1000)
7705
self.set_rc(3, 1500)
7706
self.set_rc(2, 1500)
7707
self.do_RTL()
7708
7709
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
7710
7711
# batch sampler defaults give 1024 fft and sample rate of 1kz so roughly 1hz/bin
7712
scale = 1000. / 1024.
7713
sminhz = int(100 * scale)
7714
smaxhz = int(350 * scale)
7715
freq = psd["F"][numpy.argmax(psd["X"][sminhz:smaxhz]) + sminhz]
7716
peakdb = numpy.amax(psd["X"][sminhz:smaxhz])
7717
if peakdb < 0:
7718
self.progress("Did not detect a motor peak, found %fHz at %fdB" % (freq, peakdb))
7719
else:
7720
raise NotAchievedException("Detected %fHz motor peak at %fdB" % (freq, peakdb))
7721
7722
# Step 4: loop sample rate test with larger window
7723
self.start_subtest("Hover and check that the FFT can find the motor noise when running at fast loop rate")
7724
# we are limited to half the loop rate for frequency detection
7725
self.set_parameters({
7726
"FFT_MAXHZ": 185,
7727
"INS_LOG_BAT_OPT": 4,
7728
"SIM_VIB_MOT_MAX": 220,
7729
"FFT_WINDOW_SIZE": 64,
7730
"FFT_WINDOW_OLAP": 0.75,
7731
"FFT_SAMPLE_MODE": 1,
7732
})
7733
self.reboot_sitl()
7734
7735
# do test flight:
7736
self.takeoff(10, mode="ALT_HOLD")
7737
tstart, tend, hover_throttle = self.hover_for_interval(15)
7738
self.do_RTL()
7739
7740
# why are we not checking the results from that flight? -pb20220613
7741
7742
# prevent update parameters from messing with the settings
7743
# when we pop the context
7744
self.set_parameter("FFT_ENABLE", 0)
7745
7746
def GyroFFTAverage(self):
7747
"""Use dynamic harmonic notch to control motor noise setup via FFT averaging."""
7748
# basic gyro sample rate test
7749
self.progress("Flying with gyro FFT harmonic - Gyro sample rate")
7750
# Step 1
7751
self.start_subtest("Hover to calculate approximate hover frequency and see that it is tracked")
7752
# magic tridge EKF type that dramatically speeds up the test
7753
self.set_parameters({
7754
"INS_HNTCH_ATT": 100,
7755
"AHRS_EKF_TYPE": 10,
7756
"EK2_ENABLE": 0,
7757
"EK3_ENABLE": 0,
7758
"INS_LOG_BAT_MASK": 3,
7759
"INS_LOG_BAT_OPT": 2,
7760
"INS_GYRO_FILTER": 100,
7761
"INS_FAST_SAMPLE": 0,
7762
"LOG_BITMASK": 958,
7763
"LOG_DISARMED": 0,
7764
"SIM_DRIFT_SPEED": 0,
7765
"SIM_DRIFT_TIME": 0,
7766
"SIM_GYR1_RND": 20, # enable a noisy gyro
7767
})
7768
# motor peak enabling FFT will also enable the arming
7769
# check, self-testing the functionality
7770
self.set_parameters({
7771
"FFT_ENABLE": 1,
7772
"FFT_WINDOW_SIZE": 64, # not the default, but makes the test more reliable
7773
"FFT_SNR_REF": 10,
7774
"FFT_MINHZ": 80,
7775
"FFT_MAXHZ": 450,
7776
})
7777
7778
# Step 1: inject actual motor noise and use the FFT to track it
7779
self.set_parameters({
7780
"SIM_VIB_MOT_MAX": 250, # gives a motor peak at about 175Hz
7781
"RC7_OPTION" : 162, # FFT tune
7782
})
7783
7784
self.reboot_sitl()
7785
7786
# hover and engage FFT tracker
7787
self.takeoff(10, mode="ALT_HOLD")
7788
7789
hover_time = 60
7790
7791
# start the tune
7792
self.set_rc(7, 2000)
7793
7794
tstart, tend, hover_throttle = self.hover_for_interval(hover_time)
7795
7796
# finish the tune
7797
self.set_rc(7, 1000)
7798
7799
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
7800
7801
# batch sampler defaults give 1024 fft and sample rate of 1kz so roughly 1hz/bin
7802
freq = psd["F"][numpy.argmax(psd["X"][50:450]) + 50] * (1000. / 1024.)
7803
7804
detected_ref = self.get_parameter("INS_HNTCH_REF")
7805
detected_freq = self.get_parameter("INS_HNTCH_FREQ")
7806
self.progress("FFT detected parameters were %fHz, ref %f" % (detected_freq, detected_ref))
7807
7808
# approximate the scaled frequency
7809
scaled_freq_at_hover = math.sqrt((hover_throttle / 100.) / detected_ref) * detected_freq
7810
7811
# Check we matched
7812
if abs(scaled_freq_at_hover - freq) / scaled_freq_at_hover > 0.05:
7813
raise NotAchievedException("Detected frequency %fHz did not match required %fHz" %
7814
(scaled_freq_at_hover, freq))
7815
7816
if self.get_parameter("INS_HNTCH_ENABLE") != 1:
7817
raise NotAchievedException("Harmonic notch was not enabled")
7818
7819
# Step 2: now rerun the test and check that the peak is squashed
7820
self.start_subtest("Verify that noise is suppressed by the harmonic notch")
7821
self.hover_and_check_matched_frequency_with_fft(0, 100, 350, reverse=True, takeoff=False)
7822
7823
# reset notch to defaults
7824
self.set_parameters({
7825
"INS_HNTCH_HMNCS": 3.0,
7826
"INS_HNTCH_ENABLE": 0.0,
7827
"INS_HNTCH_REF": 0.0,
7828
"INS_HNTCH_FREQ": 80,
7829
"INS_HNTCH_BW": 40,
7830
"INS_HNTCH_FM_RAT": 1.0
7831
})
7832
7833
# Step 3: add a second harmonic and check the first is still tracked
7834
self.start_subtest("Add a fixed frequency harmonic at twice the hover frequency "
7835
"and check the right harmonic is found")
7836
self.set_parameters({
7837
"SIM_VIB_FREQ_X": detected_freq * 2,
7838
"SIM_VIB_FREQ_Y": detected_freq * 2,
7839
"SIM_VIB_FREQ_Z": detected_freq * 2,
7840
"SIM_VIB_MOT_MULT": 0.25, # halve the motor noise so that the higher harmonic dominates
7841
})
7842
self.reboot_sitl()
7843
7844
# hover and engage FFT tracker
7845
self.takeoff(10, mode="ALT_HOLD")
7846
7847
hover_time = 60
7848
7849
# start the tune
7850
self.set_rc(7, 2000)
7851
7852
tstart, tend, hover_throttle = self.hover_for_interval(hover_time)
7853
7854
# finish the tune
7855
self.set_rc(7, 1000)
7856
7857
self.do_RTL()
7858
7859
detected_ref = self.get_parameter("INS_HNTCH_REF")
7860
detected_freq = self.get_parameter("INS_HNTCH_FREQ")
7861
self.progress("FFT detected parameters were %fHz, ref %f" % (detected_freq, detected_ref))
7862
7863
# approximate the scaled frequency
7864
scaled_freq_at_hover = math.sqrt((hover_throttle / 100.) / detected_ref) * detected_freq
7865
7866
# Check we matched
7867
if abs(scaled_freq_at_hover - freq) / scaled_freq_at_hover > 0.05:
7868
raise NotAchievedException("Detected frequency %fHz did not match required %fHz" %
7869
(scaled_freq_at_hover, freq))
7870
7871
if self.get_parameter("INS_HNTCH_ENABLE") != 1:
7872
raise NotAchievedException("Harmonic notch was not enabled")
7873
7874
self.set_parameters({
7875
"SIM_VIB_FREQ_X": 0,
7876
"SIM_VIB_FREQ_Y": 0,
7877
"SIM_VIB_FREQ_Z": 0,
7878
"SIM_VIB_MOT_MULT": 1.0,
7879
"INS_HNTCH_HMNCS": 3.0,
7880
"INS_HNTCH_ENABLE": 0.0,
7881
"INS_HNTCH_REF": 0.0,
7882
"INS_HNTCH_FREQ": 80,
7883
"INS_HNTCH_BW": 40,
7884
"INS_HNTCH_FM_RAT": 1.0
7885
})
7886
# prevent update parameters from messing with the settings when we pop the context
7887
self.set_parameter("FFT_ENABLE", 0)
7888
7889
def GyroFFTPostFilter(self):
7890
"""Use FFT-driven dynamic harmonic notch to control post-RPM filter motor noise."""
7891
# basic gyro sample rate test
7892
self.progress("Flying with gyro FFT post-filter suppression - Gyro sample rate")
7893
# This set of parameters creates two noise peaks one at the motor frequency and one at 250Hz
7894
# we then use ESC telemetry to drive the notch to clean up the motor noise and a post-filter
7895
# FFT notch to clean up the remaining 250Hz. If either notch fails then the test will be failed
7896
# due to too much noise being present
7897
self.set_parameters({
7898
"AHRS_EKF_TYPE": 10, # magic tridge EKF type that dramatically speeds up the test
7899
"EK2_ENABLE": 0,
7900
"EK3_ENABLE": 0,
7901
"INS_LOG_BAT_MASK": 3,
7902
"INS_LOG_BAT_OPT": 4,
7903
"INS_GYRO_FILTER": 100,
7904
"INS_FAST_SAMPLE": 3,
7905
"LOG_BITMASK": 958,
7906
"LOG_DISARMED": 0,
7907
"SIM_DRIFT_SPEED": 0,
7908
"SIM_DRIFT_TIME": 0,
7909
"SIM_GYR1_RND": 20, # enable a noisy gyro
7910
"INS_HNTCH_ENABLE": 1,
7911
"INS_HNTCH_FREQ": 80,
7912
"INS_HNTCH_REF": 1.0,
7913
"INS_HNTCH_HMNCS": 1, # first harmonic
7914
"INS_HNTCH_ATT": 50,
7915
"INS_HNTCH_BW": 30,
7916
"INS_HNTCH_MODE": 3, # ESC telemetry
7917
"INS_HNTCH_OPTS": 2, # notch-per-motor
7918
"INS_HNTC2_ENABLE": 1,
7919
"INS_HNTC2_FREQ": 80,
7920
"INS_HNTC2_REF": 1.0,
7921
"INS_HNTC2_HMNCS": 1,
7922
"INS_HNTC2_ATT": 50,
7923
"INS_HNTC2_BW": 40,
7924
"INS_HNTC2_MODE": 4, # in-flight FFT
7925
"INS_HNTC2_OPTS": 18, # triple-notch, notch-per-FFT peak
7926
"FFT_ENABLE": 1,
7927
"FFT_WINDOW_SIZE": 64, # not the default, but makes the test more reliable
7928
"FFT_OPTIONS": 1,
7929
"FFT_MINHZ": 50,
7930
"FFT_MAXHZ": 450,
7931
"SIM_VIB_MOT_MAX": 250, # gives a motor peak at about 145Hz
7932
"SIM_VIB_FREQ_X": 250, # create another peak at 250hz
7933
"SIM_VIB_FREQ_Y": 250,
7934
"SIM_VIB_FREQ_Z": 250,
7935
"SIM_GYR_FILE_RW": 2, # write data to a file
7936
})
7937
self.reboot_sitl()
7938
7939
# do test flight:
7940
self.takeoff(10, mode="ALT_HOLD")
7941
tstart, tend, hover_throttle = self.hover_for_interval(60)
7942
# fly fast forrest!
7943
self.set_rc(3, 1900)
7944
self.set_rc(2, 1200)
7945
self.wait_groundspeed(5, 1000)
7946
self.set_rc(3, 1500)
7947
self.set_rc(2, 1500)
7948
self.do_RTL()
7949
7950
psd = self.mavfft_fttd(1, 2, tstart * 1.0e6, tend * 1.0e6)
7951
7952
# batch sampler defaults give 1024 fft and sample rate of 1kz so roughly 1hz/bin
7953
scale = 1000. / 1024.
7954
sminhz = int(100 * scale)
7955
smaxhz = int(350 * scale)
7956
freq = psd["F"][numpy.argmax(psd["X"][sminhz:smaxhz]) + sminhz]
7957
peakdb = numpy.amax(psd["X"][sminhz:smaxhz])
7958
if peakdb < -5:
7959
self.progress("Did not detect a motor peak, found %fHz at %fdB" % (freq, peakdb))
7960
else:
7961
raise NotAchievedException("Detected %fHz motor peak at %fdB" % (freq, peakdb))
7962
7963
# prevent update parameters from messing with the settings when we pop the context
7964
self.set_parameters({
7965
"SIM_VIB_FREQ_X": 0,
7966
"SIM_VIB_FREQ_Y": 0,
7967
"SIM_VIB_FREQ_Z": 0,
7968
"SIM_VIB_MOT_MULT": 1.0,
7969
"SIM_GYR_FILE_RW": 0, # stop writing data
7970
"FFT_ENABLE": 0,
7971
})
7972
7973
def GyroFFTMotorNoiseCheck(self):
7974
"""Use FFT to detect post-filter motor noise."""
7975
# basic gyro sample rate test
7976
self.progress("Flying with FFT motor-noise detection - Gyro sample rate")
7977
# This set of parameters creates two noise peaks one at the motor frequency and one at 250Hz
7978
# we then use ESC telemetry to drive the notch to clean up the motor noise and a post-filter
7979
# FFT notch to clean up the remaining 250Hz. If either notch fails then the test will be failed
7980
# due to too much noise being present
7981
self.set_parameters({
7982
"AHRS_EKF_TYPE": 10, # magic tridge EKF type that dramatically speeds up the test
7983
"EK2_ENABLE": 0,
7984
"EK3_ENABLE": 0,
7985
"INS_LOG_BAT_MASK": 3,
7986
"INS_LOG_BAT_OPT": 4,
7987
"INS_GYRO_FILTER": 100,
7988
"INS_FAST_SAMPLE": 3,
7989
"LOG_BITMASK": 958,
7990
"LOG_DISARMED": 0,
7991
"SIM_DRIFT_SPEED": 0,
7992
"SIM_DRIFT_TIME": 0,
7993
"SIM_GYR1_RND": 200, # enable a noisy gyro
7994
"INS_HNTCH_ENABLE": 1,
7995
"INS_HNTCH_FREQ": 80,
7996
"INS_HNTCH_REF": 1.0,
7997
"INS_HNTCH_HMNCS": 1, # first harmonic
7998
"INS_HNTCH_ATT": 50,
7999
"INS_HNTCH_BW": 30,
8000
"INS_HNTCH_MODE": 3, # ESC telemetry
8001
"INS_HNTCH_OPTS": 2, # notch-per-motor
8002
"INS_HNTC2_ENABLE": 1,
8003
"INS_HNTC2_FREQ": 80,
8004
"INS_HNTC2_REF": 1.0,
8005
"INS_HNTC2_HMNCS": 1,
8006
"INS_HNTC2_ATT": 50,
8007
"INS_HNTC2_BW": 40,
8008
"INS_HNTC2_MODE": 0, # istatic notch
8009
"INS_HNTC2_OPTS": 16, # triple-notch
8010
"FFT_ENABLE": 1,
8011
"FFT_WINDOW_SIZE": 64, # not the default, but makes the test more reliable
8012
"FFT_OPTIONS": 3,
8013
"FFT_MINHZ": 50,
8014
"FFT_MAXHZ": 450,
8015
"SIM_VIB_MOT_MAX": 250, # gives a motor peak at about 145Hz
8016
"SIM_VIB_FREQ_X": 250, # create another peak at 250hz
8017
"SIM_VIB_FREQ_Y": 250,
8018
"SIM_VIB_FREQ_Z": 250,
8019
"SIM_GYR_FILE_RW": 2, # write data to a file
8020
})
8021
self.reboot_sitl()
8022
8023
# do test flight:
8024
self.takeoff(10, mode="ALT_HOLD")
8025
tstart, tend, hover_throttle = self.hover_for_interval(10)
8026
self.wait_statustext("Noise ", timeout=20)
8027
self.set_parameter("SIM_GYR1_RND", 0) # stop noise so that we can get home
8028
self.do_RTL()
8029
8030
# prevent update parameters from messing with the settings when we pop the context
8031
self.set_parameters({
8032
"SIM_VIB_FREQ_X": 0,
8033
"SIM_VIB_FREQ_Y": 0,
8034
"SIM_VIB_FREQ_Z": 0,
8035
"SIM_VIB_MOT_MULT": 1.0,
8036
"SIM_GYR_FILE_RW": 0, # stop writing data
8037
"FFT_ENABLE": 0,
8038
})
8039
8040
def BrakeMode(self):
8041
'''Fly Brake Mode'''
8042
# test brake mode
8043
self.progress("Testing brake mode")
8044
self.takeoff(10, mode="LOITER")
8045
8046
self.progress("Ensuring RC inputs have no effect in brake mode")
8047
self.change_mode("STABILIZE")
8048
self.set_rc(3, 1500)
8049
self.set_rc(2, 1200)
8050
self.wait_groundspeed(5, 1000)
8051
8052
self.change_mode("BRAKE")
8053
self.wait_groundspeed(0, 1)
8054
8055
self.set_rc(2, 1500)
8056
8057
self.do_RTL()
8058
self.progress("Ran brake mode")
8059
8060
def fly_guided_move_to(self, destination, timeout=30):
8061
'''move to mavutil.location location; absolute altitude'''
8062
tstart = self.get_sim_time()
8063
self.mav.mav.set_position_target_global_int_send(
8064
0, # timestamp
8065
1, # target system_id
8066
1, # target component id
8067
mavutil.mavlink.MAV_FRAME_GLOBAL_INT,
8068
MAV_POS_TARGET_TYPE_MASK.POS_ONLY | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE, # mask specifying use-only-lat-lon-alt
8069
int(destination.lat * 1e7), # lat
8070
int(destination.lng * 1e7), # lon
8071
destination.alt, # alt
8072
0, # vx
8073
0, # vy
8074
0, # vz
8075
0, # afx
8076
0, # afy
8077
0, # afz
8078
0, # yaw
8079
0, # yawrate
8080
)
8081
while True:
8082
if self.get_sim_time() - tstart > timeout:
8083
raise NotAchievedException()
8084
delta = self.get_distance(self.mav.location(), destination)
8085
self.progress("delta=%f (want <1)" % delta)
8086
if delta < 1:
8087
break
8088
8089
def AltTypes(self):
8090
'''Test Different Altitude Types'''
8091
'''start by disabling GCS failsafe, otherwise we immediately disarm
8092
due to (apparently) not receiving traffic from the GCS for
8093
too long. This is probably a function of --speedup'''
8094
8095
'''this test flies the vehicle somewhere lower than were it started.
8096
It then disarms. It then arms, which should reset home to the
8097
new, lower altitude. This delta should be outside 1m but
8098
within a few metres of the old one.
8099
8100
'''
8101
8102
self.install_terrain_handlers_context()
8103
8104
self.set_parameter("FS_GCS_ENABLE", 0)
8105
self.change_mode('GUIDED')
8106
self.wait_ready_to_arm()
8107
self.arm_vehicle()
8108
m = self.assert_receive_message('GLOBAL_POSITION_INT')
8109
max_initial_home_alt_m = 500
8110
if m.relative_alt > max_initial_home_alt_m:
8111
raise NotAchievedException("Initial home alt too high (%fm > %fm)" %
8112
(m.relative_alt*1000, max_initial_home_alt_m*1000))
8113
orig_home_offset_mm = m.alt - m.relative_alt
8114
self.user_takeoff(5)
8115
8116
self.progress("Flying to low position")
8117
current_alt = self.mav.location().alt
8118
# 10m delta low_position = mavutil.location(-35.358273, 149.169165, current_alt, 0)
8119
low_position = mavutil.location(-35.36200016, 149.16415599, current_alt, 0)
8120
self.fly_guided_move_to(low_position, timeout=240)
8121
self.change_mode('LAND')
8122
# expecting home to change when disarmed
8123
self.wait_landed_and_disarmed()
8124
# wait a while for home to move (it shouldn't):
8125
self.delay_sim_time(10)
8126
m = self.assert_receive_message('GLOBAL_POSITION_INT')
8127
new_home_offset_mm = m.alt - m.relative_alt
8128
home_offset_delta_mm = orig_home_offset_mm - new_home_offset_mm
8129
self.progress("new home offset: %f delta=%f" %
8130
(new_home_offset_mm, home_offset_delta_mm))
8131
self.progress("gpi=%s" % str(m))
8132
max_home_offset_delta_mm = 10
8133
if home_offset_delta_mm > max_home_offset_delta_mm:
8134
raise NotAchievedException("Large home offset delta: want<%f got=%f" %
8135
(max_home_offset_delta_mm, home_offset_delta_mm))
8136
self.progress("Ensuring home moves when we arm")
8137
self.change_mode('GUIDED')
8138
self.wait_ready_to_arm()
8139
self.arm_vehicle()
8140
m = self.assert_receive_message('GLOBAL_POSITION_INT')
8141
post_arming_home_offset_mm = m.alt - m.relative_alt
8142
self.progress("post-arming home offset: %f" % (post_arming_home_offset_mm))
8143
self.progress("gpi=%s" % str(m))
8144
min_post_arming_home_offset_delta_mm = -2500
8145
max_post_arming_home_offset_delta_mm = -4000
8146
delta_between_original_home_alt_offset_and_new_home_alt_offset_mm = post_arming_home_offset_mm - orig_home_offset_mm
8147
self.progress("delta=%f-%f=%f" % (
8148
post_arming_home_offset_mm,
8149
orig_home_offset_mm,
8150
delta_between_original_home_alt_offset_and_new_home_alt_offset_mm))
8151
self.progress("Home moved %fm vertically" % (delta_between_original_home_alt_offset_and_new_home_alt_offset_mm/1000.0))
8152
if delta_between_original_home_alt_offset_and_new_home_alt_offset_mm > min_post_arming_home_offset_delta_mm:
8153
raise NotAchievedException(
8154
"Home did not move vertically on arming: want<=%f got=%f" %
8155
(min_post_arming_home_offset_delta_mm, delta_between_original_home_alt_offset_and_new_home_alt_offset_mm))
8156
if delta_between_original_home_alt_offset_and_new_home_alt_offset_mm < max_post_arming_home_offset_delta_mm:
8157
raise NotAchievedException(
8158
"Home moved too far vertically on arming: want>=%f got=%f" %
8159
(max_post_arming_home_offset_delta_mm, delta_between_original_home_alt_offset_and_new_home_alt_offset_mm))
8160
8161
self.wait_disarmed()
8162
8163
def PrecisionLoiterCompanion(self):
8164
"""Use Companion PrecLand backend precision messages to loiter."""
8165
8166
self.set_parameters({
8167
"PLND_ENABLED": 1,
8168
"PLND_TYPE": 1, # enable companion backend:
8169
"RC7_OPTION": 39, # set up a channel switch to enable precision loiter:
8170
})
8171
self.set_analog_rangefinder_parameters()
8172
self.reboot_sitl()
8173
8174
self.progress("Waiting for location")
8175
self.change_mode('LOITER')
8176
self.wait_ready_to_arm()
8177
8178
# we should be doing precision loiter at this point
8179
start = self.assert_receive_message('LOCAL_POSITION_NED')
8180
8181
self.takeoff(20, mode='ALT_HOLD')
8182
8183
# move away a little
8184
self.set_rc(2, 1550)
8185
self.wait_distance(5, accuracy=1)
8186
self.set_rc(2, 1500)
8187
self.change_mode('LOITER')
8188
8189
# turn precision loiter on:
8190
self.context_collect('STATUSTEXT')
8191
self.set_rc(7, 2000)
8192
8193
# try to drag aircraft to a position 5 metres north-east-east:
8194
self.precision_loiter_to_pos(start.x + 5, start.y + 10, start.z + 10, True)
8195
self.wait_statustext("PrecLand: Target Found", check_context=True, timeout=10)
8196
self.wait_statustext("PrecLand: Init Complete", check_context=True, timeout=10)
8197
# .... then northwest
8198
self.precision_loiter_to_pos(start.x + 5, start.y - 10, start.z + 10, True)
8199
8200
# repeat using earth-frame targets
8201
self.precision_loiter_to_pos(start.x + 5, start.y - 10, start.z + 10, False)
8202
8203
self.disarm_vehicle(force=True)
8204
8205
def loiter_requires_position(self):
8206
# ensure we can't switch to LOITER without position
8207
self.progress("Ensure we can't enter LOITER without position")
8208
self.context_push()
8209
self.set_parameters({
8210
"GPS1_TYPE": 2,
8211
"SIM_GPS1_ENABLE": 0,
8212
})
8213
# if there is no GPS at all then we must direct EK3 to not use
8214
# it at all. Otherwise it will never initialise, as it wants
8215
# to calculate the lag and size its delay buffers accordingly.
8216
self.set_parameters({
8217
"EK3_SRC1_POSXY": 0,
8218
"EK3_SRC1_VELZ": 0,
8219
"EK3_SRC1_VELXY": 0,
8220
})
8221
self.reboot_sitl()
8222
self.delay_sim_time(30) # wait for accels/gyros to settle
8223
8224
# check for expected EKF flags
8225
ahrs_ekf_type = self.get_parameter("AHRS_EKF_TYPE")
8226
expected_ekf_flags = (mavutil.mavlink.ESTIMATOR_ATTITUDE |
8227
mavutil.mavlink.ESTIMATOR_VELOCITY_VERT |
8228
mavutil.mavlink.ESTIMATOR_POS_VERT_ABS |
8229
mavutil.mavlink.ESTIMATOR_CONST_POS_MODE)
8230
if ahrs_ekf_type == 2:
8231
expected_ekf_flags = expected_ekf_flags | mavutil.mavlink.ESTIMATOR_PRED_POS_HORIZ_REL
8232
self.wait_ekf_flags(expected_ekf_flags, 0, timeout=120)
8233
8234
# arm in Stabilize and attempt to switch to Loiter
8235
self.change_mode('STABILIZE')
8236
self.arm_vehicle()
8237
self.context_collect('STATUSTEXT')
8238
self.run_cmd_do_set_mode(
8239
"LOITER",
8240
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
8241
self.wait_statustext("requires position", check_context=True)
8242
self.disarm_vehicle()
8243
self.context_pop()
8244
self.reboot_sitl()
8245
8246
def ArmFeatures(self):
8247
'''Arm features'''
8248
self.loiter_requires_position()
8249
8250
super(AutoTestCopter, self).ArmFeatures()
8251
8252
def ParameterChecks(self):
8253
'''Test Arming Parameter Checks'''
8254
self.test_parameter_checks_poscontrol("PSC")
8255
8256
def PosHoldTakeOff(self):
8257
"""ensure vehicle stays put until it is ready to fly"""
8258
self.context_push()
8259
8260
self.set_parameter("PILOT_TKOFF_ALT", 700)
8261
self.change_mode('POSHOLD')
8262
self.set_rc(3, 1000)
8263
self.wait_ready_to_arm()
8264
self.arm_vehicle()
8265
self.delay_sim_time(2)
8266
# check we are still on the ground...
8267
relative_alt = self.get_altitude(relative=True)
8268
if relative_alt > 0.1:
8269
raise NotAchievedException("Took off prematurely")
8270
8271
self.progress("Pushing throttle up")
8272
self.set_rc(3, 1710)
8273
self.delay_sim_time(0.5)
8274
self.progress("Bringing back to hover throttle")
8275
self.set_rc(3, 1500)
8276
8277
# make sure we haven't already reached alt:
8278
relative_alt = self.get_altitude(relative=True)
8279
max_initial_alt = 2.0
8280
if abs(relative_alt) > max_initial_alt:
8281
raise NotAchievedException("Took off too fast (%f > %f" %
8282
(relative_alt, max_initial_alt))
8283
8284
self.progress("Monitoring takeoff-to-alt")
8285
self.wait_altitude(6.9, 8, relative=True, minimum_duration=10)
8286
self.progress("takeoff OK")
8287
8288
self.land_and_disarm()
8289
self.set_rc(8, 1000)
8290
8291
self.context_pop()
8292
8293
def initial_mode(self):
8294
return "STABILIZE"
8295
8296
def initial_mode_switch_mode(self):
8297
return "STABILIZE"
8298
8299
def default_mode(self):
8300
return "STABILIZE"
8301
8302
def rc_defaults(self):
8303
ret = super(AutoTestCopter, self).rc_defaults()
8304
ret[3] = 1000
8305
ret[5] = 1800 # mode switch
8306
return ret
8307
8308
def MANUAL_CONTROL(self):
8309
'''test MANUAL_CONTROL mavlink message'''
8310
self.set_parameter("MAV_GCS_SYSID", self.mav.source_system)
8311
8312
self.change_mode('STABILIZE')
8313
self.takeoff(10)
8314
8315
tstart = self.get_sim_time_cached()
8316
want_pitch_degrees = -12
8317
while True:
8318
if self.get_sim_time_cached() - tstart > 10:
8319
raise AutoTestTimeoutException("Did not reach pitch")
8320
self.progress("Sending pitch-forward")
8321
self.mav.mav.manual_control_send(
8322
1, # target system
8323
500, # x (pitch)
8324
32767, # y (roll)
8325
32767, # z (thrust)
8326
32767, # r (yaw)
8327
0) # button mask
8328
m = self.assert_receive_message('ATTITUDE', verbose=True)
8329
p = math.degrees(m.pitch)
8330
self.progress("pitch=%f want<=%f" % (p, want_pitch_degrees))
8331
if p <= want_pitch_degrees:
8332
break
8333
self.mav.mav.manual_control_send(
8334
1, # target system
8335
32767, # x (pitch)
8336
32767, # y (roll)
8337
32767, # z (thrust)
8338
32767, # r (yaw)
8339
0) # button mask
8340
self.do_RTL()
8341
8342
def check_avoidance_corners(self):
8343
self.takeoff(10, mode="LOITER")
8344
here = self.mav.location()
8345
self.set_rc(2, 1400)
8346
west_loc = mavutil.location(-35.363007,
8347
149.164911,
8348
here.alt,
8349
0)
8350
self.wait_location(west_loc, accuracy=6)
8351
north_loc = mavutil.location(-35.362908,
8352
149.165051,
8353
here.alt,
8354
0)
8355
self.reach_heading_manual(0)
8356
self.wait_location(north_loc, accuracy=6, timeout=200)
8357
self.reach_heading_manual(90)
8358
east_loc = mavutil.location(-35.363013,
8359
149.165194,
8360
here.alt,
8361
0)
8362
self.wait_location(east_loc, accuracy=6)
8363
self.reach_heading_manual(225)
8364
self.wait_location(west_loc, accuracy=6, timeout=200)
8365
self.set_rc(2, 1500)
8366
self.do_RTL()
8367
8368
def OBSTACLE_DISTANCE_3D_test_angle(self, angle):
8369
now = self.get_sim_time_cached()
8370
8371
distance = 15
8372
right = distance * math.sin(math.radians(angle))
8373
front = distance * math.cos(math.radians(angle))
8374
down = 0
8375
8376
expected_distance_cm = distance * 100
8377
# expected orientation
8378
expected_orientation = int((angle+22.5)/45) % 8
8379
self.progress("Angle %f expected orient %u" %
8380
(angle, expected_orientation))
8381
8382
tstart = self.get_sim_time()
8383
last_send = 0
8384
m = None
8385
while True:
8386
now = self.get_sim_time_cached()
8387
if now - tstart > 100:
8388
raise NotAchievedException("Did not get correct angle back (last-message=%s)" % str(m))
8389
8390
if now - last_send > 0.1:
8391
self.progress("ang=%f sending front=%f right=%f" %
8392
(angle, front, right))
8393
self.mav.mav.obstacle_distance_3d_send(
8394
int(now*1000), # time_boot_ms
8395
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
8396
mavutil.mavlink.MAV_FRAME_BODY_FRD,
8397
65535,
8398
front, # x (m)
8399
right, # y (m)
8400
down, # z (m)
8401
0, # min_distance (m)
8402
20 # max_distance (m)
8403
)
8404
last_send = now
8405
m = self.mav.recv_match(type="DISTANCE_SENSOR",
8406
blocking=True,
8407
timeout=1)
8408
if m is None:
8409
continue
8410
# self.progress("Got (%s)" % str(m))
8411
if m.orientation != expected_orientation:
8412
# self.progress("Wrong orientation (want=%u got=%u)" %
8413
# (expected_orientation, m.orientation))
8414
continue
8415
if abs(m.current_distance - expected_distance_cm) > 1:
8416
# self.progress("Wrong distance (want=%f got=%f)" %
8417
# (expected_distance_cm, m.current_distance))
8418
continue
8419
self.progress("distance-at-angle good")
8420
break
8421
8422
def OBSTACLE_DISTANCE_3D(self):
8423
'''Check round-trip behaviour of distance sensors'''
8424
self.context_push()
8425
self.set_parameters({
8426
"SERIAL5_PROTOCOL": 1,
8427
"PRX1_TYPE": 2,
8428
"SIM_SPEEDUP": 8, # much GCS interaction
8429
})
8430
self.reboot_sitl()
8431
# need yaw estimate to stabilise:
8432
self.wait_ekf_happy(require_absolute=True)
8433
8434
for angle in range(0, 360):
8435
self.OBSTACLE_DISTANCE_3D_test_angle(angle)
8436
8437
self.context_pop()
8438
self.reboot_sitl()
8439
8440
def AC_Avoidance_Proximity(self):
8441
'''Test proximity avoidance slide behaviour'''
8442
8443
self.context_push()
8444
8445
self.load_fence("copter-avoidance-fence.txt")
8446
self.set_parameters({
8447
"FENCE_ENABLE": 1,
8448
"PRX1_TYPE": 10,
8449
"PRX_LOG_RAW": 1,
8450
"RC10_OPTION": 40, # proximity-enable
8451
})
8452
self.reboot_sitl()
8453
self.progress("Enabling proximity")
8454
self.set_rc(10, 2000)
8455
self.check_avoidance_corners()
8456
8457
self.assert_current_onboard_log_contains_message("PRX")
8458
self.assert_current_onboard_log_contains_message("PRXR")
8459
8460
self.disarm_vehicle(force=True)
8461
8462
self.context_pop()
8463
self.reboot_sitl()
8464
8465
def ProximitySensors(self):
8466
'''ensure proximity sensors return appropriate data'''
8467
8468
self.set_parameters({
8469
"SERIAL5_PROTOCOL": 11,
8470
"OA_DB_OUTPUT": 3,
8471
"OA_TYPE": 2,
8472
})
8473
sensors = [ # tuples of name, prx_type
8474
('ld06', 16, {
8475
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 275,
8476
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 256,
8477
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 1130,
8478
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 1200,
8479
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 625,
8480
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 967,
8481
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 760,
8482
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 765,
8483
}),
8484
('sf45b', 8, {
8485
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 270,
8486
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 258,
8487
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 1146,
8488
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 632,
8489
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 629,
8490
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 972,
8491
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 774,
8492
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 774,
8493
}),
8494
('rplidara2', 5, {
8495
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 277,
8496
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 256,
8497
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 1130,
8498
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 1288,
8499
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 626,
8500
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 970,
8501
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 762,
8502
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 790,
8503
}),
8504
('terarangertower', 3, {
8505
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 450,
8506
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 282,
8507
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 450,
8508
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 450,
8509
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 450,
8510
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 450,
8511
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 450,
8512
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 450,
8513
}),
8514
]
8515
8516
# the following is a "magic" location SITL understands which
8517
# has some posts near it:
8518
home_string = "%s,%s,%s,%s" % (51.8752066, 14.6487840, 54.15, 0)
8519
for (name, prx_type, expected_distances) in sensors:
8520
self.start_subtest("Testing %s" % name)
8521
self.set_parameter("PRX1_TYPE", prx_type)
8522
self.customise_SITL_commandline([
8523
"--serial5=sim:%s:" % name,
8524
"--home", home_string,
8525
])
8526
self.wait_ready_to_arm()
8527
expected_distances_copy = copy.copy(expected_distances)
8528
tstart = self.get_sim_time()
8529
failed = False
8530
wants = []
8531
gots = []
8532
epsilon = 20
8533
while True:
8534
if self.get_sim_time_cached() - tstart > 30:
8535
raise AutoTestTimeoutException("Failed to get distances")
8536
if len(expected_distances_copy.keys()) == 0:
8537
break
8538
m = self.assert_receive_message("DISTANCE_SENSOR")
8539
if m.orientation not in expected_distances_copy:
8540
continue
8541
got = m.current_distance
8542
want = expected_distances_copy[m.orientation]
8543
wants.append(want)
8544
gots.append(got)
8545
if abs(want - got) > epsilon:
8546
failed = True
8547
del expected_distances_copy[m.orientation]
8548
if failed:
8549
raise NotAchievedException(
8550
"Distance too great (%s) (want=%s != got=%s)" %
8551
(name, wants, gots))
8552
8553
def AC_Avoidance_Proximity_AVOID_ALT_MIN(self):
8554
'''Test proximity avoidance with AVOID_ALT_MIN'''
8555
self.set_parameters({
8556
"PRX1_TYPE": 2,
8557
"AVOID_ALT_MIN": 10,
8558
})
8559
self.set_analog_rangefinder_parameters()
8560
self.reboot_sitl()
8561
8562
self.change_mode('LOITER')
8563
self.wait_ekf_happy()
8564
8565
self.set_parameter("SIM_SPEEDUP", 20)
8566
8567
tstart = self.get_sim_time()
8568
while True:
8569
if self.armed(cached=True):
8570
break
8571
if self.get_sim_time_cached() - tstart > 60:
8572
raise AutoTestTimeoutException("Did not arm")
8573
self.mav.mav.distance_sensor_send(
8574
0, # time_boot_ms
8575
10, # min_distance cm
8576
500, # max_distance cm
8577
400, # current_distance cm
8578
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER, # type
8579
26, # id
8580
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE, # orientation
8581
255 # covariance
8582
)
8583
self.send_mavlink_arm_command()
8584
# make a minimal effort to not do ridiculous amounts of
8585
# mavlink to the autopilot:
8586
self.do_timesync_roundtrip()
8587
8588
self.takeoff(15, mode='LOITER')
8589
self.progress("Poking vehicle; should avoid")
8590
8591
def shove(a, b):
8592
self.mav.mav.distance_sensor_send(
8593
0, # time_boot_ms
8594
10, # min_distance cm
8595
500, # max_distance cm
8596
20, # current_distance cm
8597
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER, # type
8598
21, # id
8599
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE, # orientation
8600
255 # covariance
8601
)
8602
self.wait_speed_vector_bf(
8603
Vector3(-0.4, 0.0, 0.0),
8604
timeout=10,
8605
called_function=shove,
8606
)
8607
8608
self.change_alt(5)
8609
8610
tstart = self.get_sim_time()
8611
while True:
8612
if self.get_sim_time_cached() - tstart > 10:
8613
break
8614
vel = self.get_body_frame_velocity()
8615
if vel.length() > 0.5:
8616
raise NotAchievedException("Moved too much (%s)" %
8617
(str(vel),))
8618
shove(None, None)
8619
8620
self.disarm_vehicle(force=True)
8621
8622
def AC_Avoidance_Fence(self):
8623
'''Test fence avoidance slide behaviour'''
8624
self.load_fence("copter-avoidance-fence.txt")
8625
self.set_parameter("FENCE_ENABLE", 1)
8626
self.check_avoidance_corners()
8627
8628
def AvoidanceAltFence(self):
8629
'''Test fence avoidance at minimum and maximum altitude'''
8630
self.context_push()
8631
8632
self.set_parameters({
8633
"FENCE_ENABLE": 1,
8634
"FENCE_TYPE": 9, # min and max alt fence
8635
"FENCE_ALT_MIN": 10,
8636
"FENCE_ALT_MAX": 30,
8637
})
8638
8639
self.change_mode('LOITER')
8640
self.wait_ekf_happy()
8641
8642
tstart = self.get_sim_time()
8643
self.takeoff(15, mode='LOITER')
8644
self.progress("Increasing throttle, vehicle should stay below 30m")
8645
self.set_rc(3, 1920)
8646
8647
tstart = self.get_sim_time()
8648
while True:
8649
if self.get_sim_time_cached() - tstart > 20:
8650
break
8651
alt = self.get_altitude(relative=True)
8652
self.progress("Altitude %s" % alt)
8653
if alt > 30:
8654
raise NotAchievedException("Breached maximum altitude (%s)" % (str(alt),))
8655
8656
self.progress("Decreasing, vehicle should stay above 10m")
8657
self.set_rc(3, 1080)
8658
tstart = self.get_sim_time()
8659
while True:
8660
if self.get_sim_time_cached() - tstart > 20:
8661
break
8662
alt = self.get_altitude(relative=True)
8663
self.progress("Altitude %s" % alt)
8664
if alt < 10:
8665
raise NotAchievedException("Breached minimum altitude (%s)" % (str(alt),))
8666
self.context_pop()
8667
8668
self.do_RTL()
8669
8670
def mav_location_from_message_cache(self) -> mavutil.location:
8671
return mavutil.location(
8672
self.mav.messages['GPS_RAW_INT'].lat*1.0e-7,
8673
self.mav.messages['GPS_RAW_INT'].lon*1.0e-7,
8674
self.mav.messages['VFR_HUD'].alt, # relative alt only
8675
self.mav.messages['VFR_HUD'].heading
8676
)
8677
8678
def ModeFollow(self):
8679
'''Fly follow mode'''
8680
foll_ofs_x = 30 # metres
8681
self.set_parameters({
8682
"FOLL_ENABLE": 1,
8683
"FOLL_SYSID": self.mav.source_system,
8684
"FOLL_OFS_X": -foll_ofs_x,
8685
"FOLL_OFS_TYPE": 1, # relative to other vehicle heading
8686
})
8687
self.takeoff(10, mode="LOITER")
8688
self.context_push()
8689
self.set_parameter("SIM_SPEEDUP", 1)
8690
self.change_mode("FOLLOW")
8691
new_loc = self.mav.location()
8692
new_loc_offset_n = 20
8693
new_loc_offset_e = 30
8694
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
8695
self.progress("new_loc: %s" % str(new_loc))
8696
heading = 0
8697
if self.mavproxy is not None:
8698
self.mavproxy.send("map icon %f %f greenplane %f\n" %
8699
(new_loc.lat, new_loc.lng, heading))
8700
8701
expected_loc = copy.copy(new_loc)
8702
self.location_offset_ne(expected_loc, -foll_ofs_x, 0)
8703
if self.mavproxy is not None:
8704
self.mavproxy.send("map icon %f %f hoop\n" %
8705
(expected_loc.lat, expected_loc.lng))
8706
self.progress("expected_loc: %s" % str(expected_loc))
8707
8708
origin = self.poll_message('GPS_GLOBAL_ORIGIN')
8709
8710
last_sent = 0
8711
tstart = self.get_sim_time()
8712
while True:
8713
now = self.get_sim_time_cached()
8714
if now - tstart > 60:
8715
raise NotAchievedException("Did not FOLLOW")
8716
if now - last_sent > 0.5:
8717
gpi = self.mav.mav.global_position_int_encode(
8718
int(now * 1000), # time_boot_ms
8719
int(new_loc.lat * 1e7),
8720
int(new_loc.lng * 1e7),
8721
int(new_loc.alt * 1000), # alt in mm
8722
int(new_loc.alt * 1000 - origin.altitude), # relative alt - urp.
8723
vx=0,
8724
vy=0,
8725
vz=0,
8726
hdg=heading
8727
)
8728
gpi.pack(self.mav.mav)
8729
self.mav.mav.send(gpi)
8730
self.assert_receive_message('GLOBAL_POSITION_INT')
8731
pos = self.mav_location_from_message_cache()
8732
delta = self.get_distance(expected_loc, pos)
8733
max_delta = 3
8734
self.progress("position delta=%f (want <%f)" % (delta, max_delta))
8735
if delta < max_delta:
8736
break
8737
8738
self.start_subtest("Trying relative-follow mode")
8739
self.change_mode('LOITER')
8740
self.set_parameter('FOLL_ALT_TYPE', 1) # use relative-home data
8741
new_loc = self.mav.location()
8742
new_loc_offset_n = -40
8743
new_loc_offset_e = 60
8744
new_loc.alt += 1
8745
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
8746
expected_loc = copy.copy(new_loc)
8747
self.location_offset_ne(expected_loc, -foll_ofs_x, 0)
8748
if self.mavproxy is not None:
8749
self.mavproxy.send("map icon %f %f hoop\n" %
8750
(expected_loc.lat, expected_loc.lng))
8751
self.progress("expected_loc: %s" % str(expected_loc))
8752
self.progress("new_loc: %s" % str(new_loc))
8753
self.change_mode('FOLLOW')
8754
last_sent = 0
8755
tstart = self.get_sim_time()
8756
while True:
8757
now = self.get_sim_time_cached()
8758
if now - tstart > 60:
8759
raise NotAchievedException("Did not FOLLOW")
8760
if now - last_sent > 0.5:
8761
gpi = self.mav.mav.global_position_int_encode(
8762
int(now * 1000), # time_boot_ms
8763
int(new_loc.lat * 1e7),
8764
int(new_loc.lng * 1e7),
8765
666, # alt in mm - note incorrect data!
8766
int(new_loc.alt * 1000 - origin.altitude), # relative alt - urp.
8767
vx=0,
8768
vy=0,
8769
vz=0,
8770
hdg=heading
8771
)
8772
gpi.pack(self.mav.mav)
8773
self.mav.mav.send(gpi)
8774
self.assert_receive_message('GLOBAL_POSITION_INT')
8775
pos = self.mav_location_from_message_cache()
8776
delta = self.get_distance(expected_loc, pos)
8777
max_delta = 3
8778
self.progress("position delta=%f (want <%f)" % (delta, max_delta))
8779
if delta < max_delta:
8780
break
8781
8782
self.start_subtest("Trying follow-with-velocity mode")
8783
self.change_mode('LOITER')
8784
self.set_parameter('FOLL_ALT_TYPE', 1) # use relative-home data
8785
new_loc = self.mav.location()
8786
vel_n = 3 # m/s
8787
vel_e = 2
8788
vel_d = -1
8789
tstart = self.get_sim_time()
8790
first_loop = True
8791
last_sent = 0
8792
last_loop_s = tstart
8793
heading = math.atan2(vel_n, vel_e)
8794
while True:
8795
now = self.get_sim_time_cached()
8796
dt = now - last_loop_s
8797
last_loop_s = now
8798
new_loc_offset_n = vel_n * dt
8799
new_loc_offset_e = vel_e * dt
8800
new_loc_offset_u = vel_d * dt * -1
8801
8802
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
8803
new_loc.alt += new_loc_offset_u
8804
expected_loc = copy.copy(new_loc)
8805
self.location_offset_ne(expected_loc, -foll_ofs_x, 0)
8806
self.progress("expected_loc: %s" % str(expected_loc))
8807
self.progress("new_loc: %s" % str(new_loc))
8808
if first_loop:
8809
self.change_mode('FOLLOW')
8810
first_loop = False
8811
8812
if now - tstart > 60:
8813
raise NotAchievedException("Did not FOLLOW")
8814
if now - last_sent > 0.5:
8815
gpi = self.mav.mav.global_position_int_encode(
8816
int(now * 1000), # time_boot_ms
8817
int(new_loc.lat * 1e7),
8818
int(new_loc.lng * 1e7),
8819
666, # alt in mm - note incorrect data!
8820
int(new_loc.alt * 1000 - origin.altitude), # relative alt - urp.
8821
vx=int(vel_n*100),
8822
vy=int(vel_e*100),
8823
vz=int(vel_d*100),
8824
hdg=int(math.degrees(heading)*100) # rad->cdeg
8825
)
8826
gpi.pack(self.mav.mav)
8827
self.mav.mav.send(gpi)
8828
self.assert_receive_message('GLOBAL_POSITION_INT')
8829
pos = self.mav.location()
8830
delta = self.get_distance(expected_loc, pos)
8831
want_delta = foll_ofs_x
8832
self.progress("position delta=%f (want <%f)" % (delta, max_delta))
8833
if abs(want_delta - delta) < 3:
8834
break
8835
8836
self.context_pop()
8837
self.do_RTL()
8838
8839
def ModeFollow_with_FOLLOW_TARGET(self):
8840
'''test ModeFollow passing in FOLLOW_TARGET messages'''
8841
foll_ofs_x = 30 # metres
8842
self.set_parameters({
8843
"FOLL_ENABLE": 1,
8844
"FOLL_SYSID": self.mav.source_system,
8845
"FOLL_OFS_X": -foll_ofs_x,
8846
"FOLL_OFS_TYPE": 1, # relative to other vehicle heading
8847
})
8848
self.takeoff(10, mode="LOITER")
8849
self.context_push()
8850
self.set_parameter("SIM_SPEEDUP", 1)
8851
self.change_mode("FOLLOW")
8852
new_loc = self.mav.location()
8853
new_loc_offset_n = 40
8854
new_loc_offset_e = 60
8855
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
8856
self.progress("new_loc: %s" % str(new_loc))
8857
heading = 0
8858
if self.mavproxy is not None:
8859
self.mavproxy.send("map icon %f %f greenplane %f\n" %
8860
(new_loc.lat, new_loc.lng, heading))
8861
8862
expected_loc = copy.copy(new_loc)
8863
self.location_offset_ne(expected_loc, -foll_ofs_x, 0)
8864
if self.mavproxy is not None:
8865
self.mavproxy.send("map icon %f %f hoop\n" %
8866
(expected_loc.lat, expected_loc.lng))
8867
self.progress("expected_loc: %s" % str(expected_loc))
8868
8869
def send_target():
8870
vel_n = 3 # m/s
8871
vel_e = 2
8872
# vel_d = -1
8873
heading = math.atan2(vel_n, vel_e)
8874
attitude = quaternion.Quaternion([
8875
math.radians(0),
8876
math.radians(0),
8877
math.radians(heading)
8878
])
8879
now = self.get_sim_time_cached()
8880
self.mav.mav.follow_target_send(
8881
int(now * 1000), # time_boot_ms
8882
1 << 0 | 1 << 1 | 1 << 3, # pos, vel, att+rates
8883
int(new_loc.lat * 1e7),
8884
int(new_loc.lng * 1e7),
8885
new_loc.alt, # alt in m
8886
[0, 0, 0], # velocity m/s
8887
[0, 0, 0], # acceleration m/s/s
8888
attitude, # attitude quaternion
8889
[0, 0, 0], # rates
8890
[0, 0, 0], # covariances
8891
0 # custom state
8892
)
8893
self.wait_location(
8894
expected_loc,
8895
fn=send_target,
8896
fn_interval=0.5,
8897
accuracy=3,
8898
timeout=60,
8899
)
8900
8901
self.start_subtest("Trying follow-with-velocity mode")
8902
self.change_mode('LOITER')
8903
self.set_parameter('FOLL_ALT_TYPE', 1) # use relative-home data
8904
new_loc = self.mav.location()
8905
self.change_mode('FOLLOW')
8906
last_loop_s = self.get_sim_time_cached()
8907
8908
vel_n = 3 # m/s
8909
vel_e = 2
8910
vel_d = 0
8911
8912
mul = 40
8913
self.location_offset_ne(expected_loc, mul*vel_n, mul*vel_e)
8914
8915
def send_target_vel():
8916
nonlocal last_loop_s
8917
8918
now = self.get_sim_time_cached()
8919
dt = now - last_loop_s
8920
last_loop_s = now
8921
8922
new_loc_offset_n = vel_n * dt
8923
new_loc_offset_e = vel_e * dt
8924
new_loc_offset_u = vel_d * dt * -1
8925
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
8926
new_loc.alt += new_loc_offset_u
8927
8928
# update heading
8929
heading = math.atan2(vel_n, vel_e)
8930
attitude = quaternion.Quaternion([
8931
math.radians(0),
8932
math.radians(0),
8933
math.radians(heading)
8934
])
8935
now = self.get_sim_time_cached()
8936
self.mav.mav.follow_target_send(
8937
int(now * 1000), # time_boot_ms
8938
1 << 0 | 1 << 1 | 1 << 3, # pos, vel, att+rates
8939
int(new_loc.lat * 1e7),
8940
int(new_loc.lng * 1e7),
8941
new_loc.alt, # alt in m
8942
[vel_n, vel_e, vel_d], # velocity m/s
8943
[0, 0, 0], # acceleration m/s/s
8944
attitude, # attitude quaternion
8945
[0, 0, 0], # rates
8946
[0, 0, 0], # covariances
8947
0 # custom state
8948
)
8949
self.wait_speed_vector(
8950
Vector3(vel_n, vel_e, 0),
8951
timeout=100,
8952
called_function=lambda plop, empty: send_target_vel(),
8953
minimum_duration=2,
8954
)
8955
8956
self.context_pop()
8957
self.do_RTL()
8958
8959
def get_global_position_int(self, timeout=30):
8960
tstart = self.get_sim_time()
8961
while True:
8962
if self.get_sim_time_cached() - tstart > timeout:
8963
raise NotAchievedException("Did not get good global_position_int")
8964
m = self.assert_receive_message('GLOBAL_POSITION_INT')
8965
self.progress("GPI: %s" % str(m))
8966
if m is None:
8967
continue
8968
if m.lat != 0 or m.lon != 0:
8969
return m
8970
8971
def BeaconPosition(self):
8972
'''Fly Beacon Position'''
8973
self.reboot_sitl()
8974
8975
self.wait_ready_to_arm(require_absolute=True)
8976
8977
old_pos = self.get_global_position_int()
8978
print("old_pos=%s" % str(old_pos))
8979
8980
self.set_parameters({
8981
"BCN_TYPE": 10,
8982
"BCN_LATITUDE": SITL_START_LOCATION.lat,
8983
"BCN_LONGITUDE": SITL_START_LOCATION.lng,
8984
"BCN_ALT": SITL_START_LOCATION.alt,
8985
"BCN_ORIENT_YAW": 0,
8986
"AVOID_ENABLE": 4,
8987
"GPS1_TYPE": 0,
8988
"EK3_ENABLE": 1,
8989
"EK3_SRC1_POSXY": 4, # Beacon
8990
"EK3_SRC1_POSZ": 1, # Baro
8991
"EK3_SRC1_VELXY": 0, # None
8992
"EK3_SRC1_VELZ": 0, # None
8993
"EK2_ENABLE": 0,
8994
"AHRS_EKF_TYPE": 3,
8995
})
8996
self.reboot_sitl()
8997
8998
# require_absolute=True infers a GPS is present
8999
self.wait_ready_to_arm(require_absolute=False)
9000
9001
tstart = self.get_sim_time()
9002
timeout = 20
9003
while True:
9004
if self.get_sim_time_cached() - tstart > timeout:
9005
raise NotAchievedException("Did not get new position like old position")
9006
self.progress("Fetching location")
9007
new_pos = self.get_global_position_int()
9008
pos_delta = self.get_distance_int(old_pos, new_pos)
9009
max_delta = 1
9010
self.progress("delta=%u want <= %u" % (pos_delta, max_delta))
9011
if pos_delta <= max_delta:
9012
break
9013
9014
self.progress("Moving to ensure location is tracked")
9015
self.takeoff(10, mode="STABILIZE")
9016
self.change_mode("CIRCLE")
9017
9018
self.context_push()
9019
validator = vehicle_test_suite.TestSuite.ValidateGlobalPositionIntAgainstSimState(self, max_allowed_divergence=10)
9020
self.install_message_hook_context(validator)
9021
9022
self.delay_sim_time(20)
9023
self.progress("Tracked location just fine")
9024
self.context_pop()
9025
9026
self.change_mode("LOITER")
9027
self.wait_groundspeed(0, 0.3, timeout=120)
9028
self.land_and_disarm()
9029
9030
self.assert_current_onboard_log_contains_message("BCN")
9031
9032
self.disarm_vehicle(force=True)
9033
9034
def AC_Avoidance_Beacon(self):
9035
'''Test beacon avoidance slide behaviour'''
9036
self.context_push()
9037
ex = None
9038
try:
9039
self.set_parameters({
9040
"BCN_TYPE": 10,
9041
"BCN_LATITUDE": int(SITL_START_LOCATION.lat),
9042
"BCN_LONGITUDE": int(SITL_START_LOCATION.lng),
9043
"BCN_ORIENT_YAW": 45,
9044
"AVOID_ENABLE": 4,
9045
})
9046
self.reboot_sitl()
9047
9048
self.takeoff(10, mode="LOITER")
9049
self.set_rc(2, 1400)
9050
here = self.mav.location()
9051
west_loc = mavutil.location(-35.362919, 149.165055, here.alt, 0)
9052
self.wait_location(west_loc, accuracy=1)
9053
self.reach_heading_manual(0)
9054
north_loc = mavutil.location(-35.362881, 149.165103, here.alt, 0)
9055
self.wait_location(north_loc, accuracy=1)
9056
self.set_rc(2, 1500)
9057
self.set_rc(1, 1600)
9058
east_loc = mavutil.location(-35.362986, 149.165227, here.alt, 0)
9059
self.wait_location(east_loc, accuracy=1)
9060
self.set_rc(1, 1500)
9061
self.set_rc(2, 1600)
9062
south_loc = mavutil.location(-35.363025, 149.165182, here.alt, 0)
9063
self.wait_location(south_loc, accuracy=1)
9064
self.set_rc(2, 1500)
9065
self.do_RTL()
9066
9067
except Exception as e:
9068
self.print_exception_caught(e)
9069
ex = e
9070
self.context_pop()
9071
self.clear_fence()
9072
self.disarm_vehicle(force=True)
9073
self.reboot_sitl()
9074
if ex is not None:
9075
raise ex
9076
9077
def BaroWindCorrection(self):
9078
'''Test wind estimation and baro position error compensation'''
9079
self.customise_SITL_commandline(
9080
[],
9081
defaults_filepath=self.model_defaults_filepath('Callisto'),
9082
model="octa-quad:@ROMFS/models/Callisto.json",
9083
wipe=True,
9084
)
9085
wind_spd_truth = 8.0
9086
wind_dir_truth = 90.0
9087
self.set_parameters({
9088
"EK3_ENABLE": 1,
9089
"EK2_ENABLE": 0,
9090
"AHRS_EKF_TYPE": 3,
9091
"BARO1_WCF_ENABLE": 1.000000,
9092
})
9093
self.reboot_sitl()
9094
self.set_parameters({
9095
"BARO1_WCF_FWD": -0.300000,
9096
"BARO1_WCF_BCK": -0.300000,
9097
"BARO1_WCF_RGT": 0.300000,
9098
"BARO1_WCF_LFT": 0.300000,
9099
"BARO1_WCF_UP": 0.300000,
9100
"BARO1_WCF_DN": 0.300000,
9101
"SIM_BARO_WCF_FWD": -0.300000,
9102
"SIM_BARO_WCF_BAK": -0.300000,
9103
"SIM_BARO_WCF_RGT": 0.300000,
9104
"SIM_BARO_WCF_LFT": 0.300000,
9105
"SIM_BARO_WCF_UP": 0.300000,
9106
"SIM_BARO_WCF_DN": 0.300000,
9107
"SIM_WIND_DIR": wind_dir_truth,
9108
"SIM_WIND_SPD": wind_spd_truth,
9109
"SIM_WIND_T": 1.000000,
9110
})
9111
self.reboot_sitl()
9112
9113
# require_absolute=True infers a GPS is present
9114
self.wait_ready_to_arm(require_absolute=False)
9115
9116
self.progress("Climb to 20m in LOITER and yaw spin for 30 seconds")
9117
self.takeoff(10, mode="LOITER")
9118
self.set_rc(4, 1400)
9119
self.delay_sim_time(30)
9120
9121
# check wind estimates
9122
m = self.assert_receive_message('WIND')
9123
speed_error = abs(m.speed - wind_spd_truth)
9124
angle_error = abs(m.direction - wind_dir_truth)
9125
if (speed_error > 1.0):
9126
raise NotAchievedException("Wind speed incorrect - want %f +-1 got %f m/s" % (wind_spd_truth, m.speed))
9127
if (angle_error > 15.0):
9128
raise NotAchievedException(
9129
"Wind direction incorrect - want %f +-15 got %f deg" %
9130
(wind_dir_truth, m.direction))
9131
self.progress("Wind estimate is good, now check height variation for 30 seconds")
9132
9133
# check height stability over another 30 seconds
9134
z_min = 1E6
9135
z_max = -1E6
9136
tstart = self.get_sim_time()
9137
while (self.get_sim_time() < tstart + 30):
9138
m = self.assert_receive_message('LOCAL_POSITION_NED')
9139
if (m.z > z_max):
9140
z_max = m.z
9141
if (m.z < z_min):
9142
z_min = m.z
9143
if (z_max-z_min > 0.5):
9144
raise NotAchievedException("Height variation is excessive")
9145
self.progress("Height variation is good")
9146
9147
self.set_rc(4, 1500)
9148
self.land_and_disarm()
9149
9150
def wait_generator_speed_and_state(self, rpm_min, rpm_max, want_state, timeout=240):
9151
tstart = self.get_sim_time()
9152
while True:
9153
if self.get_sim_time_cached() - tstart > timeout:
9154
raise NotAchievedException("Did not move to state/speed")
9155
9156
m = self.assert_receive_message("GENERATOR_STATUS", timeout=10)
9157
9158
if m.generator_speed < rpm_min:
9159
self.progress("Too slow (%u<%u)" % (m.generator_speed, rpm_min))
9160
continue
9161
if m.generator_speed > rpm_max:
9162
self.progress("Too fast (%u>%u)" % (m.generator_speed, rpm_max))
9163
continue
9164
if m.status != want_state:
9165
self.progress("Wrong state (got=%u want=%u)" % (m.status, want_state))
9166
break
9167
self.progress("Got generator speed and state")
9168
9169
def RichenPower(self):
9170
'''Test RichenPower generator'''
9171
self.set_parameters({
9172
"SERIAL5_PROTOCOL": 30,
9173
"SIM_RICH_ENABLE": 1,
9174
"SERVO8_FUNCTION": 42,
9175
"SIM_RICH_CTRL": 8,
9176
"RC9_OPTION": 85,
9177
"LOG_DISARMED": 1,
9178
"BATT2_MONITOR": 17,
9179
"GEN_TYPE": 3,
9180
})
9181
self.reboot_sitl()
9182
self.set_rc(9, 1000) # remember this is a switch position - stop
9183
self.customise_SITL_commandline(["--serial5=sim:richenpower"])
9184
self.wait_statustext("requested state is not RUN", timeout=60)
9185
9186
self.set_message_rate_hz("GENERATOR_STATUS", 10)
9187
9188
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9189
9190
self.context_collect('STATUSTEXT')
9191
self.set_rc(9, 2000) # remember this is a switch position - run
9192
self.wait_statustext("Generator HIGH", check_context=True)
9193
self.set_rc(9, 1000) # remember this is a switch position - stop
9194
self.wait_statustext("requested state is not RUN", timeout=200)
9195
9196
self.set_rc(9, 1500) # remember this is a switch position - idle
9197
self.wait_generator_speed_and_state(3000, 8000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
9198
9199
self.set_rc(9, 2000) # remember this is a switch position - run
9200
# self.wait_generator_speed_and_state(3000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_WARMING_UP)
9201
9202
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
9203
9204
bs = self.assert_receive_message(
9205
"BATTERY_STATUS",
9206
condition="BATTERY_STATUS.id==1", # id is zero-indexed
9207
)
9208
self.progress("Received battery status: %s" % str(bs))
9209
want_bs_volt = 50000
9210
if bs.voltages[0] != want_bs_volt:
9211
raise NotAchievedException("Battery voltage not as expected (want=%f) got=(%f)" % (want_bs_volt, bs.voltages[0],))
9212
9213
self.progress("Moving *back* to idle")
9214
self.set_rc(9, 1500) # remember this is a switch position - idle
9215
self.wait_generator_speed_and_state(3000, 10000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
9216
9217
self.progress("Moving *back* to run")
9218
self.set_rc(9, 2000) # remember this is a switch position - run
9219
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
9220
9221
self.set_message_rate_hz("GENERATOR_STATUS", -1)
9222
self.set_parameter("LOG_DISARMED", 0)
9223
if not self.current_onboard_log_contains_message("RICH"):
9224
raise NotAchievedException("Did not find expected RICH message")
9225
9226
def IE24(self):
9227
'''Test IntelligentEnergy 2.4kWh generator with V1 and V2 telemetry protocols'''
9228
protocol_ver = (1, 2)
9229
for ver in protocol_ver:
9230
self.start_subtest(f"IE24 general tests for protocol {ver}")
9231
self.context_push()
9232
self.run_IE24(ver)
9233
self.context_pop()
9234
self.reboot_sitl()
9235
9236
self.start_subtest(f"IE24 low capacity failsafe test for protocol {ver}")
9237
self.context_push()
9238
self.test_IE24_low_capacity_failsafe(ver)
9239
self.context_pop()
9240
self.reboot_sitl()
9241
9242
def run_IE24(self, proto_ver):
9243
'''Test IntelligentEnergy 2.4kWh generator'''
9244
elec_battery_instance = 2
9245
fuel_battery_instance = 1
9246
self.set_parameters({
9247
"SERIAL5_PROTOCOL": 30,
9248
"SERIAL5_BAUD": 115200,
9249
"GEN_TYPE": 2,
9250
"BATT%u_MONITOR" % (fuel_battery_instance + 1): 18, # fuel-based generator
9251
"BATT%u_MONITOR" % (elec_battery_instance + 1): 17,
9252
"SIM_IE24_ENABLE": proto_ver,
9253
"LOG_DISARMED": 1,
9254
})
9255
9256
self.customise_SITL_commandline(["--serial5=sim:ie24"])
9257
9258
self.start_subtest("Protocol %i: ensure that BATTERY_STATUS for electrical generator message looks right" % proto_ver)
9259
self.start_subsubtest("Protocol %i: Checking original voltage (electrical)" % proto_ver)
9260
# ArduPilot spits out essentially uninitialised battery
9261
# messages until we read things from the battery:
9262
self.delay_sim_time(30)
9263
original_elec_m = self.wait_message_field_values('BATTERY_STATUS', {
9264
"charge_state": mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_OK
9265
}, instance=elec_battery_instance)
9266
original_fuel_m = self.wait_message_field_values('BATTERY_STATUS', {
9267
"charge_state": mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_OK
9268
}, instance=fuel_battery_instance)
9269
9270
if original_elec_m.battery_remaining < 90:
9271
raise NotAchievedException("Bad original percentage")
9272
self.start_subsubtest("Ensure percentage is counting down")
9273
self.wait_message_field_values('BATTERY_STATUS', {
9274
"battery_remaining": original_elec_m.battery_remaining - 1,
9275
}, instance=elec_battery_instance)
9276
9277
self.start_subtest("Protocol %i: ensure that BATTERY_STATUS for fuel generator message looks right" % proto_ver)
9278
self.start_subsubtest("Protocol %i: Checking original voltage (fuel)" % proto_ver)
9279
# ArduPilot spits out essentially uninitialised battery
9280
# messages until we read things from the battery:
9281
if original_fuel_m.battery_remaining <= 90:
9282
raise NotAchievedException("Bad original percentage (want=>%f got %f" % (90, original_fuel_m.battery_remaining))
9283
self.start_subsubtest("Protocol %i: Ensure percentage is counting down" % proto_ver)
9284
self.wait_message_field_values('BATTERY_STATUS', {
9285
"battery_remaining": original_fuel_m.battery_remaining - 1,
9286
}, instance=fuel_battery_instance)
9287
9288
self.wait_ready_to_arm()
9289
self.arm_vehicle()
9290
self.disarm_vehicle()
9291
9292
# Test for pre-arm check fail when state is not running
9293
self.start_subtest("Protocol %i: Without takeoff generator error should cause failsafe and disarm" % proto_ver)
9294
self.set_parameter("SIM_IE24_STATE", 8)
9295
self.wait_statustext("Status not running", timeout=40)
9296
self.try_arm(result=False,
9297
expect_msg="Status not running")
9298
self.set_parameter("SIM_IE24_STATE", 2) # Explicitly set state to running
9299
9300
# Test that error code does result in failsafe
9301
self.start_subtest("Protocol %i: Without taken off generator error should cause failsafe and disarm" % proto_ver)
9302
self.change_mode("STABILIZE")
9303
self.set_parameter("DISARM_DELAY", 0)
9304
self.arm_vehicle()
9305
self.set_parameter("SIM_IE24_ERROR", 30)
9306
self.disarm_wait(timeout=1)
9307
self.set_parameter("SIM_IE24_ERROR", 0)
9308
self.set_parameter("DISARM_DELAY", 10)
9309
9310
def test_IE24_low_capacity_failsafe(self, proto_ver):
9311
elec_battery_instance = 2
9312
fuel_battery_instance = 1
9313
self.set_parameters({
9314
"SERIAL5_PROTOCOL": 30,
9315
"SERIAL5_BAUD": 115200,
9316
"GEN_TYPE": 2,
9317
"BATT%u_MONITOR" % (fuel_battery_instance + 1): 18, # fuel-based generator
9318
"BATT%u_MONITOR" % (elec_battery_instance + 1): 17,
9319
"SIM_IE24_ENABLE": proto_ver,
9320
"LOG_DISARMED": 1,
9321
})
9322
9323
self.customise_SITL_commandline(["--serial5=sim:ie24"])
9324
9325
self.change_mode('GUIDED')
9326
self.wait_ready_to_arm()
9327
9328
original_fuel_m = self.wait_message_field_values('BATTERY_STATUS', {
9329
"charge_state": mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_OK
9330
}, instance=fuel_battery_instance)
9331
9332
# set failsafe parameters
9333
self.set_parameters({
9334
"BATT%u_CAPACITY" % (fuel_battery_instance + 1): 100,
9335
"BATT%u_LOW_MAH" % (fuel_battery_instance + 1): original_fuel_m.battery_remaining - 5,
9336
"BATT%u_FS_LOW_ACT" % (fuel_battery_instance + 1): 1, # LAND
9337
})
9338
9339
self.takeoff(mode='GUIDED')
9340
self.wait_mode('LAND')
9341
self.wait_disarmed()
9342
self.set_rc(3, 1000) # Restore the throttle stick since takeoff raised it.
9343
9344
def LoweheiserAuto(self):
9345
'''Ensure the Loweheiser generator works as expected in auto-starter mode.'''
9346
9347
gen_ctrl_ch = 9
9348
9349
self.set_parameters({
9350
"SERIAL5_PROTOCOL": 2, # mavlink
9351
"MAV3_OPTIONS": 2, # private
9352
"GEN_TYPE": 4, # loweheiser
9353
"EFI_TYPE": 4, # loweheiser
9354
"SIM_EFI_TYPE": 2, # loweheiser sim
9355
"BATT2_MONITOR": 17, # generator (elec)
9356
"BATT3_MONITOR": 18, # generator (fuel-level)
9357
"BATT3_CAPACITY": 10000, # generator (fuel) in mL
9358
f"RC{gen_ctrl_ch}_OPTION": 85, # generator control
9359
"LOG_DISARMED": 1
9360
})
9361
9362
self.set_rc_from_map({
9363
gen_ctrl_ch: 1000, # remember this is a switch position - stop
9364
})
9365
9366
self.customise_SITL_commandline(["--serial5=sim:loweheiser"])
9367
9368
self.set_parameters({
9369
"GEN_L_IDLE_TH": 25,
9370
"GEN_L_IDLE_TH_H": 40,
9371
"GEN_L_RUN_TEMP": 60,
9372
"GEN_L_IDLE_TEMP": 80,
9373
})
9374
9375
self.reboot_sitl()
9376
9377
self.assert_parameter_value("GEN_L_IDLE_TH", 25)
9378
9379
self.delay_sim_time(10) # so we can actually receive messages...
9380
9381
#######################################################################
9382
# Generator OFF subtest.
9383
#######################################################################
9384
self.start_subtest("Checking GENERATOR_STATUS while OFF.")
9385
self.set_message_rate_hz("GENERATOR_STATUS", 10)
9386
self.delay_sim_time(1)
9387
9388
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9389
9390
self.start_subtest("Checking EFI_STATUS while OFF.")
9391
self.set_message_rate_hz("EFI_STATUS", 10)
9392
self.delay_sim_time(1)
9393
self.assert_received_message_field_values('EFI_STATUS', {
9394
"health": 1,
9395
"ecu_index": 1.0,
9396
"rpm": 0.0,
9397
"fuel_consumed": 0,
9398
"fuel_flow": float("nan"),
9399
"engine_load": 0.0,
9400
"throttle_position": 0.0,
9401
"spark_dwell_time": 0.0,
9402
"barometric_pressure": 0.0,
9403
"intake_manifold_pressure": float("nan"),
9404
"intake_manifold_temperature": float("nan"),
9405
"cylinder_head_temperature": float("nan"),
9406
"ignition_timing": 0.0,
9407
"injection_time": float("nan"),
9408
"exhaust_gas_temperature": float("nan"),
9409
"throttle_out": 0.0,
9410
"pt_compensation": 0.0,
9411
"ignition_voltage": 0, # As per the spec, 0 means "Unknown".
9412
}, epsilon=1.0)
9413
self.assert_received_message_field_values('GENERATOR_STATUS', {
9414
"status": 1,
9415
"generator_speed": 0,
9416
"battery_current": -0.30000001192092896,
9417
"load_current": 10.119999885559082,
9418
"power_generated": 521.0,
9419
"bus_voltage": 50,
9420
"rectifier_temperature": 32767,
9421
"bat_current_setpoint": float("nan"),
9422
"generator_temperature": 32767,
9423
"runtime": 0,
9424
"time_until_maintenance": 300*60*60,
9425
})
9426
9427
self.context_collect('STATUSTEXT')
9428
9429
# check prearms - should bounce due to generator not in correct state
9430
self.try_arm(result=False, expect_msg="requested state is not RUN")
9431
9432
#######################################################################
9433
# Generator IDLE subtest.
9434
#######################################################################
9435
self.start_subtest("Setting generator to IDLE state.")
9436
self.set_rc(gen_ctrl_ch, 1500) # remember this is a switch position - idle
9437
self.wait_statustext("Generator MIDDLE", check_context=True)
9438
self.delay_sim_time(2)
9439
self.drain_mav()
9440
self.assert_received_message_field_values('EFI_STATUS', {
9441
"intake_manifold_pressure": 94,
9442
"exhaust_gas_temperature": float("nan"),
9443
"ignition_voltage": 12,
9444
"throttle_position": 40,
9445
}, epsilon=1.0)
9446
9447
self.wait_message_field_values('EFI_STATUS', {
9448
"cylinder_head_temperature": 20,
9449
}, epsilon=5.0, timeout=10)
9450
9451
self.assert_received_message_field_values('GENERATOR_STATUS', {
9452
# Commenting out "status" argument.
9453
# For some reason the test suite can't accept an integer value. Says it's not part of the FLAG.
9454
# "status": mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE,
9455
"battery_current": -0.30000001192092896,
9456
"load_current": 10.119999885559082,
9457
"power_generated": 521.0,
9458
"bus_voltage": 50,
9459
"rectifier_temperature": 32767,
9460
"bat_current_setpoint": float("nan"),
9461
"generator_temperature": 32767,
9462
"runtime": 2,
9463
"time_until_maintenance": 300*60*60 - 2,
9464
})
9465
self.wait_generator_speed_and_state(2000, 3000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
9466
9467
#######################################################################
9468
# Generator RUN subtest.
9469
#######################################################################
9470
self.start_subtest("Setting generator to RUN state.")
9471
self.set_rc(gen_ctrl_ch, 2000) # remember this is a switch position - run
9472
self.wait_statustext("Generator HIGH", check_context=True)
9473
9474
# check prearms - should bounce due to generator too cold
9475
self.try_arm(result=False, expect_msg="Generator warming up")
9476
9477
self.set_rc(gen_ctrl_ch, 1000) # remember this is a switch position - stop
9478
self.wait_statustext("requested state is not RUN", timeout=200)
9479
9480
self.set_rc(gen_ctrl_ch, 1500) # remember this is a switch position - idle
9481
self.wait_generator_speed_and_state(2000, 3000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
9482
9483
self.set_rc(gen_ctrl_ch, 2000) # remember this is a switch position - run
9484
9485
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
9486
9487
bs = self.assert_receive_message(
9488
"BATTERY_STATUS",
9489
condition="BATTERY_STATUS.id==1", # id is zero-indexed
9490
timeout=1,
9491
very_verbose=True,
9492
)
9493
if bs is None:
9494
raise NotAchievedException("Did not receive BATTERY_STATUS")
9495
self.progress("Received battery status: %s" % str(bs))
9496
want_bs_volt = 50000
9497
if bs.voltages[0] != want_bs_volt:
9498
raise NotAchievedException("Battery voltage not as expected (want=%f) got=(%f)" % (want_bs_volt, bs.voltages[0],))
9499
9500
self.progress("Checking battery remaining")
9501
bs = self.assert_receive_message(
9502
"BATTERY_STATUS",
9503
condition="BATTERY_STATUS.id==2", # id is zero-indexed
9504
timeout=1,
9505
very_verbose=True,
9506
)
9507
self.progress("Waiting for some fuel to be consumed...")
9508
self.wait_message_field_values("BATTERY_STATUS", {
9509
"id": 2,
9510
"battery_remaining": bs.battery_remaining-1,
9511
}, timeout=100)
9512
9513
bs2 = self.assert_receive_message(
9514
"BATTERY_STATUS",
9515
condition="BATTERY_STATUS.id==2", # id is zero-indexed
9516
timeout=1,
9517
very_verbose=True,
9518
)
9519
if bs2.battery_remaining >= bs.battery_remaining:
9520
raise NotAchievedException("Expected battery remaining to drop")
9521
if bs2.current_consumed <= bs.current_consumed:
9522
raise NotAchievedException("Expected energy consumed to rise")
9523
9524
self.progress("Checking battery reset")
9525
batt_reset_values = [(25, 24),
9526
(50, 49),
9527
(63, 62),
9528
(87, 86),
9529
(100, 99)]
9530
9531
for (reset_val, return_val) in batt_reset_values:
9532
self.run_cmd(mavutil.mavlink.MAV_CMD_BATTERY_RESET,
9533
(1 << 2), # param1 - bitmask of batteries to reset
9534
reset_val, # level to reset to
9535
0, # param3
9536
0, # param4
9537
0, # param5
9538
0, # param6
9539
0 # param7
9540
)
9541
self.wait_message_field_values("BATTERY_STATUS", {
9542
"id": 2,
9543
"battery_remaining": return_val,
9544
}, timeout=5)
9545
9546
self.progress("Moving *back* to idle")
9547
self.set_rc(gen_ctrl_ch, 1500) # remember this is a switch position - idle
9548
self.wait_generator_speed_and_state(3000, 10000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
9549
9550
self.progress("Moving *back* to run")
9551
self.set_rc(gen_ctrl_ch, 2000) # remember this is a switch position - run
9552
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
9553
9554
self.set_parameter("LOG_DISARMED", 0)
9555
if not self.current_onboard_log_contains_message("LOEG"):
9556
raise NotAchievedException("Did not find expected LOEG message in .bin log")
9557
if not self.current_onboard_log_contains_message("LOEC"):
9558
raise NotAchievedException("Did not find expected LOEC message in .bin log")
9559
9560
self.set_parameter("LOG_DISARMED", 1) # Re-start logging, to help with debugging.
9561
self.progress("Stopping generator")
9562
self.set_rc(gen_ctrl_ch, 1000)
9563
9564
self.wait_statustext("Cooling down")
9565
9566
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9567
9568
#######################################################################
9569
# E-stop subtest.
9570
#######################################################################
9571
self.start_subtest("Checking safety switch estop")
9572
self.set_rc(gen_ctrl_ch, 2000)
9573
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
9574
9575
self.set_safetyswitch_on()
9576
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9577
self.wait_message_field_values('EFI_STATUS', {
9578
# note that percent isn't honouring dead-zones...
9579
"throttle_position": 0,
9580
"rpm": 0,
9581
})
9582
9583
self.set_safetyswitch_off()
9584
9585
self.wait_message_field_values('EFI_STATUS', {
9586
# note that percent isn't honouring dead-zones...
9587
"throttle_position": 80, # Throttle set by governor.
9588
"rpm": 8000, # RPM for 50% throttle
9589
}, timeout=20)
9590
9591
# stop generator:
9592
self.set_rc(gen_ctrl_ch, 1000)
9593
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9594
9595
#######################################################################
9596
# Battery failsafe subtest.
9597
#######################################################################
9598
self.start_subtest("Battery Failsafes")
9599
self.context_push()
9600
batt3_capacity = 500
9601
batt3_low_mah = 100
9602
batt3_low_consumed_mah = batt3_capacity - batt3_low_mah
9603
self.set_parameters({
9604
"BATT3_CAPACITY": batt3_capacity,
9605
"BATT3_LOW_MAH": batt3_low_mah,
9606
"BATT3_CRT_MAH": 50,
9607
"BATT3_FS_LOW_ACT": 2, # RTL
9608
"BATT3_FS_CRT_ACT": 1, # LAND
9609
"BATT3_LOW_VOLT": 0,
9610
})
9611
self.reboot_sitl()
9612
self.set_rc(gen_ctrl_ch, 2000)
9613
self.takeoff(10, mode='GUIDED')
9614
9615
first_efi_status = self.assert_receive_message('EFI_STATUS', verbose=True)
9616
if first_efi_status.fuel_consumed < 100: # takes about this much to get going
9617
raise NotAchievedException("Unexpected fuel consumed value after takeoff (%f)" % first_efi_status.fuel_consumed)
9618
9619
self.fly_guided_move_local(100, 100, 20)
9620
9621
self.wait_mode('RTL', timeout=300)
9622
9623
second_efi_status = self.assert_receive_message('EFI_STATUS', verbose=True)
9624
9625
if second_efi_status.fuel_consumed < batt3_low_consumed_mah:
9626
raise NotAchievedException("Unexpected fuel consumed value after failsafe (%f)" % second_efi_status.fuel_consumed)
9627
9628
self.wait_mode('LAND', timeout=300)
9629
self.wait_disarmed()
9630
9631
self.context_pop()
9632
self.reboot_sitl()
9633
9634
self.set_message_rate_hz("EFI_STATUS", -1)
9635
self.set_message_rate_hz("GENERATOR_STATUS", -1)
9636
9637
def LoweheiserManual(self):
9638
'''Ensure the Loweheiser generator works as expected in manual starter mode.'''
9639
9640
gen_ctrl_ch = 9
9641
loweheiser_man_throt_ch = 10
9642
loweheiser_man_start_ch = 11
9643
9644
self.set_parameters({
9645
"SERIAL5_PROTOCOL": 2, # mavlink
9646
"MAV3_OPTIONS": 2, # private
9647
"GEN_TYPE": 4, # loweheiser
9648
"EFI_TYPE": 4, # loweheiser
9649
"SIM_EFI_TYPE": 2, # loweheiser sim
9650
"BATT2_MONITOR": 17, # generator (elec)
9651
"BATT3_MONITOR": 18, # generator (fuel-level)
9652
"BATT3_CAPACITY": 10000, # generator (fuel) in mL
9653
f"RC{gen_ctrl_ch}_OPTION": 85, # generator control
9654
"LOG_DISARMED": 1,
9655
"RC%u_OPTION" % loweheiser_man_throt_ch: 218, # loweheiser manual throttle control
9656
"RC%u_DZ" % loweheiser_man_throt_ch: 20,
9657
"RC%u_TRIM" % loweheiser_man_throt_ch: 1000,
9658
"RC%u_MIN" % loweheiser_man_throt_ch: 1000,
9659
"RC%u_MAX" % loweheiser_man_throt_ch: 2000,
9660
"RC%u_OPTION" % loweheiser_man_start_ch: 111, # loweheiser starter channel
9661
})
9662
9663
self.set_rc_from_map({
9664
gen_ctrl_ch: 1000, # remember this is a switch position - stop
9665
loweheiser_man_throt_ch: 1000, # zero throttle
9666
loweheiser_man_start_ch: 1000, # the starter is off
9667
})
9668
9669
self.customise_SITL_commandline(["--serial5=sim:loweheiser"])
9670
9671
self.set_parameters({
9672
"GEN_L_IDLE_TH": 25,
9673
"GEN_L_IDLE_TH_H": 40,
9674
"GEN_L_RUN_TEMP": 60,
9675
"GEN_L_IDLE_TEMP": 80,
9676
})
9677
9678
self.reboot_sitl()
9679
9680
self.assert_parameter_value("GEN_L_IDLE_TH", 25)
9681
9682
self.delay_sim_time(10) # so we can actually receive messages...
9683
9684
#######################################################################
9685
# Generator OFF subtest.
9686
#######################################################################
9687
self.start_subtest("Checking GENERATOR_STATUS while OFF.")
9688
self.set_message_rate_hz("GENERATOR_STATUS", 10)
9689
self.delay_sim_time(1)
9690
9691
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9692
9693
self.start_subtest("Checking EFI_STATUS while OFF.")
9694
self.set_message_rate_hz("EFI_STATUS", 10)
9695
self.delay_sim_time(1)
9696
self.assert_received_message_field_values('EFI_STATUS', {
9697
"health": 1,
9698
"ecu_index": 1.0,
9699
"rpm": 0.0,
9700
"fuel_consumed": 0,
9701
"fuel_flow": float("nan"),
9702
"engine_load": 0.0,
9703
"throttle_position": 0.0,
9704
"spark_dwell_time": 0.0,
9705
"barometric_pressure": 0.0,
9706
"intake_manifold_pressure": float("nan"),
9707
"intake_manifold_temperature": float("nan"),
9708
"cylinder_head_temperature": float("nan"),
9709
"ignition_timing": 0.0,
9710
"injection_time": float("nan"),
9711
"exhaust_gas_temperature": float("nan"),
9712
"throttle_out": 0.0,
9713
"pt_compensation": 0.0,
9714
"ignition_voltage": 0, # As per the spec, 0 means "Unknown".
9715
}, epsilon=1.0)
9716
self.assert_received_message_field_values('GENERATOR_STATUS', {
9717
"status": 1,
9718
"generator_speed": 0,
9719
"battery_current": -0.30000001192092896,
9720
"load_current": 10.119999885559082,
9721
"power_generated": 521.0,
9722
"bus_voltage": 50,
9723
"rectifier_temperature": 32767,
9724
"bat_current_setpoint": float("nan"),
9725
"generator_temperature": 32767,
9726
"runtime": 0,
9727
"time_until_maintenance": 300*60*60,
9728
})
9729
9730
rc_dz = self.get_parameter('RC%u_DZ' % loweheiser_man_throt_ch)
9731
rc_trim = int(self.get_parameter('RC%u_TRIM' % loweheiser_man_throt_ch))
9732
rc_min = self.get_parameter('RC%u_MIN' % loweheiser_man_throt_ch)
9733
rc_max = int(self.get_parameter('RC%u_MAX' % loweheiser_man_throt_ch))
9734
9735
self.progress("Ensuring 1000 and rc_trim all leave throttle out 0")
9736
for i in 1000, rc_trim:
9737
self.progress("Checking %u pwm" % i)
9738
self.set_rc(loweheiser_man_throt_ch, i)
9739
self.delay_sim_time(1)
9740
self.assert_received_message_field_values('EFI_STATUS', {
9741
"throttle_position": 0,
9742
"rpm": 0,
9743
})
9744
9745
self.context_collect('STATUSTEXT')
9746
# check prearms - should bounce due to generator not in correct state
9747
self.try_arm(result=False, expect_msg="requested state is not RUN")
9748
9749
#######################################################################
9750
# Generator IDLE subtest.
9751
#######################################################################
9752
self.start_subtest("Setting generator to IDLE state.")
9753
self.set_rc(gen_ctrl_ch, 1500) # remember this is a switch position - idle
9754
self.wait_statustext("Generator MIDDLE", check_context=True)
9755
self.delay_sim_time(5)
9756
9757
# Ensure the generator has not auto-started.
9758
self.assert_received_message_field_values('EFI_STATUS', {
9759
"throttle_out": 0,
9760
"rpm": 0,
9761
})
9762
9763
# at 50 percent throttle should not start until user triggers starter
9764
pwm_for_fifty_percent_throttle = int(rc_min + rc_dz + int((rc_max-rc_min-rc_dz)/2))
9765
self.progress("Using PWM of %u for 50 percent throttle" % pwm_for_fifty_percent_throttle)
9766
self.set_rc(loweheiser_man_throt_ch, pwm_for_fifty_percent_throttle)
9767
self.wait_message_field_values('EFI_STATUS', {
9768
# note that percent isn't honouring dead-zones...
9769
"throttle_position": 51, # magic fixed throttle value from AP_Generator_Loweheiser.cpp
9770
"rpm": 0,
9771
}, timeout=20)
9772
9773
self.set_rc(loweheiser_man_start_ch, 2000) # Run the starter for a few seconds.
9774
9775
self.drain_mav()
9776
self.assert_received_message_field_values('EFI_STATUS', {
9777
"intake_manifold_pressure": 94,
9778
"exhaust_gas_temperature": float("nan"),
9779
"ignition_voltage": 12,
9780
}, epsilon=1.0)
9781
9782
self.wait_message_field_values('EFI_STATUS', {
9783
"cylinder_head_temperature": 20,
9784
}, epsilon=5.0, timeout=10)
9785
9786
self.set_rc(loweheiser_man_start_ch, 1000)
9787
9788
self.assert_received_message_field_values('GENERATOR_STATUS', {
9789
# Commenting out "status" argument.
9790
# For some reason the test suite can't accept an integer value. Says it's not part of the FLAG.
9791
# "status": mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE,
9792
"battery_current": -0.30000001192092896,
9793
"load_current": 10.119999885559082,
9794
"power_generated": 521.0,
9795
"bus_voltage": 50,
9796
"rectifier_temperature": 32767,
9797
"bat_current_setpoint": float("nan"),
9798
"generator_temperature": 32767,
9799
"runtime": 2,
9800
"time_until_maintenance": 300*60*60 - 2,
9801
})
9802
9803
self.progress("Generator at idle should not run governor and use throttle input")
9804
self.wait_message_field_values('EFI_STATUS', {
9805
# note that percent isn't honouring dead-zones...
9806
"throttle_position": 51, # magic fixed throttle value from AP_Generator_Loweheiser.cpp
9807
"rpm": 4080, # RPM for 50% throttle
9808
}, timeout=20)
9809
9810
#######################################################################
9811
# Generator RUN subtest.
9812
#######################################################################
9813
self.start_subtest("Setting generator to RUN state.")
9814
self.set_rc(gen_ctrl_ch, 2000) # remember this is a switch position - run
9815
self.wait_statustext("Generator HIGH", check_context=True)
9816
9817
# check prearms - should bounce due to generator too cold
9818
self.try_arm(result=False, expect_msg="Generator warming up")
9819
9820
self.set_rc(gen_ctrl_ch, 1000) # remember this is a switch position - stop
9821
self.wait_statustext("requested state is not RUN", timeout=200)
9822
9823
self.set_rc(gen_ctrl_ch, 1500) # remember this is a switch position - idle
9824
self.set_rc(loweheiser_man_start_ch, 2000)
9825
self.wait_generator_speed_and_state(2000, 3000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
9826
self.set_rc(loweheiser_man_start_ch, 1000)
9827
9828
self.set_rc(gen_ctrl_ch, 2000) # remember this is a switch position - run
9829
9830
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
9831
9832
bs = self.assert_receive_message(
9833
"BATTERY_STATUS",
9834
condition="BATTERY_STATUS.id==1", # id is zero-indexed
9835
timeout=1,
9836
very_verbose=True,
9837
)
9838
if bs is None:
9839
raise NotAchievedException("Did not receive BATTERY_STATUS")
9840
self.progress("Received battery status: %s" % str(bs))
9841
want_bs_volt = 50000
9842
if bs.voltages[0] != want_bs_volt:
9843
raise NotAchievedException("Battery voltage not as expected (want=%f) got=(%f)" % (want_bs_volt, bs.voltages[0],))
9844
9845
self.progress("Checking battery remaining")
9846
bs = self.assert_receive_message(
9847
"BATTERY_STATUS",
9848
condition="BATTERY_STATUS.id==2", # id is zero-indexed
9849
timeout=1,
9850
very_verbose=True,
9851
)
9852
self.progress("Waiting for some fuel to be consumed...")
9853
self.wait_message_field_values("BATTERY_STATUS", {
9854
"id": 2,
9855
"battery_remaining": bs.battery_remaining-1,
9856
}, timeout=100)
9857
9858
bs2 = self.assert_receive_message(
9859
"BATTERY_STATUS",
9860
condition="BATTERY_STATUS.id==2", # id is zero-indexed
9861
timeout=1,
9862
very_verbose=True,
9863
)
9864
if bs2.battery_remaining >= bs.battery_remaining:
9865
raise NotAchievedException("Expected battery remaining to drop")
9866
if bs2.current_consumed <= bs.current_consumed:
9867
raise NotAchievedException("Expected energy consumed to rise")
9868
9869
self.progress("Checking battery reset")
9870
batt_reset_values = [(25, 24),
9871
(50, 49),
9872
(63, 62),
9873
(87, 86),
9874
(100, 99)]
9875
9876
for (reset_val, return_val) in batt_reset_values:
9877
self.run_cmd(mavutil.mavlink.MAV_CMD_BATTERY_RESET,
9878
(1 << 2), # param1 - bitmask of batteries to reset
9879
reset_val, # level to reset to
9880
0, # param3
9881
0, # param4
9882
0, # param5
9883
0, # param6
9884
0 # param7
9885
)
9886
self.wait_message_field_values("BATTERY_STATUS", {
9887
"id": 2,
9888
"battery_remaining": return_val,
9889
}, timeout=5)
9890
9891
self.progress("Moving *back* to idle")
9892
self.set_rc(gen_ctrl_ch, 1500) # remember this is a switch position - idle
9893
self.wait_generator_speed_and_state(3000, 10000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
9894
9895
self.progress("Moving *back* to run")
9896
self.set_rc(gen_ctrl_ch, 2000) # remember this is a switch position - run
9897
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
9898
9899
self.set_parameter("LOG_DISARMED", 0)
9900
if not self.current_onboard_log_contains_message("LOEG"):
9901
raise NotAchievedException("Did not find expected LOEG message in .bin log")
9902
if not self.current_onboard_log_contains_message("LOEC"):
9903
raise NotAchievedException("Did not find expected LOEC message in .bin log")
9904
9905
self.set_parameter("LOG_DISARMED", 1) # Re-start logging, to help with debugging.
9906
self.progress("Stopping generator")
9907
self.set_rc(gen_ctrl_ch, 1000)
9908
9909
self.wait_statustext("Cooling down")
9910
9911
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9912
9913
#######################################################################
9914
# E-stop subtest.
9915
#######################################################################
9916
self.start_subtest("Checking safety switch estop")
9917
# Start the engine.
9918
self.set_rc(gen_ctrl_ch, 1500)
9919
self.set_rc(loweheiser_man_start_ch, 2000)
9920
self.wait_message_field_values('EFI_STATUS', {
9921
# note that percent isn't honouring dead-zones...
9922
"rpm": 4000, # RPM for 50% throttle
9923
}, timeout=20)
9924
self.set_rc(loweheiser_man_start_ch, 1000)
9925
9926
self.set_safetyswitch_on()
9927
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9928
self.wait_message_field_values('EFI_STATUS', {
9929
# note that percent isn't honouring dead-zones...
9930
"throttle_position": 0,
9931
"rpm": 0,
9932
})
9933
9934
self.set_safetyswitch_off()
9935
9936
self.set_rc(gen_ctrl_ch, 1500)
9937
self.set_rc(loweheiser_man_start_ch, 2000)
9938
self.wait_message_field_values('EFI_STATUS', {
9939
# note that percent isn't honouring dead-zones...
9940
"throttle_position": 51, # magic fixed throttle value from AP_Generator_Loweheiser.cpp
9941
"rpm": 4000, # RPM for 50% throttle
9942
}, timeout=20)
9943
self.set_rc(loweheiser_man_start_ch, 1000)
9944
9945
# stop generator:
9946
self.set_rc(gen_ctrl_ch, 1000)
9947
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
9948
9949
#######################################################################
9950
# Battery failsafe subtest.
9951
#######################################################################
9952
self.start_subtest("Battery Failsafes")
9953
self.context_push()
9954
batt3_capacity = 500
9955
batt3_low_mah = 100
9956
batt3_low_consumed_mah = batt3_capacity - batt3_low_mah
9957
self.set_parameters({
9958
"BATT3_CAPACITY": batt3_capacity,
9959
"BATT3_LOW_MAH": batt3_low_mah,
9960
"BATT3_CRT_MAH": 50,
9961
"BATT3_FS_LOW_ACT": 2, # RTL
9962
"BATT3_FS_CRT_ACT": 1, # LAND
9963
"BATT3_LOW_VOLT": 0,
9964
})
9965
self.reboot_sitl()
9966
9967
# Start the generator anew.
9968
self.set_rc(gen_ctrl_ch, 1500)
9969
self.set_rc(loweheiser_man_start_ch, 2000)
9970
self.delay_sim_time(5)
9971
self.set_rc(loweheiser_man_start_ch, 1000)
9972
self.set_rc(gen_ctrl_ch, 2000)
9973
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
9974
self.takeoff(10, mode='GUIDED')
9975
9976
first_efi_status = self.assert_receive_message('EFI_STATUS', verbose=True)
9977
if first_efi_status.fuel_consumed < 100: # takes about this much to get going
9978
raise NotAchievedException("Unexpected fuel consumed value after takeoff (%f)" % first_efi_status.fuel_consumed)
9979
9980
self.fly_guided_move_local(100, 100, 20)
9981
9982
self.wait_mode('RTL', timeout=300)
9983
9984
second_efi_status = self.assert_receive_message('EFI_STATUS', verbose=True)
9985
9986
if second_efi_status.fuel_consumed < batt3_low_consumed_mah:
9987
raise NotAchievedException("Unexpected fuel consumed value after failsafe (%f)" % second_efi_status.fuel_consumed)
9988
9989
self.wait_mode('LAND', timeout=300)
9990
self.wait_disarmed()
9991
9992
self.context_pop()
9993
self.reboot_sitl()
9994
9995
self.set_message_rate_hz("EFI_STATUS", -1)
9996
self.set_message_rate_hz("GENERATOR_STATUS", -1)
9997
9998
def AuxSwitchOptions(self):
9999
'''Test random aux mode options'''
10000
self.set_parameter("RC7_OPTION", 58) # clear waypoints
10001
self.load_mission("copter_loiter_to_alt.txt")
10002
self.set_rc(7, 1000)
10003
self.assert_mission_count(5)
10004
self.progress("Clear mission")
10005
self.set_rc(7, 2000)
10006
self.delay_sim_time(1) # allow switch to debounce
10007
self.assert_mission_count(0)
10008
self.set_rc(7, 1000)
10009
self.set_parameter("RC7_OPTION", 24) # reset mission
10010
self.delay_sim_time(2)
10011
self.load_mission("copter_loiter_to_alt.txt")
10012
set_wp = 4
10013
self.set_current_waypoint(set_wp)
10014
self.wait_current_waypoint(set_wp, timeout=10)
10015
self.progress("Reset mission")
10016
self.set_rc(7, 2000)
10017
self.delay_sim_time(1)
10018
self.wait_current_waypoint(0, timeout=10)
10019
self.set_rc(7, 1000)
10020
10021
def AuxFunctionsInMission(self):
10022
'''Test use of auxiliary functions in missions'''
10023
self.load_mission("aux_functions.txt")
10024
self.change_mode('LOITER')
10025
self.wait_ready_to_arm()
10026
self.arm_vehicle()
10027
self.change_mode('AUTO')
10028
self.set_rc(3, 1500)
10029
self.wait_mode('ALT_HOLD')
10030
self.change_mode('AUTO')
10031
self.wait_rtl_complete()
10032
10033
def MAV_CMD_AIRFRAME_CONFIGURATION(self):
10034
'''deploy/retract landing gear using mavlink command'''
10035
self.context_push()
10036
self.set_parameters({
10037
"LGR_ENABLE": 1,
10038
"SERVO10_FUNCTION": 29,
10039
"SERVO10_MIN": 1001,
10040
"SERVO10_MAX": 1999,
10041
})
10042
self.reboot_sitl()
10043
10044
# starts loose:
10045
self.wait_servo_channel_value(10, 0)
10046
10047
# 0 is down:
10048
self.start_subtest("Put gear down")
10049
self.run_cmd(mavutil.mavlink.MAV_CMD_AIRFRAME_CONFIGURATION, p2=0)
10050
self.wait_servo_channel_value(10, 1999)
10051
10052
# 1 is up:
10053
self.start_subtest("Put gear up")
10054
self.run_cmd_int(mavutil.mavlink.MAV_CMD_AIRFRAME_CONFIGURATION, p2=1)
10055
self.wait_servo_channel_value(10, 1001)
10056
10057
# 0 is down:
10058
self.start_subtest("Put gear down")
10059
self.run_cmd(mavutil.mavlink.MAV_CMD_AIRFRAME_CONFIGURATION, p2=0)
10060
self.wait_servo_channel_value(10, 1999)
10061
10062
self.context_pop()
10063
self.reboot_sitl()
10064
10065
def WatchAlts(self):
10066
'''Ensure we can monitor different altitudes'''
10067
self.takeoff(30, mode='GUIDED')
10068
self.delay_sim_time(5, reason='let altitude settle')
10069
10070
self.progress("Testing absolute altitudes")
10071
absolute_alt = self.get_altitude(altitude_source='SIM_STATE.alt')
10072
self.progress("absolute_alt=%f" % absolute_alt)
10073
epsilon = 4 # SIM_STATE and vehicle state can be off by a bit...
10074
for source in ['GLOBAL_POSITION_INT.alt', 'SIM_STATE.alt', 'GPS_RAW_INT.alt']:
10075
self.watch_altitude_maintained(
10076
absolute_alt-epsilon,
10077
absolute_alt+epsilon,
10078
altitude_source=source
10079
)
10080
10081
self.progress("Testing absolute altitudes")
10082
relative_alt = self.get_altitude(relative=True)
10083
for source in ['GLOBAL_POSITION_INT.relative_alt']:
10084
self.watch_altitude_maintained(
10085
relative_alt-epsilon,
10086
relative_alt+epsilon,
10087
altitude_source=source
10088
)
10089
10090
self.do_RTL()
10091
10092
def TestTetherStuck(self):
10093
"""Test tethered vehicle stuck because of tether"""
10094
# Enable tether simulation
10095
self.set_parameters({
10096
"SIM_TETH_ENABLE": 1,
10097
})
10098
self.delay_sim_time(2)
10099
self.reboot_sitl()
10100
10101
# Set tether line length
10102
self.set_parameters({
10103
"SIM_TETH_LINELEN": 10,
10104
})
10105
self.delay_sim_time(2)
10106
10107
# Prepare and take off
10108
self.wait_ready_to_arm()
10109
self.arm_vehicle()
10110
self.takeoff(10, mode='LOITER')
10111
10112
# Simulate vehicle getting stuck by increasing RC throttle
10113
self.set_rc(3, 1900)
10114
self.delay_sim_time(10, reason='let tether get stuck')
10115
10116
# Monitor behavior for 10 seconds
10117
tstart = self.get_sim_time()
10118
initial_alt = self.get_altitude(altitude_source='SIM_STATE.alt')
10119
self.progress(f"initial_alt={initial_alt}")
10120
stuck = True # Assume it's stuck unless proven otherwise
10121
10122
while self.get_sim_time() - tstart < 10:
10123
# Fetch current altitude
10124
current_alt = self.get_altitude(altitude_source='SIM_STATE.alt')
10125
self.progress(f"current_alt={current_alt}")
10126
10127
# Fetch and log battery status
10128
battery_status = self.assert_receive_message('BATTERY_STATUS')
10129
if battery_status:
10130
self.progress(f"Battery: {battery_status}")
10131
10132
# Check if the vehicle is stuck.
10133
# We assume the vehicle is stuck if the current is high and the altitude is not changing
10134
if battery_status and (battery_status.current_battery < 6500 or abs(current_alt - initial_alt) > 3):
10135
stuck = False # Vehicle moved or current is abnormal
10136
break
10137
10138
if not stuck:
10139
raise NotAchievedException("Vehicle did not get stuck as expected")
10140
10141
# Land and disarm the vehicle to ensure we can go down
10142
self.land_and_disarm()
10143
10144
def fly_rangefinder_drivers_fly(self, rangefinders):
10145
'''ensure rangefinder gives height-above-ground'''
10146
expected_alt = 5
10147
self.takeoff(expected_alt, mode='GUIDED', alt_minimum_duration=5)
10148
ds = self.assert_receive_message("DISTANCE_SENSOR")
10149
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
10150
delta = abs(ds.current_distance*0.01 - gpi.relative_alt/1000.0)
10151
alt_msg = f"{ds.current_distance*0.01=} disagrees with global-position-int.relative_alt ({gpi.relative_alt/1000.0}) by {delta}m" # NOQA:E501
10152
self.progress(alt_msg)
10153
self.progress(f"Terrain report: {self.mav.messages['TERRAIN_REPORT']}")
10154
if delta > 1:
10155
raise NotAchievedException(alt_msg)
10156
10157
for i in range(0, len(rangefinders)):
10158
name = rangefinders[i]
10159
self.progress("i=%u (%s)" % (i, name))
10160
ds = self.assert_receive_message(
10161
"DISTANCE_SENSOR",
10162
timeout=2,
10163
condition=f"DISTANCE_SENSOR.id=={i}",
10164
verbose=True,
10165
)
10166
if abs(ds.current_distance/100.0 - gpi.relative_alt/1000.0) > 1:
10167
raise NotAchievedException(
10168
"distance sensor.current_distance (%f) (%s) disagrees with global-position-int.relative_alt (%s)" %
10169
(ds.current_distance/100.0, name, gpi.relative_alt/1000.0))
10170
10171
self.land_and_disarm()
10172
10173
self.progress("Ensure RFND messages in log")
10174
if not self.current_onboard_log_contains_message("RFND"):
10175
raise NotAchievedException("No RFND messages in log")
10176
10177
def MAVProximity(self):
10178
'''Test MAVLink proximity driver'''
10179
self.start_subtest("Test mavlink proximity sensor using DISTANCE_SENSOR messages") # noqa
10180
self.set_parameter("SERIAL5_PROTOCOL", 1)
10181
self.set_parameter("PRX1_TYPE", 2) # mavlink
10182
self.reboot_sitl()
10183
10184
self.progress("Should be unhealthy while we don't send messages")
10185
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_PROXIMITY, True, True, False)
10186
10187
self.progress("Should be healthy while we're sending good messages")
10188
tstart = self.get_sim_time()
10189
while True:
10190
if self.get_sim_time() - tstart > 5:
10191
raise NotAchievedException("Sensor did not come good")
10192
self.mav.mav.distance_sensor_send(
10193
0, # time_boot_ms
10194
10, # min_distance cm
10195
50, # max_distance cm
10196
20, # current_distance cm
10197
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER, # type
10198
21, # id
10199
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE, # orientation
10200
255 # covariance
10201
)
10202
if self.sensor_has_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_PROXIMITY, True, True, True):
10203
self.progress("Sensor has good state")
10204
break
10205
self.delay_sim_time(0.1)
10206
10207
self.progress("Should be unhealthy again if we stop sending messages")
10208
self.delay_sim_time(1)
10209
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_PROXIMITY, True, True, False)
10210
10211
# now make sure we get echoed back the same sorts of things we send:
10212
# distances are in cm
10213
distance_map = {
10214
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 30,
10215
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 35,
10216
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 20,
10217
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 15,
10218
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 70,
10219
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 80,
10220
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 10,
10221
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 90,
10222
}
10223
10224
wanted_distances = copy.copy(distance_map)
10225
sensor_enum = mavutil.mavlink.enums["MAV_SENSOR_ORIENTATION"]
10226
10227
def my_message_hook(mav, m):
10228
if m.get_type() != 'DISTANCE_SENSOR':
10229
return
10230
self.progress("Got (%s)" % str(m))
10231
want = distance_map[m.orientation]
10232
got = m.current_distance
10233
# ArduPilot's floating point conversions make it imprecise:
10234
delta = abs(want-got)
10235
if delta > 1:
10236
self.progress(
10237
"Wrong distance (%s): want=%f got=%f" %
10238
(sensor_enum[m.orientation].name, want, got))
10239
return
10240
if m.orientation not in wanted_distances:
10241
return
10242
self.progress(
10243
"Correct distance (%s): want=%f got=%f" %
10244
(sensor_enum[m.orientation].name, want, got))
10245
del wanted_distances[m.orientation]
10246
10247
self.install_message_hook_context(my_message_hook)
10248
tstart = self.get_sim_time()
10249
while True:
10250
if self.get_sim_time() - tstart > 5:
10251
raise NotAchievedException("Sensor did not give right distances") # noqa
10252
for (orient, dist) in distance_map.items():
10253
self.mav.mav.distance_sensor_send(
10254
0, # time_boot_ms
10255
10, # min_distance cm
10256
90, # max_distance cm
10257
dist, # current_distance cm
10258
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER, # type
10259
21, # id
10260
orient, # orientation
10261
255 # covariance
10262
)
10263
self.wait_heartbeat()
10264
if len(wanted_distances.keys()) == 0:
10265
break
10266
10267
def fly_rangefinder_mavlink_distance_sensor(self):
10268
self.start_subtest("Test mavlink rangefinder using DISTANCE_SENSOR messages")
10269
self.context_push()
10270
self.set_parameters({
10271
"RTL_ALT_TYPE": 0,
10272
"LGR_ENABLE": 1,
10273
"LGR_DEPLOY_ALT": 1,
10274
"LGR_RETRACT_ALT": 10, # metres
10275
"SERVO10_FUNCTION": 29
10276
})
10277
ex = None
10278
try:
10279
self.set_parameter("SERIAL5_PROTOCOL", 1)
10280
self.set_parameter("RNGFND1_TYPE", 10)
10281
self.reboot_sitl()
10282
self.set_parameter("RNGFND1_MAX", 327.67)
10283
10284
self.progress("Should be unhealthy while we don't send messages")
10285
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION, True, True, False)
10286
10287
self.progress("Should be healthy while we're sending good messages")
10288
tstart = self.get_sim_time()
10289
while True:
10290
if self.get_sim_time() - tstart > 5:
10291
raise NotAchievedException("Sensor did not come good")
10292
self.mav.mav.distance_sensor_send(
10293
0, # time_boot_ms
10294
10, # min_distance
10295
50, # max_distance
10296
20, # current_distance
10297
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER, # type
10298
21, # id
10299
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270, # orientation
10300
255 # covariance
10301
)
10302
if self.sensor_has_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION, True, True, True):
10303
self.progress("Sensor has good state")
10304
break
10305
self.delay_sim_time(0.1)
10306
10307
self.progress("Should be unhealthy again if we stop sending messages")
10308
self.delay_sim_time(1)
10309
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION, True, True, False)
10310
10311
self.progress("Landing gear should deploy with current_distance below min_distance")
10312
self.change_mode('STABILIZE')
10313
timeout = 60
10314
tstart = self.get_sim_time()
10315
while not self.armed():
10316
if self.get_sim_time() - tstart > timeout:
10317
raise NotAchievedException("Failed to become armable after %f seconds" % timeout)
10318
self.mav.mav.distance_sensor_send(
10319
0, # time_boot_ms
10320
100, # min_distance (cm)
10321
2500, # max_distance (cm)
10322
200, # current_distance (cm)
10323
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER, # type
10324
21, # id
10325
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270, # orientation
10326
255 # covariance
10327
)
10328
try:
10329
self.arm_vehicle()
10330
except Exception:
10331
pass
10332
self.delay_sim_time(1) # servo function maps only periodically updated
10333
# self.send_debug_trap()
10334
10335
self.run_cmd(
10336
mavutil.mavlink.MAV_CMD_AIRFRAME_CONFIGURATION,
10337
p2=0, # deploy
10338
)
10339
10340
self.context_collect("STATUSTEXT")
10341
tstart = self.get_sim_time()
10342
while True:
10343
if self.get_sim_time_cached() - tstart > 5:
10344
raise NotAchievedException("Retraction did not happen")
10345
self.mav.mav.distance_sensor_send(
10346
0, # time_boot_ms
10347
100, # min_distance (cm)
10348
6000, # max_distance (cm)
10349
1500, # current_distance (cm)
10350
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER, # type
10351
21, # id
10352
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270, # orientation
10353
255 # covariance
10354
)
10355
self.delay_sim_time(0.1)
10356
try:
10357
self.wait_text("LandingGear: RETRACT", check_context=True, timeout=0.1)
10358
except Exception:
10359
continue
10360
self.progress("Retracted")
10361
break
10362
# self.send_debug_trap()
10363
while True:
10364
if self.get_sim_time_cached() - tstart > 5:
10365
raise NotAchievedException("Deployment did not happen")
10366
self.progress("Sending distance-sensor message")
10367
self.mav.mav.distance_sensor_send(
10368
0, # time_boot_ms
10369
300, # min_distance
10370
500, # max_distance
10371
250, # current_distance
10372
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER, # type
10373
21, # id
10374
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270, # orientation
10375
255 # covariance
10376
)
10377
try:
10378
self.wait_text("LandingGear: DEPLOY", check_context=True, timeout=0.1)
10379
except Exception:
10380
continue
10381
self.progress("Deployed")
10382
break
10383
self.disarm_vehicle()
10384
10385
except Exception as e:
10386
self.print_exception_caught(e)
10387
ex = e
10388
self.context_pop()
10389
self.reboot_sitl()
10390
if ex is not None:
10391
raise ex
10392
10393
def GSF(self):
10394
'''test the Gaussian Sum filter'''
10395
self.context_push()
10396
self.set_parameter("EK2_ENABLE", 1)
10397
self.reboot_sitl()
10398
self.takeoff(20, mode='LOITER')
10399
self.set_rc(2, 1400)
10400
self.delay_sim_time(5)
10401
self.set_rc(2, 1500)
10402
self.progress("Path: %s" % self.current_onboard_log_filepath())
10403
dfreader = self.dfreader_for_current_onboard_log()
10404
self.do_RTL()
10405
self.context_pop()
10406
self.reboot_sitl()
10407
10408
# ensure log messages present
10409
want = set(["XKY0", "XKY1", "NKY0", "NKY1"])
10410
still_want = want
10411
while len(still_want):
10412
m = dfreader.recv_match(type=want)
10413
if m is None:
10414
raise NotAchievedException("Did not get %s" % want)
10415
still_want.remove(m.get_type())
10416
10417
def GSF_reset(self):
10418
'''test the Gaussian Sum filter based Emergency reset'''
10419
self.context_push()
10420
self.set_parameters({
10421
"COMPASS_ORIENT": 4, # yaw 180
10422
"COMPASS_USE2": 0, # disable backup compasses to avoid pre-arm failures
10423
"COMPASS_USE3": 0,
10424
})
10425
self.reboot_sitl()
10426
self.change_mode('GUIDED')
10427
self.wait_ready_to_arm()
10428
10429
# record starting position
10430
startpos = self.assert_receive_message('LOCAL_POSITION_NED')
10431
self.progress("startpos=%s" % str(startpos))
10432
10433
# arm vehicle and takeoff to at least 5m
10434
self.arm_vehicle()
10435
expected_alt = 5
10436
self.user_takeoff(alt_min=expected_alt)
10437
10438
# watch for emergency yaw reset
10439
self.wait_text("EKF3 IMU. emergency yaw reset", timeout=5, regex=True)
10440
10441
# record how far vehicle flew off
10442
endpos = self.assert_receive_message('LOCAL_POSITION_NED')
10443
delta_x = endpos.x - startpos.x
10444
delta_y = endpos.y - startpos.y
10445
dist_m = math.sqrt(delta_x*delta_x + delta_y*delta_y)
10446
self.progress("GSF yaw reset triggered at %f meters" % dist_m)
10447
10448
self.do_RTL()
10449
self.context_pop()
10450
self.reboot_sitl()
10451
10452
# ensure vehicle did not fly too far
10453
dist_m_max = 8
10454
if dist_m > dist_m_max:
10455
raise NotAchievedException("GSF reset failed, vehicle flew too far (%f > %f)" % (dist_m, dist_m_max))
10456
10457
def FlyRangeFinderMAVlink(self):
10458
'''fly mavlink-connected rangefinder'''
10459
self.fly_rangefinder_mavlink_distance_sensor()
10460
10461
# explicit test for the mavlink driver as it doesn't play so nice:
10462
self.set_parameters({
10463
"SERIAL5_PROTOCOL": 1,
10464
"RNGFND1_TYPE": 10,
10465
"RTL_ALT_M": 5,
10466
"RTL_ALT_TYPE": 1,
10467
"SIM_TERRAIN": 0,
10468
})
10469
self.customise_SITL_commandline(['--serial5=sim:rf_mavlink'])
10470
10471
self.change_mode('GUIDED')
10472
self.wait_ready_to_arm()
10473
self.arm_vehicle()
10474
expected_alt = 5
10475
self.user_takeoff(alt_min=expected_alt)
10476
10477
self.context_push()
10478
self.context_set_message_rate_hz('RANGEFINDER', self.sitl_streamrate())
10479
10480
tstart = self.get_sim_time()
10481
while True:
10482
if self.get_sim_time() - tstart > 5:
10483
raise NotAchievedException("Mavlink rangefinder not working")
10484
rf = self.assert_receive_message("RANGEFINDER")
10485
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
10486
if abs(rf.distance - gpi.relative_alt/1000.0) > 1:
10487
self.progress("rangefinder alt (%s) disagrees with global-position-int.relative_alt (%s)" %
10488
(rf.distance, gpi.relative_alt/1000.0))
10489
continue
10490
10491
ds = self.assert_receive_message("DISTANCE_SENSOR", timeout=2, verbose=True)
10492
if abs(ds.current_distance/100.0 - gpi.relative_alt/1000.0) > 1:
10493
self.progress(
10494
"distance sensor.current_distance (%f) disagrees with global-position-int.relative_alt (%s)" %
10495
(ds.current_distance/100.0, gpi.relative_alt/1000.0))
10496
continue
10497
break
10498
10499
self.context_pop()
10500
10501
self.progress("mavlink rangefinder OK")
10502
self.land_and_disarm()
10503
10504
def MaxBotixI2CXL(self):
10505
'''Test maxbotix rangefinder drivers'''
10506
ex = None
10507
try:
10508
self.context_push()
10509
10510
self.start_subtest("No messages")
10511
self.assert_not_receiving_message("DISTANCE_SENSOR", timeout=5)
10512
10513
self.start_subtest("Default address")
10514
self.set_parameter("RNGFND1_TYPE", 2) # maxbotix
10515
self.reboot_sitl()
10516
self.do_timesync_roundtrip()
10517
self.assert_receive_message("DISTANCE_SENSOR", timeout=5, verbose=True)
10518
10519
self.start_subtest("Explicitly set to default address")
10520
self.set_parameters({
10521
"RNGFND1_TYPE": 2, # maxbotix
10522
"RNGFND1_ADDR": 0x70,
10523
})
10524
self.reboot_sitl()
10525
self.do_timesync_roundtrip()
10526
rf = self.assert_receive_message("DISTANCE_SENSOR", timeout=5, verbose=True)
10527
10528
self.start_subtest("Explicitly set to non-default address")
10529
self.set_parameter("RNGFND1_ADDR", 0x71)
10530
self.reboot_sitl()
10531
self.do_timesync_roundtrip()
10532
rf = self.assert_receive_message("DISTANCE_SENSOR", timeout=5, verbose=True)
10533
10534
self.start_subtest("Two MaxBotix RangeFinders")
10535
self.set_parameters({
10536
"RNGFND1_TYPE": 2, # maxbotix
10537
"RNGFND1_ADDR": 0x70,
10538
"RNGFND1_MIN": 1.50,
10539
"RNGFND2_TYPE": 2, # maxbotix
10540
"RNGFND2_ADDR": 0x71,
10541
"RNGFND2_MIN": 2.50,
10542
})
10543
self.reboot_sitl()
10544
self.do_timesync_roundtrip()
10545
for i in [0, 1]:
10546
rf = self.assert_receive_message(
10547
"DISTANCE_SENSOR",
10548
timeout=5,
10549
condition=f"DISTANCE_SENSOR.id=={i}",
10550
verbose=True,
10551
)
10552
expected_dist = 150
10553
if i == 1:
10554
expected_dist = 250
10555
if rf.min_distance != expected_dist:
10556
raise NotAchievedException("Unexpected min_cm (want=%u got=%u)" %
10557
(expected_dist, rf.min_distance))
10558
10559
self.context_pop()
10560
except Exception as e:
10561
self.print_exception_caught(e)
10562
ex = e
10563
10564
self.reboot_sitl()
10565
if ex is not None:
10566
raise ex
10567
10568
def FlyRangeFinderSITL(self):
10569
'''fly the type=100 perfect rangefinder'''
10570
self.set_parameters({
10571
"RNGFND1_TYPE": 100,
10572
"RTL_ALT_M": 5,
10573
"RTL_ALT_TYPE": 1,
10574
"SIM_TERRAIN": 0,
10575
})
10576
self.reboot_sitl()
10577
self.fly_rangefinder_drivers_fly([("unused", "sitl")])
10578
self.wait_disarmed()
10579
10580
def RangeFinderDrivers(self):
10581
'''Test rangefinder drivers'''
10582
self.set_parameters({
10583
"RTL_ALT_M": 5,
10584
"RTL_ALT_TYPE": 1,
10585
"SIM_TERRAIN": 0,
10586
})
10587
drivers = [
10588
("lightwareserial", 8), # autodetected between this and -binary
10589
("lightwareserial-binary", 8),
10590
("USD1_v0", 11),
10591
("USD1_v1", 11),
10592
("leddarone", 12),
10593
("maxsonarseriallv", 13),
10594
("nmea", 17, {"baud": 9600}),
10595
("wasp", 18),
10596
("benewake_tf02", 19),
10597
("blping", 23),
10598
("benewake_tfmini", 20),
10599
("lanbao", 26),
10600
("benewake_tf03", 27),
10601
("gyus42v2", 31),
10602
("teraranger_serial", 35),
10603
("nooploop_tofsense", 37),
10604
("ainsteinlrd1", 42),
10605
("rds02uf", 43),
10606
("lightware_grf", 45),
10607
]
10608
# you can use terrain - if you don't the vehicle just uses a
10609
# plane based on home.
10610
# self.install_terrain_handlers_context()
10611
while len(drivers):
10612
do_drivers = drivers[0:3]
10613
drivers = drivers[3:]
10614
command_line_args = []
10615
self.context_push()
10616
for offs in range(3):
10617
serial_num = offs + 4
10618
if len(do_drivers) > offs:
10619
if len(do_drivers[offs]) > 2:
10620
(sim_name, rngfnd_param_value, kwargs) = do_drivers[offs]
10621
else:
10622
(sim_name, rngfnd_param_value) = do_drivers[offs]
10623
kwargs = {}
10624
command_line_args.append("--serial%s=sim:%s" %
10625
(serial_num, sim_name))
10626
sets = {
10627
"SERIAL%u_PROTOCOL" % serial_num: 9, # rangefinder
10628
"RNGFND%u_TYPE" % (offs+1): rngfnd_param_value,
10629
}
10630
if "baud" in kwargs:
10631
sets["SERIAL%u_BAUD" % serial_num] = kwargs["baud"]
10632
self.set_parameters(sets)
10633
self.customise_SITL_commandline(command_line_args)
10634
self.fly_rangefinder_drivers_fly([x[0] for x in do_drivers])
10635
self.context_pop()
10636
10637
class I2CDriverToTest:
10638
def __init__(self, name, rngfnd_type, rngfnd_addr=None):
10639
self.name = name
10640
self.rngfnd_type = rngfnd_type
10641
self.rngfnd_addr = rngfnd_addr
10642
10643
i2c_drivers = [
10644
I2CDriverToTest("maxbotixi2cxl", 2),
10645
I2CDriverToTest("terarangeri2c", 14, rngfnd_addr=0x31),
10646
I2CDriverToTest("lightware_legacy16bit", 7, rngfnd_addr=0x66),
10647
I2CDriverToTest("tfs20l", 46, rngfnd_addr=0x10),
10648
I2CDriverToTest("TFMiniPlus", 25, rngfnd_addr=0x09),
10649
]
10650
while len(i2c_drivers):
10651
do_drivers = i2c_drivers[0:9]
10652
i2c_drivers = i2c_drivers[9:]
10653
count = 1
10654
p = {}
10655
for d in do_drivers:
10656
p[f"RNGFND{count}_TYPE"] = d.rngfnd_type
10657
if d.rngfnd_addr is not None:
10658
p[f"RNGFND{count}_ADDR"] = d.rngfnd_addr
10659
count += 1
10660
10661
self.set_parameters(p)
10662
10663
self.reboot_sitl()
10664
self.fly_rangefinder_drivers_fly([x.name for x in do_drivers])
10665
10666
def RangeFinderDriversMaxAlt_FlyDriver(self,
10667
name : str,
10668
rngfnd_type : int,
10669
simname : str,
10670
maxalt : float,
10671
sqalt : float | None = None,
10672
sq_at_sqalt : float | None = None,
10673
expected_statustext : str | None = None,
10674
):
10675
'''test max-height behaviour'''
10676
# lightwareserial goes to 130m when out of range
10677
self.progress(f"Flying {name}")
10678
self.set_parameters({
10679
"SERIAL4_PROTOCOL": 9,
10680
"RNGFND1_TYPE": rngfnd_type,
10681
"WPNAV_SPEED_UP": 1000, # cm/s
10682
})
10683
self.customise_SITL_commandline([
10684
f"--serial4=sim:{simname}",
10685
])
10686
takeoff_alt = sqalt
10687
if sqalt is None:
10688
takeoff_alt = maxalt
10689
self.context_set_message_rate_hz('RANGEFINDER', self.sitl_streamrate())
10690
self.takeoff(takeoff_alt, mode='GUIDED', timeout=240, max_err=0.5)
10691
self.assert_rangefinder_distance_between(takeoff_alt-5, takeoff_alt+5)
10692
10693
self.wait_rangefinder_distance(takeoff_alt-5, takeoff_alt+5)
10694
10695
rf_bit = mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION
10696
10697
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
10698
if sq_at_sqalt is not None:
10699
self.assert_distance_sensor_quality(sq_at_sqalt)
10700
10701
self.progress("Moving higher to be out of max rangefinder range")
10702
self.fly_guided_move_local(0, 0, takeoff_alt + 50)
10703
10704
# sensor remains healthy even out-of-range
10705
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
10706
10707
self.assert_distance_sensor_quality(1)
10708
10709
self.progress("Test behaviour above 655.35 metres (UINT16_MAX cm)")
10710
self.context_push()
10711
self.context_collect('STATUSTEXT')
10712
# self.send_debug_trap()
10713
self.fly_guided_move_local(0, 0, 700)
10714
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
10715
self.assert_distance_sensor_quality(1)
10716
if expected_statustext is not None:
10717
self.wait_statustext(expected_statustext, check_context=True)
10718
self.context_pop()
10719
10720
# life's too short for soft landings:
10721
self.disarm_vehicle(force=True)
10722
10723
def RangeFinderDriversMaxAlt_LightwareSerial(self) -> None:
10724
'''test max-height behaviour - LightwareSerial'''
10725
self.RangeFinderDriversMaxAlt_FlyDriver(
10726
name="LightwareSerial",
10727
rngfnd_type=8,
10728
simname='lightwareserial',
10729
maxalt=100,
10730
sqalt=95,
10731
sq_at_sqalt=100,
10732
expected_statustext=None,
10733
)
10734
10735
def RangeFinderDriversMaxAlt_AinsteinLRD1(self) -> None:
10736
'''test max-height behaviour - Ainstein LRD1 - pre v19.0.0 firmware'''
10737
self.context_collect('STATUSTEXT')
10738
self.RangeFinderDriversMaxAlt_FlyDriver(
10739
name="Ainstein-LR-D1",
10740
rngfnd_type=42,
10741
simname='ainsteinlrd1',
10742
maxalt=500,
10743
sqalt=495,
10744
sq_at_sqalt=39,
10745
expected_statustext='Altitude reading overflow',
10746
)
10747
self.wait_text("Rangefinder: Temperature alert", check_context=True)
10748
10749
def RangeFinderDriversMaxAlt_AinsteinLRD1_v19(self) -> None:
10750
'''test max-height behaviour - Ainstein LRD1 - v19.0.0 firmware'''
10751
self.context_collect('STATUSTEXT')
10752
self.RangeFinderDriversMaxAlt_FlyDriver(
10753
name="Ainstein-LR-D1-v19",
10754
rngfnd_type=42,
10755
simname='ainsteinlrd1_v19',
10756
maxalt=500,
10757
sqalt=495,
10758
sq_at_sqalt=39,
10759
)
10760
self.wait_text("Rangefinder: MCU Temperature alert", check_context=True)
10761
10762
def RangeFinderDriversLongRange(self):
10763
'''test rangefinder above 327m'''
10764
# FIXME: when we get a driver+simulator for a rangefinder with
10765
# >327m range change this to use that driver
10766
self.set_parameters({
10767
"SERIAL4_PROTOCOL": 9,
10768
"RNGFND1_TYPE": 19, # BenewakeTF02
10769
"WPNAV_SPEED_UP": 1000, # cm/s
10770
})
10771
self.customise_SITL_commandline([
10772
"--serial4=sim:benewake_tf02",
10773
])
10774
10775
text_good = "GOOD"
10776
text_out_of_range_high = "OUT_OF_RANGE_HIGH"
10777
10778
rf_bit = mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION
10779
10780
alt = 3
10781
self.takeoff(alt, mode='GUIDED')
10782
self.assert_parameter_value("RNGFND1_MAX", 7.00)
10783
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
10784
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
10785
if abs(m.current_distance * 0.01 - alt) > 1:
10786
raise NotAchievedException(f"Expected {alt}m range got range={m.current_distance * 0.01}")
10787
self.assert_parameter_value("RNGFND1_MAX", m.max_distance * 0.01, epsilon=0.00001)
10788
self.assert_parameter_value("RNGFND1_MIN", m.min_distance * 0.01, epsilon=0.00001)
10789
10790
self.send_statustext(text_good)
10791
10792
alt = 10
10793
self.fly_guided_move_local(0, 0, alt)
10794
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
10795
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
10796
if abs(m.current_distance * 0.01 - alt) > 1:
10797
raise NotAchievedException(f"Expected {alt}m range")
10798
self.assert_parameter_value("RNGFND1_MAX", m.max_distance * 0.01, epsilon=0.00001)
10799
self.assert_parameter_value("RNGFND1_MIN", m.min_distance * 0.01, epsilon=0.00001)
10800
self.send_statustext(text_out_of_range_high)
10801
10802
self.progress("Moving the goalposts")
10803
self.set_parameter("RNGFND1_MAX", 20)
10804
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
10805
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
10806
if abs(m.current_distance * 0.01 - alt) > 1:
10807
raise NotAchievedException(f"Expected {alt}m range")
10808
self.assert_parameter_value("RNGFND1_MAX", m.max_distance * 0.01, epsilon=0.00001)
10809
self.assert_parameter_value("RNGFND1_MIN", m.min_distance * 0.01, epsilon=0.00001)
10810
self.send_statustext(text_good)
10811
self.delay_sim_time(2)
10812
10813
dfreader = self.dfreader_for_current_onboard_log()
10814
10815
self.do_RTL()
10816
10817
self.progress("Checking in/out of range markers in log")
10818
required_range = None
10819
count = 0
10820
while True:
10821
m = dfreader.recv_match(type=["MSG", "RFND"])
10822
if m is None:
10823
break
10824
m_type = m.get_type()
10825
if m_type == "MSG":
10826
for t in [text_good, text_out_of_range_high]:
10827
if t in m.Message:
10828
required_range = t
10829
continue
10830
if m_type == "RFND":
10831
if required_range is None:
10832
continue
10833
if required_range == text_good:
10834
required_state = 4
10835
elif required_range == text_out_of_range_high:
10836
required_state = 3
10837
else:
10838
raise ValueError(f"Unexpected text {required_range}")
10839
if m.Stat != required_state:
10840
raise NotAchievedException(f"Not in expected state want={required_state} got={m.Stat} dist={m.Dist}")
10841
self.progress(f"In expected state {required_range}")
10842
required_range = None
10843
count += 1
10844
10845
if count < 3:
10846
raise NotAchievedException("Did not see range markers")
10847
10848
def RangeFinderSITLLongRange(self):
10849
'''test rangefinder above 327m'''
10850
# FIXME: when we get a driver+simulator for a rangefinder with
10851
# >327m range change this to use that driver
10852
self.set_parameters({
10853
"RNGFND1_TYPE": 100, # SITL
10854
"WPNAV_SPEED_UP": 1000, # cm/s
10855
"RNGFND1_MAX": 7000, # metres
10856
})
10857
self.reboot_sitl()
10858
10859
text_good = "GOOD"
10860
text_high = "HIGH"
10861
10862
rf_bit = mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION
10863
10864
alt = 3
10865
self.takeoff(alt, mode='GUIDED')
10866
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
10867
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
10868
got = m.current_distance * 0.01
10869
if abs(got - alt) > 1:
10870
raise NotAchievedException(f"Expected {alt}m range {got=}")
10871
10872
self.send_statustext(text_good)
10873
10874
alt = 635
10875
self.fly_guided_move_local(0, 0, alt)
10876
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
10877
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
10878
if abs(m.current_distance * 0.01 - alt) > 1:
10879
raise NotAchievedException(f"Expected {alt}m range")
10880
10881
self.send_statustext(text_high)
10882
10883
dfreader = self.dfreader_for_current_onboard_log()
10884
10885
# fast descent!
10886
def hook(msg, m):
10887
if m.get_type() != 'HEARTBEAT':
10888
return
10889
# tell vehicle to only pay attention to the attitude:
10890
bitmask = 0
10891
bitmask |= mavutil.mavlink.ATTITUDE_TARGET_TYPEMASK_BODY_ROLL_RATE_IGNORE
10892
bitmask |= mavutil.mavlink.ATTITUDE_TARGET_TYPEMASK_BODY_PITCH_RATE_IGNORE
10893
bitmask |= mavutil.mavlink.ATTITUDE_TARGET_TYPEMASK_BODY_YAW_RATE_IGNORE
10894
bitmask |= mavutil.mavlink.ATTITUDE_TARGET_TYPEMASK_THROTTLE_IGNORE
10895
10896
self.mav.mav.set_attitude_target_send(
10897
0, # time_boot_ms
10898
1, # target sysid
10899
1, # target compid
10900
0, # bitmask of things to ignore
10901
mavextra.euler_to_quat([
10902
math.radians(-180),
10903
math.radians(0),
10904
math.radians(0)]), # att
10905
0, # roll rate (rad/s)
10906
0, # pitch rate (rad/s)
10907
0, # yaw rate (rad/s)
10908
1 # thrust, 0 to 1, translated to a climb/descent rate
10909
)
10910
10911
self.install_message_hook_context(hook)
10912
10913
self.wait_altitude(0, 30, timeout=200, relative=True)
10914
self.do_RTL()
10915
10916
self.progress("Checking in/out of range markers in log")
10917
good = False
10918
while True:
10919
m = dfreader.recv_match(type=["MSG", "RFND"])
10920
if m is None:
10921
break
10922
m_type = m.get_type()
10923
if m_type == "RFND":
10924
if m.Dist < 600:
10925
continue
10926
good = True
10927
break
10928
10929
if not good:
10930
raise NotAchievedException("Did not see good alt")
10931
10932
def ShipTakeoff(self):
10933
'''Fly Simulated Ship Takeoff'''
10934
# test ship takeoff
10935
self.wait_groundspeed(0, 2)
10936
self.set_parameters({
10937
"SIM_SHIP_ENABLE": 1,
10938
"SIM_SHIP_SPEED": 10,
10939
"SIM_SHIP_DSIZE": 2,
10940
})
10941
self.wait_ready_to_arm()
10942
# we should be moving with the ship
10943
self.wait_groundspeed(9, 11)
10944
self.takeoff(10)
10945
# above ship our speed drops to 0
10946
self.wait_groundspeed(0, 2)
10947
self.land_and_disarm()
10948
# ship will have moved on, so we land on the water which isn't moving
10949
self.wait_groundspeed(0, 2)
10950
10951
def ParameterValidation(self):
10952
'''Test parameters are checked for validity'''
10953
# wait 10 seconds for initialisation
10954
self.delay_sim_time(10)
10955
self.progress("invalid; min must be less than max:")
10956
self.set_parameters({
10957
"MOT_PWM_MIN": 100,
10958
"MOT_PWM_MAX": 50,
10959
})
10960
self.drain_mav()
10961
self.assert_prearm_failure("Motors: Check MOT_PWM_MIN and MOT_PWM_MAX")
10962
self.progress("invalid; min must be less than max (equal case):")
10963
self.set_parameters({
10964
"MOT_PWM_MIN": 100,
10965
"MOT_PWM_MAX": 100,
10966
})
10967
self.drain_mav()
10968
self.assert_prearm_failure("Motors: Check MOT_PWM_MIN and MOT_PWM_MAX")
10969
self.progress("Spin min more than 0.3")
10970
self.set_parameters({
10971
"MOT_PWM_MIN": 1000,
10972
"MOT_PWM_MAX": 2000,
10973
"MOT_SPIN_MIN": 0.5,
10974
})
10975
self.drain_mav()
10976
self.assert_prearm_failure("PreArm: Motors: MOT_SPIN_MIN too high 0.50 > 0.3")
10977
self.progress("Spin arm more than spin min")
10978
self.set_parameters({
10979
"MOT_SPIN_MIN": 0.1,
10980
"MOT_SPIN_ARM": 0.2,
10981
})
10982
self.drain_mav()
10983
self.assert_prearm_failure("PreArm: Motors: MOT_SPIN_ARM > MOT_SPIN_MIN")
10984
10985
def SensorErrorFlags(self):
10986
'''Test we get ERR messages when sensors have issues'''
10987
self.reboot_sitl()
10988
10989
for (param_names, param_value, expected_subsys, expected_ecode, desc) in [
10990
(['SIM_BARO_DISABLE', 'SIM_BAR2_DISABLE'], 1, 18, 4, 'unhealthy'),
10991
(['SIM_BARO_DISABLE', 'SIM_BAR2_DISABLE'], 0, 18, 0, 'healthy'),
10992
(['SIM_MAG1_FAIL', 'SIM_MAG2_FAIL', 'SIM_MAG3_FAIL'], 1, 3, 4, 'unhealthy'),
10993
(['SIM_MAG1_FAIL', 'SIM_MAG2_FAIL', 'SIM_MAG3_FAIL'], 0, 3, 0, 'healthy'),
10994
]:
10995
sp = dict()
10996
for name in param_names:
10997
sp[name] = param_value
10998
self.set_parameters(sp)
10999
self.delay_sim_time(1)
11000
mlog = self.dfreader_for_current_onboard_log()
11001
success = False
11002
while True:
11003
m = mlog.recv_match(type='ERR')
11004
print("Got (%s)" % str(m))
11005
if m is None:
11006
break
11007
if m.Subsys == expected_subsys and m.ECode == expected_ecode: # baro / ecode
11008
success = True
11009
break
11010
if not success:
11011
raise NotAchievedException("Did not find %s log message" % desc)
11012
11013
def AltEstimation(self):
11014
'''Test that Alt Estimation is mandatory for ALT_HOLD'''
11015
# disable barometer so there is no altitude source
11016
self.set_parameters({
11017
"SIM_BARO_DISABLE": 1,
11018
"SIM_BAR2_DISABLE": 1,
11019
})
11020
11021
self.wait_gps_disable(position_vertical=True)
11022
11023
# turn off arming checks (mandatory arming checks will still be run)
11024
self.set_parameter("ARMING_SKIPCHK", -1)
11025
11026
# delay 12 sec to allow EKF to lose altitude estimate
11027
self.delay_sim_time(12)
11028
11029
self.change_mode("ALT_HOLD")
11030
self.assert_prearm_failure("Need Alt Estimate")
11031
11032
# arm vehicle in stabilize to bypass barometer pre-arm checks
11033
self.change_mode("STABILIZE")
11034
self.arm_vehicle()
11035
self.set_rc(3, 1700)
11036
try:
11037
self.change_mode("ALT_HOLD", timeout=10)
11038
except AutoTestTimeoutException:
11039
self.progress("PASS not able to set mode without Position : %s" % "ALT_HOLD")
11040
11041
# check that mode change to ALT_HOLD has failed (it should)
11042
if self.mode_is("ALT_HOLD"):
11043
raise NotAchievedException("Changed to ALT_HOLD with no altitude estimate")
11044
self.disarm_vehicle(force=True)
11045
11046
def EKFSource(self):
11047
'''Check EKF Source Prearms work'''
11048
self.wait_ready_to_arm()
11049
11050
self.start_subtest("bad yaw source")
11051
self.set_parameter("EK3_SRC3_YAW", 17)
11052
self.assert_prearm_failure("Check EK3_SRC3_YAW")
11053
11054
self.context_push()
11055
self.start_subtest("missing required yaw source")
11056
self.set_parameters({
11057
"EK3_SRC3_YAW": 3, # External Yaw with Compass Fallback
11058
"COMPASS_USE": 0,
11059
"COMPASS_USE2": 0,
11060
"COMPASS_USE3": 0,
11061
})
11062
self.assert_prearm_failure("EK3 sources require Compass")
11063
self.context_pop()
11064
11065
def EK3_EXT_NAV_vel_without_vert(self):
11066
'''Test that EK3 External Navigation velocity works without vertical velocity.'''
11067
11068
# Setup Vicon system and EKF3 parameters
11069
self.set_parameters({
11070
"VISO_TYPE": 2, # Enable Vicon system
11071
"SERIAL5_PROTOCOL": 2,
11072
"EK3_ENABLE": 1, # Enable EKF3
11073
"EK3_SRC2_POSXY": 6, # External Navigation for horizontal position
11074
"EK3_SRC2_POSZ": 1, # Use Barometer for altitude
11075
"EK3_SRC2_VELXY": 6, # External Navigation for horizontal velocity
11076
"EK3_SRC2_VELZ": 0, # No vertical velocity input
11077
"EK3_SRC2_YAW": 1, # Use Compass for yaw
11078
"EK3_SRC_OPTIONS": 0, # Dont fuse all velocity sources
11079
"RC8_OPTION": 90, # RC aux switch 8 set to EKF source selector
11080
"AHRS_EKF_TYPE": 3, # Enable EKF3
11081
})
11082
11083
# Customize SITL command line for Vicon simulation
11084
self.customise_SITL_commandline(["--serial5=sim:vicon:"])
11085
11086
# Switch to use Vicon
11087
self.set_rc(8, 1500)
11088
11089
# Take off
11090
self.progress("Moving")
11091
self.takeoffAndMoveAway()
11092
11093
# Increase Vicon vertical velocity error
11094
self.set_parameter("SIM_VICON_VGLI_Z", 1)
11095
11096
# Set innovation limit
11097
innov_limit = 0.1 # Threshold for velocity variance
11098
11099
max_variance = 0.0
11100
tstart = self.get_sim_time()
11101
while True:
11102
msg = self.assert_receive_message(type="EKF_STATUS_REPORT")
11103
variance = msg.velocity_variance
11104
max_variance = max(max_variance, variance)
11105
if variance > innov_limit:
11106
raise NotAchievedException(
11107
f"Velocity variance too high {variance:.2f} > {innov_limit:.2f}"
11108
)
11109
if self.get_sim_time_cached() - tstart > 10:
11110
break
11111
print(f"Max variance before enabling vertical velocity fusion: {max_variance:.2f}")
11112
11113
# Now enable vertical velocity fusion and check for innovation spike
11114
self.set_parameter("EK3_SRC2_VELZ", 6)
11115
11116
spike_detected = False
11117
max_variance = 0.0
11118
tstart = self.get_sim_time()
11119
while True:
11120
msg = self.assert_receive_message(type="EKF_STATUS_REPORT")
11121
variance = msg.velocity_variance
11122
max_variance = max(max_variance, variance)
11123
if variance > innov_limit:
11124
spike_detected = True
11125
if self.get_sim_time_cached() - tstart > 10:
11126
break
11127
print(f"Max variance after enabling vertical velocity fusion: {max_variance:.2f}")
11128
11129
if not spike_detected:
11130
raise NotAchievedException("Velocity variance did not spike as expected after enabling vertical velocity fusion.")
11131
11132
self.disarm_vehicle(force=True)
11133
11134
def test_replay_gps_bit(self):
11135
self.set_parameters({
11136
"LOG_REPLAY": 1,
11137
"LOG_DISARMED": 1,
11138
"EK3_ENABLE": 1,
11139
"EK2_ENABLE": 1,
11140
"AHRS_TRIM_X": 0.01,
11141
"AHRS_TRIM_Y": -0.03,
11142
"GPS2_TYPE": 1,
11143
"GPS1_POS_X": 0.1,
11144
"GPS1_POS_Y": 0.2,
11145
"GPS1_POS_Z": 0.3,
11146
"GPS2_POS_X": -0.1,
11147
"GPS2_POS_Y": -0.02,
11148
"GPS2_POS_Z": -0.31,
11149
"INS_POS1_X": 0.12,
11150
"INS_POS1_Y": 0.14,
11151
"INS_POS1_Z": -0.02,
11152
"INS_POS2_X": 0.07,
11153
"INS_POS2_Y": 0.012,
11154
"INS_POS2_Z": -0.06,
11155
"RNGFND1_TYPE": 1,
11156
"RNGFND1_PIN": 0,
11157
"RNGFND1_SCALING": 30,
11158
"RNGFND1_POS_X": 0.17,
11159
"RNGFND1_POS_Y": -0.07,
11160
"RNGFND1_POS_Z": -0.005,
11161
"SIM_SONAR_SCALE": 30,
11162
"SIM_GPS2_ENABLE": 1,
11163
})
11164
self.reboot_sitl()
11165
11166
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_LOGGING, True, True, True)
11167
11168
current_log_filepath = self.current_onboard_log_filepath()
11169
self.progress("Current log path: %s" % str(current_log_filepath))
11170
11171
self.change_mode("LOITER")
11172
self.wait_ready_to_arm(require_absolute=True)
11173
self.arm_vehicle()
11174
self.takeoffAndMoveAway()
11175
self.do_RTL()
11176
11177
self.reboot_sitl()
11178
11179
return current_log_filepath
11180
11181
def test_replay_gps_yaw_bit(self):
11182
self.load_default_params_file("copter-gps-for-yaw.parm")
11183
self.set_parameters({
11184
"LOG_REPLAY": 1,
11185
"LOG_DISARMED": 1,
11186
})
11187
self.reboot_sitl()
11188
11189
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_LOGGING, True, True, True)
11190
self.wait_gps_fix_type_gte(6, message_type="GPS2_RAW", verbose=True)
11191
11192
current_log_filepath = self.current_onboard_log_filepath()
11193
self.progress("Current log path: %s" % str(current_log_filepath))
11194
11195
self.change_mode("LOITER")
11196
self.wait_ready_to_arm(require_absolute=True)
11197
self.arm_vehicle()
11198
self.takeoffAndMoveAway()
11199
self.do_RTL()
11200
11201
self.reboot_sitl()
11202
11203
return current_log_filepath
11204
11205
def test_replay_beacon_bit(self):
11206
self.set_parameters({
11207
"LOG_REPLAY": 1,
11208
"LOG_DISARMED": 1,
11209
})
11210
11211
old_onboard_logs = sorted(self.log_list())
11212
self.BeaconPosition()
11213
new_onboard_logs = sorted(self.log_list())
11214
11215
self.reboot_sitl()
11216
11217
log_difference = [x for x in new_onboard_logs if x not in old_onboard_logs]
11218
return log_difference[1] # index depends on the reboots and ordering thereof in BeaconPosition!
11219
11220
def test_replay_optical_flow_bit(self):
11221
self.set_parameters({
11222
"LOG_REPLAY": 1,
11223
"LOG_DISARMED": 1,
11224
})
11225
11226
old_onboard_logs = sorted(self.log_list())
11227
self.OpticalFlowLimits()
11228
new_onboard_logs = sorted(self.log_list())
11229
11230
log_difference = [x for x in new_onboard_logs if x not in old_onboard_logs]
11231
print("log difference: %s" % str(log_difference))
11232
return log_difference[0]
11233
11234
def test_replay_wind_and_airspeed_bit(self):
11235
self.set_parameters({
11236
"LOG_REPLAY": 1,
11237
"LOG_DISARMED": 1,
11238
"EK3_ENABLE": 1,
11239
"WP_YAW_BEHAVIOR": 1, # face into wind
11240
11241
"EK3_DRAG_BCOEF_X": 17.209, # wind estimation must be on for airspeed
11242
"EK3_DRAG_BCOEF_Y": 17.209, # these are the suggested values printed out by sitl
11243
"EK3_DRAG_MCOEF": 0.209,
11244
"SIM_WIND_SPD": 3,
11245
11246
"ARSPD_ENABLE": 1,
11247
"ARSPD_TYPE": 100,
11248
"ARSPD_USE": 1, # technically not actually supported on copter
11249
})
11250
self.reboot_sitl()
11251
11252
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_LOGGING, True, True, True)
11253
11254
current_log_filepath = self.current_onboard_log_filepath()
11255
self.progress("Current log path: %s" % str(current_log_filepath))
11256
11257
self.change_mode("LOITER")
11258
self.wait_ready_to_arm(require_absolute=True)
11259
# make sure airspeed is being used
11260
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_DIFFERENTIAL_PRESSURE, True, True, True)
11261
self.arm_vehicle()
11262
self.takeoffAndMoveAway()
11263
11264
# make sure airspeed was fused at some point after some flying
11265
m = self.assert_receive_message('EKF_STATUS_REPORT')
11266
if m.airspeed_variance == 0:
11267
raise NotAchievedException("never fused airspeed")
11268
11269
self.do_RTL()
11270
11271
self.reboot_sitl()
11272
11273
return current_log_filepath
11274
11275
def GPSBlendingLog(self):
11276
'''Test GPS Blending'''
11277
'''ensure we get dataflash log messages for blended instance'''
11278
# configure:
11279
self.set_parameters({
11280
"GPS2_TYPE": 1,
11281
"SIM_GPS2_TYPE": 1,
11282
"SIM_GPS2_ENABLE": 1,
11283
"GPS_AUTO_SWITCH": 2,
11284
})
11285
self.reboot_sitl()
11286
11287
# ensure we're seeing the second GPS:
11288
tstart = self.get_sim_time()
11289
while True:
11290
if self.get_sim_time_cached() - tstart > 60:
11291
raise NotAchievedException("Did not get good GPS2_RAW message")
11292
m = self.assert_receive_message('GPS2_RAW', verbose=True)
11293
if m.lat == 0:
11294
continue
11295
break
11296
11297
# create a log we can expect blended data to appear in:
11298
self.change_mode('LOITER')
11299
self.wait_ready_to_arm()
11300
self.arm_vehicle()
11301
self.delay_sim_time(5)
11302
self.disarm_vehicle()
11303
11304
# inspect generated log for messages:
11305
dfreader = self.dfreader_for_current_onboard_log()
11306
wanted = set([0, 1, 2])
11307
seen_primary_change = False
11308
while True:
11309
m = dfreader.recv_match(type=["GPS", "EV"]) # disarmed
11310
if m is None:
11311
break
11312
mtype = m.get_type()
11313
if mtype == 'GPS':
11314
try:
11315
wanted.remove(m.I)
11316
except KeyError:
11317
continue
11318
elif mtype == 'EV':
11319
if m.Id == 67: # GPS_PRIMARY_CHANGED
11320
seen_primary_change = True
11321
if len(wanted) == 0 and seen_primary_change:
11322
break
11323
11324
if len(wanted):
11325
raise NotAchievedException("Did not get all three GPS types")
11326
if not seen_primary_change:
11327
raise NotAchievedException("Did not see primary change")
11328
11329
def GPSBlending(self):
11330
'''Test GPS Blending'''
11331
'''ensure we get dataflash log messages for blended instance'''
11332
11333
# configure:
11334
self.set_parameters({
11335
"WP_YAW_BEHAVIOR": 0, # do not yaw
11336
"GPS2_TYPE": 1,
11337
"SIM_GPS2_TYPE": 1,
11338
"SIM_GPS2_ENABLE": 1,
11339
"SIM_GPS1_POS_X": 1.0,
11340
"SIM_GPS1_POS_Y": -1.0,
11341
"SIM_GPS2_POS_X": -1.0,
11342
"SIM_GPS2_POS_Y": 1.0,
11343
"GPS_AUTO_SWITCH": 2,
11344
})
11345
self.reboot_sitl()
11346
11347
alt = 10
11348
self.takeoff(alt, mode='GUIDED')
11349
self.fly_guided_move_local(30, 0, alt)
11350
self.fly_guided_move_local(30, 30, alt)
11351
self.fly_guided_move_local(0, 30, alt)
11352
self.fly_guided_move_local(0, 0, alt)
11353
self.change_mode('LAND')
11354
11355
current_log_file = self.dfreader_for_current_onboard_log()
11356
11357
self.wait_disarmed()
11358
11359
# ensure that the blended solution is always about half-way
11360
# between the two GPSs:
11361
passes = 0
11362
current_ts = None
11363
while True:
11364
m = current_log_file.recv_match(type='GPS')
11365
if m is None:
11366
break
11367
if current_ts is None:
11368
if m.I != 0: # noqa
11369
continue
11370
current_ts = m.TimeUS
11371
measurements = {}
11372
if m.TimeUS != current_ts:
11373
current_ts = None
11374
continue
11375
measurements[m.I] = (m.Lat, m.Lng)
11376
if len(measurements) == 3:
11377
# check lat:
11378
for n in 0, 1:
11379
expected_blended = (measurements[0][n] + measurements[1][n])/2
11380
epsilon = 0.0000002
11381
error = abs(measurements[2][n] - expected_blended)
11382
if error > epsilon:
11383
raise NotAchievedException("Blended diverged")
11384
current_ts = None
11385
passes += 1
11386
11387
if passes == 0:
11388
raise NotAchievedException("Did not see three GPS measurements!")
11389
11390
def GPSWeightedBlending(self):
11391
'''Test GPS Weighted Blending'''
11392
11393
self.context_push()
11394
11395
# configure:
11396
self.set_parameters({
11397
"WP_YAW_BEHAVIOR": 0, # do not yaw
11398
"GPS2_TYPE": 1,
11399
"SIM_GPS2_TYPE": 1,
11400
"SIM_GPS2_ENABLE": 1,
11401
"SIM_GPS1_POS_X": 1.0,
11402
"SIM_GPS1_POS_Y": -1.0,
11403
"SIM_GPS2_POS_X": -1.0,
11404
"SIM_GPS2_POS_Y": 1.0,
11405
"GPS_AUTO_SWITCH": 2,
11406
})
11407
# configure velocity errors such that the 1st GPS should be
11408
# 4/5, second GPS 1/5 of result (0.5**2)/((0.5**2)+(1.0**2))
11409
self.set_parameters({
11410
"SIM_GPS1_VERR_X": 0.3, # m/s
11411
"SIM_GPS1_VERR_Y": 0.4,
11412
"SIM_GPS2_VERR_X": 0.6, # m/s
11413
"SIM_GPS2_VERR_Y": 0.8,
11414
"GPS_BLEND_MASK": 4, # use only speed for blend calculations
11415
})
11416
self.reboot_sitl()
11417
11418
alt = 10
11419
self.takeoff(alt, mode='GUIDED')
11420
self.fly_guided_move_local(30, 0, alt)
11421
self.fly_guided_move_local(30, 30, alt)
11422
self.fly_guided_move_local(0, 30, alt)
11423
self.fly_guided_move_local(0, 0, alt)
11424
self.change_mode('LAND')
11425
11426
current_log_file = self.dfreader_for_current_onboard_log()
11427
11428
self.wait_disarmed()
11429
11430
# ensure that the blended solution is always about half-way
11431
# between the two GPSs:
11432
current_ts = None
11433
while True:
11434
m = current_log_file.recv_match(type='GPS')
11435
if m is None:
11436
break
11437
if current_ts is None:
11438
if m.I != 0: # noqa
11439
continue
11440
current_ts = m.TimeUS
11441
measurements = {}
11442
if m.TimeUS != current_ts:
11443
current_ts = None
11444
continue
11445
measurements[m.I] = (m.Lat, m.Lng, m.Alt)
11446
if len(measurements) == 3:
11447
# check lat:
11448
for n in 0, 1, 2:
11449
expected_blended = 0.8*measurements[0][n] + 0.2*measurements[1][n]
11450
axis_epsilons = [0.0000002, 0.0000002, 0.2]
11451
epsilon = axis_epsilons[n]
11452
error = abs(measurements[2][n] - expected_blended)
11453
if error > epsilon:
11454
raise NotAchievedException(f"Blended diverged {n=} {measurements[0][n]=} {measurements[1][n]=}")
11455
current_ts = None
11456
11457
self.context_pop()
11458
self.reboot_sitl()
11459
11460
def GPSBlendingAffinity(self):
11461
'''test blending when affinity in use'''
11462
# configure:
11463
self.set_parameters({
11464
"WP_YAW_BEHAVIOR": 0, # do not yaw
11465
"GPS2_TYPE": 1,
11466
"SIM_GPS2_TYPE": 1,
11467
"SIM_GPS2_ENABLE": 1,
11468
"SIM_GPS1_POS_X": 1.0,
11469
"SIM_GPS1_POS_Y": -1.0,
11470
"SIM_GPS2_POS_X": -1.0,
11471
"SIM_GPS2_POS_Y": 1.0,
11472
"GPS_AUTO_SWITCH": 2,
11473
11474
"EK3_AFFINITY" : 1,
11475
"EK3_IMU_MASK": 7,
11476
"SIM_IMU_COUNT": 3,
11477
"INS_ACC3OFFS_X": 0.001,
11478
"INS_ACC3OFFS_Y": 0.001,
11479
"INS_ACC3OFFS_Z": 0.001,
11480
})
11481
# force-calibration of accel:
11482
self.run_cmd(mavutil.mavlink.MAV_CMD_PREFLIGHT_CALIBRATION, p5=76)
11483
self.reboot_sitl()
11484
11485
alt = 10
11486
self.takeoff(alt, mode='GUIDED')
11487
self.fly_guided_move_local(30, 0, alt)
11488
self.fly_guided_move_local(30, 30, alt)
11489
self.fly_guided_move_local(0, 30, alt)
11490
self.fly_guided_move_local(0, 0, alt)
11491
self.change_mode('LAND')
11492
11493
current_log_file = self.dfreader_for_current_onboard_log()
11494
11495
self.wait_disarmed()
11496
11497
# ensure that the blended solution is always about half-way
11498
# between the two GPSs:
11499
current_ts = None
11500
max_errors = [0, 0, 0]
11501
while True:
11502
m = current_log_file.recv_match(type='XKF1')
11503
if m is None:
11504
break
11505
if current_ts is None:
11506
if m.C != 0: # noqa
11507
continue
11508
current_ts = m.TimeUS
11509
measurements = {}
11510
if m.TimeUS != current_ts:
11511
current_ts = None
11512
continue
11513
measurements[m.C] = (m.PN, m.PE, m.PD)
11514
if len(measurements) == 3:
11515
# check lat:
11516
axis_epsilons = [0.05, 0.05, 0.06]
11517
for n in 0, 1, 2:
11518
expected_blended = 0.5*measurements[0][n] + 0.5*measurements[1][n]
11519
epsilon = axis_epsilons[n]
11520
error = abs(measurements[2][n] - expected_blended)
11521
# self.progress(f"{n=} {error=}")
11522
if error > max_errors[n]:
11523
max_errors[n] = error
11524
if error > epsilon:
11525
raise NotAchievedException(f"Blended diverged {n=} {measurements[0][n]=} {measurements[1][n]=} {measurements[2][n]=} {error=}") # noqa:E501
11526
current_ts = None
11527
self.progress(f"{max_errors=}")
11528
11529
def Callisto(self):
11530
'''Test Callisto'''
11531
self.customise_SITL_commandline(
11532
[],
11533
defaults_filepath=self.model_defaults_filepath('Callisto'),
11534
model="octa-quad:@ROMFS/models/Callisto.json",
11535
wipe=True,
11536
)
11537
self.takeoff(10)
11538
self.do_RTL()
11539
11540
def FlyEachFrame(self):
11541
'''Fly each supported internal frame'''
11542
vinfo = vehicleinfo.VehicleInfo()
11543
copter_vinfo_options = vinfo.options[self.vehicleinfo_key()]
11544
known_broken_frames = {
11545
'heli-compound': "wrong binary, different takeoff regime",
11546
'heli-dual': "wrong binary, different takeoff regime",
11547
'heli': "wrong binary, different takeoff regime",
11548
'heli-gas': "wrong binary, different takeoff regime",
11549
'heli-blade360': "wrong binary, different takeoff regime",
11550
"quad-can" : "needs CAN periph",
11551
}
11552
for frame in sorted(copter_vinfo_options["frames"].keys()):
11553
self.start_subtest("Testing frame (%s)" % str(frame))
11554
if frame in known_broken_frames:
11555
self.progress("Actually, no I'm not - it is known-broken (%s)" %
11556
(known_broken_frames[frame]))
11557
continue
11558
frame_bits = copter_vinfo_options["frames"][frame]
11559
print("frame_bits: %s" % str(frame_bits))
11560
if frame_bits.get("external", False):
11561
self.progress("Actually, no I'm not - it is an external simulation")
11562
continue
11563
model = frame_bits.get("model", frame)
11564
defaults = self.model_defaults_filepath(frame)
11565
if not isinstance(defaults, list):
11566
defaults = [defaults]
11567
self.context_push()
11568
frame_script = frame_bits.get('frame_example_script', None)
11569
if frame_script is not None:
11570
self.install_example_script_context(frame_script)
11571
self.customise_SITL_commandline(
11572
[],
11573
defaults_filepath=defaults,
11574
model=model,
11575
wipe=True,
11576
)
11577
if frame_script is not None:
11578
self.set_parameters({
11579
"SCR_ENABLE": 1,
11580
"LOG_BITMASK": 65535,
11581
})
11582
self.reboot_sitl()
11583
11584
# add a listener that verifies yaw looks good:
11585
def verify_yaw(mav, m):
11586
if m.get_type() != 'ATTITUDE':
11587
return
11588
yawspeed_thresh_rads = math.radians(20)
11589
if m.yawspeed > yawspeed_thresh_rads:
11590
raise NotAchievedException("Excessive yaw on takeoff: %f deg/s > %f deg/s (frame=%s)" %
11591
(math.degrees(m.yawspeed), math.degrees(yawspeed_thresh_rads), frame))
11592
self.context_push()
11593
self.install_message_hook_context(verify_yaw)
11594
self.takeoff(10)
11595
self.context_pop()
11596
self.hover()
11597
self.change_mode('ALT_HOLD')
11598
self.delay_sim_time(1)
11599
11600
def verify_rollpitch(mav, m):
11601
if m.get_type() != 'ATTITUDE':
11602
return
11603
pitch_thresh_rad = math.radians(2)
11604
if m.pitch > pitch_thresh_rad:
11605
raise NotAchievedException("Excessive pitch %f deg > %f deg" %
11606
(math.degrees(m.pitch), math.degrees(pitch_thresh_rad)))
11607
roll_thresh_rad = math.radians(2)
11608
if m.roll > roll_thresh_rad:
11609
raise NotAchievedException("Excessive roll %f deg > %f deg" %
11610
(math.degrees(m.roll), math.degrees(roll_thresh_rad)))
11611
self.context_push()
11612
self.install_message_hook_context(verify_rollpitch)
11613
for i in range(5):
11614
self.set_rc(4, 2000)
11615
self.delay_sim_time(0.5)
11616
self.set_rc(4, 1500)
11617
self.delay_sim_time(5)
11618
self.context_pop()
11619
11620
self.do_RTL()
11621
11622
self.context_pop()
11623
11624
def Replay(self):
11625
'''test replay correctness'''
11626
self.progress("Building Replay")
11627
util.build_SITL('tool/Replay', clean=False, configure=False)
11628
self.set_parameters({
11629
"LOG_DARM_RATEMAX": 0,
11630
"LOG_FILE_RATEMAX": 0,
11631
})
11632
11633
bits = [
11634
('GPS', self.test_replay_gps_bit),
11635
('GPSForYaw', self.test_replay_gps_yaw_bit),
11636
('WindAndAirspeed', self.test_replay_wind_and_airspeed_bit),
11637
('Beacon', self.test_replay_beacon_bit),
11638
('OpticalFlow', self.test_replay_optical_flow_bit),
11639
]
11640
for (name, func) in bits:
11641
self.start_subtest("%s" % name)
11642
self.test_replay_bit(func)
11643
11644
def test_replay_bit(self, bit):
11645
11646
self.context_push()
11647
current_log_filepath = bit()
11648
11649
self.progress("Running replay on (%s) (%u bytes)" % (
11650
(current_log_filepath, os.path.getsize(current_log_filepath))
11651
))
11652
11653
self.zero_throttle()
11654
self.run_replay(current_log_filepath)
11655
11656
replay_log_filepath = self.current_onboard_log_filepath()
11657
11658
self.context_pop()
11659
11660
self.progress("Replay log path: %s" % str(replay_log_filepath))
11661
11662
check_replay = util.load_local_module("Tools/Replay/check_replay.py")
11663
11664
ok = check_replay.check_log(replay_log_filepath, self.progress, verbose=True)
11665
if not ok:
11666
raise NotAchievedException("check_replay (%s) failed" % current_log_filepath)
11667
11668
def DefaultIntervalsFromFiles(self):
11669
'''Test setting default mavlink message intervals from files'''
11670
ex = None
11671
intervals_filepath = util.reltopdir("message-intervals-chan0.txt")
11672
self.progress("Using filepath (%s)" % intervals_filepath)
11673
try:
11674
with open(intervals_filepath, "w") as f:
11675
f.write("""30 50
11676
28 100
11677
29 200
11678
""")
11679
f.close()
11680
11681
# other tests may have explicitly set rates, so wipe parameters:
11682
def custom_stream_rate_setter():
11683
for stream in mavutil.mavlink.MAV_DATA_STREAM_EXTRA3, mavutil.mavlink.MAV_DATA_STREAM_RC_CHANNELS:
11684
self.set_streamrate(5, stream=stream)
11685
11686
self.customise_SITL_commandline(
11687
[],
11688
wipe=True,
11689
set_streamrate_callback=custom_stream_rate_setter,
11690
)
11691
11692
self.assert_message_rate_hz("ATTITUDE", 20)
11693
self.assert_message_rate_hz("SCALED_PRESSURE", 5)
11694
11695
except Exception as e:
11696
self.print_exception_caught(e)
11697
ex = e
11698
11699
os.unlink(intervals_filepath)
11700
11701
self.reboot_sitl()
11702
11703
if ex is not None:
11704
raise ex
11705
11706
def BaroDrivers(self):
11707
'''Test Baro Drivers'''
11708
sensors = [
11709
("MS5611", 2),
11710
]
11711
for (name, bus) in sensors:
11712
self.context_push()
11713
if bus is not None:
11714
self.set_parameter("BARO_EXT_BUS", bus)
11715
self.set_parameter("BARO_PROBE_EXT", 1 << 2)
11716
self.reboot_sitl()
11717
self.wait_ready_to_arm()
11718
self.arm_vehicle()
11719
11720
# insert listener to compare airspeeds:
11721
messages = [None, None, None]
11722
11723
global count
11724
count = 0
11725
11726
def check_pressure(mav, m):
11727
global count
11728
m_type = m.get_type()
11729
count += 1
11730
# if count > 500:
11731
# if press_abs[0] is None or press_abs[1] is None:
11732
# raise NotAchievedException("Not receiving messages")
11733
if m_type == 'SCALED_PRESSURE3':
11734
off = 2
11735
elif m_type == 'SCALED_PRESSURE2':
11736
off = 1
11737
elif m_type == 'SCALED_PRESSURE':
11738
off = 0
11739
else:
11740
return
11741
11742
messages[off] = m
11743
11744
if None in messages:
11745
return
11746
first = messages[0]
11747
for msg in messages[1:]:
11748
delta_press_abs = abs(first.press_abs - msg.press_abs)
11749
if delta_press_abs > 0.5: # 50 Pa leeway
11750
raise NotAchievedException("Press_Abs mismatch (press1=%s press2=%s)" % (first, msg))
11751
delta_temperature = abs(first.temperature - msg.temperature)
11752
if delta_temperature > 300: # that's 3-degrees leeway
11753
raise NotAchievedException("Temperature mismatch (t1=%s t2=%s)" % (first, msg))
11754
self.install_message_hook_context(check_pressure)
11755
self.fly_mission("copter_mission.txt", strict=False)
11756
if None in messages:
11757
raise NotAchievedException("Missing a message")
11758
11759
self.context_pop()
11760
self.reboot_sitl()
11761
11762
def PositionWhenGPSIsZero(self):
11763
'''Ensure position doesn't zero when GPS lost'''
11764
# https://github.com/ArduPilot/ardupilot/issues/14236
11765
self.progress("arm the vehicle and takeoff in Guided")
11766
self.takeoff(20, mode='GUIDED')
11767
self.progress("fly 50m North (or whatever)")
11768
old_pos = self.assert_receive_message('GLOBAL_POSITION_INT')
11769
self.fly_guided_move_global_relative_alt(50, 0, 20)
11770
self.set_parameter('GPS1_TYPE', 0)
11771
self.drain_mav()
11772
tstart = self.get_sim_time()
11773
while True:
11774
if self.get_sim_time_cached() - tstart > 30 and self.mode_is('LAND'):
11775
self.progress("Bug not reproduced")
11776
break
11777
m = self.assert_receive_message('GLOBAL_POSITION_INT', verbose=True)
11778
pos_delta = self.get_distance_int(old_pos, m)
11779
self.progress("Distance: %f" % pos_delta)
11780
if pos_delta < 5:
11781
raise NotAchievedException("Bug reproduced - returned to near origin")
11782
self.wait_disarmed()
11783
self.reboot_sitl()
11784
11785
def RTL_ALT_FINAL_M(self):
11786
'''Test RTL with RTL_ALT_FINAL_M'''
11787
self.progress("arm the vehicle and takeoff in Guided")
11788
self.takeoff(20, mode='GUIDED')
11789
11790
# Set home current location, this gives a large home vs origin difference
11791
self.set_home(self.mav.location())
11792
11793
self.progress("fly 50m North (or whatever)")
11794
self.fly_guided_move_local(50, 0, 50)
11795
target_alt = 10
11796
self.set_parameter('RTL_ALT_FINAL_M', target_alt)
11797
11798
self.progress("Waiting RTL to reach Home and hold")
11799
self.change_mode('RTL')
11800
11801
# Expecting to return and hold 10m above home
11802
tstart = self.get_sim_time()
11803
reachedHome = False
11804
while self.get_sim_time_cached() < tstart + 250:
11805
m = self.assert_receive_message('GLOBAL_POSITION_INT')
11806
alt = m.relative_alt / 1000.0 # mm -> m
11807
home_distance = self.distance_to_home(use_cached_home=True)
11808
home = math.sqrt((alt-target_alt)**2 + home_distance**2) < 2
11809
if home and not reachedHome:
11810
reachedHome = True
11811
self.progress("Reached home - holding")
11812
self.delay_sim_time(20)
11813
continue
11814
11815
if reachedHome:
11816
if not home:
11817
raise NotAchievedException("Should still be at home")
11818
if not self.armed():
11819
raise NotAchievedException("Should still be armed")
11820
break
11821
11822
self.progress("Hold at home successful - landing")
11823
self.change_mode("LAND")
11824
self.wait_landed_and_disarmed()
11825
11826
def SMART_RTL(self):
11827
'''Check SMART_RTL'''
11828
self.progress("arm the vehicle and takeoff in Guided")
11829
self.takeoff(20, mode='GUIDED')
11830
self.progress("fly around a bit (or whatever)")
11831
locs = [
11832
(50, 0, 20),
11833
(-50, 50, 20),
11834
(-50, 0, 20),
11835
]
11836
for (lat, lng, alt) in locs:
11837
self.fly_guided_move_local(lat, lng, alt)
11838
11839
self.change_mode('SMART_RTL')
11840
for (lat, lng, alt) in reversed(locs):
11841
self.wait_distance_to_local_position(
11842
(lat, lng, -alt),
11843
0,
11844
10,
11845
timeout=60
11846
)
11847
self.wait_disarmed()
11848
11849
def get_ground_effect_duration_from_current_onboard_log(self, bit, ignore_multi=False):
11850
'''returns a duration in seconds we were expecting to interact with
11851
the ground. Will die if there's more than one such block of
11852
time and ignore_multi is not set (will return first duration
11853
otherwise)
11854
'''
11855
ret = []
11856
dfreader = self.dfreader_for_current_onboard_log()
11857
seen_expected_start_TimeUS = None
11858
first = None
11859
last = None
11860
while True:
11861
m = dfreader.recv_match(type="XKF4")
11862
if m is None:
11863
break
11864
last = m
11865
if first is None:
11866
first = m
11867
# self.progress("%s" % str(m))
11868
expected = m.SS & (1 << bit)
11869
if expected:
11870
if seen_expected_start_TimeUS is None:
11871
seen_expected_start_TimeUS = m.TimeUS
11872
continue
11873
else:
11874
if seen_expected_start_TimeUS is not None:
11875
duration = (m.TimeUS - seen_expected_start_TimeUS)/1000000.0
11876
ret.append(duration)
11877
seen_expected_start_TimeUS = None
11878
if seen_expected_start_TimeUS is not None:
11879
duration = (last.TimeUS - seen_expected_start_TimeUS)/1000000.0
11880
ret.append(duration)
11881
return ret
11882
11883
def get_takeoffexpected_durations_from_current_onboard_log(self, ignore_multi=False):
11884
return self.get_ground_effect_duration_from_current_onboard_log(11, ignore_multi=ignore_multi)
11885
11886
def get_touchdownexpected_durations_from_current_onboard_log(self, ignore_multi=False):
11887
return self.get_ground_effect_duration_from_current_onboard_log(12, ignore_multi=ignore_multi)
11888
11889
def ThrowDoubleDrop(self):
11890
'''Test a more complicated drop-mode scenario'''
11891
self.progress("Getting a lift to altitude")
11892
self.set_parameters({
11893
"SIM_SHOVE_Z": -11,
11894
"THROW_TYPE": 1, # drop
11895
"MOT_SPOOL_TIME": 2,
11896
})
11897
self.change_mode('THROW')
11898
self.wait_ready_to_arm()
11899
self.arm_vehicle()
11900
try:
11901
self.set_parameter("SIM_SHOVE_TIME", 30000)
11902
except ValueError:
11903
# the shove resets this to zero
11904
pass
11905
11906
self.wait_altitude(100, 1000, timeout=100, relative=True)
11907
self.context_collect('STATUSTEXT')
11908
self.wait_statustext("throw detected - spooling motors", check_context=True, timeout=10)
11909
self.wait_statustext("throttle is unlimited - uprighting", check_context=True)
11910
self.wait_statustext("uprighted - controlling height", check_context=True)
11911
self.wait_statustext("height achieved - controlling position", check_context=True)
11912
self.progress("Waiting for still")
11913
self.wait_speed_vector(Vector3(0, 0, 0))
11914
self.change_mode('ALT_HOLD')
11915
self.set_rc(3, 1000)
11916
self.wait_disarmed(timeout=90)
11917
self.zero_throttle()
11918
11919
self.progress("second flight")
11920
self.upload_square_mission_items_around_location(self.poll_home_position())
11921
11922
self.set_parameters({
11923
"THROW_NEXTMODE": 3, # auto
11924
})
11925
11926
self.change_mode('THROW')
11927
self.wait_ready_to_arm()
11928
self.arm_vehicle()
11929
try:
11930
self.set_parameter("SIM_SHOVE_TIME", 30000)
11931
except ValueError:
11932
# the shove resets this to zero
11933
pass
11934
11935
self.wait_altitude(100, 1000, timeout=100, relative=True)
11936
self.wait_statustext("throw detected - spooling motors", check_context=True, timeout=10)
11937
self.wait_statustext("throttle is unlimited - uprighting", check_context=True)
11938
self.wait_statustext("uprighted - controlling height", check_context=True)
11939
self.wait_statustext("height achieved - controlling position", check_context=True)
11940
self.wait_mode('AUTO')
11941
self.wait_disarmed(timeout=240)
11942
11943
def GroundEffectCompensation_takeOffExpected(self):
11944
'''Test EKF's handling of takeoff-expected'''
11945
self.change_mode('ALT_HOLD')
11946
self.set_parameter("LOG_FILE_DSRMROT", 1)
11947
self.progress("Making sure we'll have a short log to look at")
11948
self.wait_ready_to_arm()
11949
self.arm_vehicle()
11950
self.disarm_vehicle()
11951
11952
# arm the vehicle and let it disarm normally. This should
11953
# yield a log where the EKF considers a takeoff imminent until
11954
# disarm
11955
self.start_subtest("Check ground effect compensation remains set in EKF while we're at idle on the ground")
11956
self.arm_vehicle()
11957
self.wait_disarmed()
11958
11959
durations = self.get_takeoffexpected_durations_from_current_onboard_log()
11960
duration = durations[0]
11961
want = 9
11962
self.progress("takeoff-expected duration: %fs" % (duration,))
11963
if duration < want: # assumes default 10-second DISARM_DELAY
11964
raise NotAchievedException("Should have been expecting takeoff for longer than %fs (want>%f)" %
11965
(duration, want))
11966
11967
self.start_subtest("takeoffExpected should be false very soon after we launch into the air")
11968
self.takeoff(mode='ALT_HOLD', alt_min=5)
11969
self.change_mode('LAND')
11970
self.wait_disarmed()
11971
durations = self.get_takeoffexpected_durations_from_current_onboard_log(ignore_multi=True)
11972
self.progress("touchdown-durations: %s" % str(durations))
11973
duration = durations[0]
11974
self.progress("takeoff-expected-duration %f" % (duration,))
11975
want_lt = 5
11976
if duration >= want_lt:
11977
raise NotAchievedException("Was expecting takeoff for longer than expected; got=%f want<=%f" %
11978
(duration, want_lt))
11979
11980
def _MAV_CMD_CONDITION_YAW(self, command):
11981
self.start_subtest("absolute")
11982
self.takeoff(20, mode='GUIDED')
11983
11984
m = self.assert_receive_message('VFR_HUD')
11985
initial_heading = m.heading
11986
11987
self.progress("Ensuring initial heading is steady")
11988
target = initial_heading
11989
command(
11990
mavutil.mavlink.MAV_CMD_CONDITION_YAW,
11991
p1=target, # target angle
11992
p2=10, # degrees/second
11993
p3=1, # -1 is counter-clockwise, 1 clockwise
11994
p4=0, # 1 for relative, 0 for absolute
11995
)
11996
self.wait_heading(target, minimum_duration=2, timeout=50)
11997
self.wait_yaw_speed(0)
11998
11999
degsecond = 2
12000
12001
def rate_watcher(mav, m):
12002
if m.get_type() != 'ATTITUDE':
12003
return
12004
if abs(math.degrees(m.yawspeed)) > 5*degsecond:
12005
raise NotAchievedException("Moved too fast (%f>%f)" %
12006
(math.degrees(m.yawspeed), 5*degsecond))
12007
self.install_message_hook_context(rate_watcher)
12008
self.progress("Yaw CW 60 degrees")
12009
target = initial_heading + 60
12010
part_way_target = initial_heading + 10
12011
command(
12012
mavutil.mavlink.MAV_CMD_CONDITION_YAW,
12013
p1=target, # target angle
12014
p2=degsecond, # degrees/second
12015
p3=1, # -1 is counter-clockwise, 1 clockwise
12016
p4=0, # 1 for relative, 0 for absolute
12017
)
12018
self.wait_heading(part_way_target)
12019
self.wait_heading(target, minimum_duration=2)
12020
12021
self.progress("Yaw CCW 60 degrees")
12022
target = initial_heading
12023
part_way_target = initial_heading + 30
12024
command(
12025
mavutil.mavlink.MAV_CMD_CONDITION_YAW,
12026
p1=target, # target angle
12027
p2=degsecond, # degrees/second
12028
p3=-1, # -1 is counter-clockwise, 1 clockwise
12029
p4=0, # 1 for relative, 0 for absolute
12030
)
12031
self.wait_heading(part_way_target)
12032
self.wait_heading(target, minimum_duration=2)
12033
12034
self.disarm_vehicle(force=True)
12035
self.reboot_sitl()
12036
12037
def MAV_CMD_CONDITION_YAW(self):
12038
'''Test response to MAV_CMD_CONDITION_YAW via mavlink'''
12039
self.context_push()
12040
self._MAV_CMD_CONDITION_YAW(self.run_cmd_int)
12041
self.context_pop()
12042
self.context_push()
12043
self._MAV_CMD_CONDITION_YAW(self.run_cmd)
12044
self.context_pop()
12045
12046
def GroundEffectCompensation_touchDownExpected(self):
12047
'''Test EKF's handling of touchdown-expected'''
12048
self.zero_throttle()
12049
self.change_mode('ALT_HOLD')
12050
self.set_parameter("LOG_FILE_DSRMROT", 1)
12051
self.progress("Making sure we'll have a short log to look at")
12052
self.wait_ready_to_arm()
12053
self.arm_vehicle()
12054
self.disarm_vehicle()
12055
12056
self.start_subtest("Make sure touchdown-expected duration is about right")
12057
self.takeoff(20, mode='ALT_HOLD')
12058
self.change_mode('LAND')
12059
self.wait_disarmed()
12060
12061
durations = self.get_touchdownexpected_durations_from_current_onboard_log(ignore_multi=True)
12062
self.progress("touchdown-durations: %s" % str(durations))
12063
duration = durations[-1]
12064
expected = 23 # this is the time in the final descent phase of LAND
12065
if abs(duration - expected) > 5:
12066
raise NotAchievedException("Was expecting roughly %fs of touchdown expected, got %f" % (expected, duration))
12067
12068
def upload_square_mission_items_around_location(self, loc):
12069
alt = 20
12070
loc.alt = alt
12071
items = [
12072
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, alt)
12073
]
12074
12075
for (ofs_n, ofs_e) in (20, 20), (20, -20), (-20, -20), (-20, 20), (20, 20):
12076
items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, ofs_n, ofs_e, alt))
12077
12078
items.append((mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0))
12079
12080
self.upload_simple_relhome_mission(items)
12081
12082
def RefindGPS(self):
12083
'''Refind the GPS and attempt to RTL rather than continue to land'''
12084
# https://github.com/ArduPilot/ardupilot/issues/14236
12085
self.progress("arm the vehicle and takeoff in Guided")
12086
self.takeoff(50, mode='GUIDED')
12087
self.progress("fly 50m North (or whatever)")
12088
old_pos = self.assert_receive_message('GLOBAL_POSITION_INT')
12089
self.fly_guided_move_global_relative_alt(50, 0, 50)
12090
self.set_parameter('GPS1_TYPE', 0)
12091
self.drain_mav()
12092
tstart = self.get_sim_time()
12093
while True:
12094
if self.get_sim_time_cached() - tstart > 30 and self.mode_is('LAND'):
12095
self.progress("Bug not reproduced")
12096
break
12097
m = self.assert_receive_message('GLOBAL_POSITION_INT', verbose=True)
12098
pos_delta = self.get_distance_int(old_pos, m)
12099
self.progress("Distance: %f" % pos_delta)
12100
if pos_delta < 5:
12101
raise NotAchievedException("Bug reproduced - returned to near origin")
12102
self.set_parameter('GPS1_TYPE', 1)
12103
self.do_RTL()
12104
12105
def GPSForYaw(self):
12106
'''Moving baseline GPS yaw'''
12107
self.load_default_params_file("copter-gps-for-yaw.parm")
12108
self.reboot_sitl()
12109
12110
self.wait_gps_fix_type_gte(6, message_type="GPS2_RAW", verbose=True)
12111
m = self.assert_receive_message("GPS2_RAW", very_verbose=True)
12112
want = 27000
12113
if abs(m.yaw - want) > 500:
12114
raise NotAchievedException("Expected to get GPS-from-yaw (want %f got %f)" % (want, m.yaw))
12115
self.wait_ready_to_arm()
12116
12117
def SMART_RTL_EnterLeave(self):
12118
'''check SmartRTL behaviour when entering/leaving'''
12119
# we had a bug where we would consume points when re-entering smartrtl
12120
12121
self.upload_simple_relhome_mission([
12122
# N E U
12123
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 10),
12124
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
12125
])
12126
self.set_parameter('AUTO_OPTIONS', 3)
12127
self.change_mode('AUTO')
12128
self.wait_ready_to_arm()
12129
self.change_mode('ALT_HOLD')
12130
self.change_mode('SMART_RTL')
12131
self.change_mode('ALT_HOLD')
12132
self.change_mode('SMART_RTL')
12133
12134
def SMART_RTL_Repeat(self):
12135
'''Test whether Smart RTL catches the repeat'''
12136
self.takeoff(alt_min=10, mode='GUIDED')
12137
self.set_rc(3, 1500)
12138
self.change_mode("CIRCLE")
12139
self.delay_sim_time(1300)
12140
self.change_mode("SMART_RTL")
12141
self.wait_disarmed()
12142
12143
def GPSForYawCompassLearn(self):
12144
'''Moving baseline GPS yaw - with compass learning'''
12145
self.context_push()
12146
self.load_default_params_file("copter-gps-for-yaw.parm")
12147
self.set_parameter("EK3_SRC1_YAW", 3) # GPS with compass fallback
12148
self.reboot_sitl()
12149
12150
self.wait_gps_fix_type_gte(6, message_type="GPS2_RAW", verbose=True)
12151
12152
self.wait_ready_to_arm()
12153
12154
self.takeoff(10, mode='GUIDED')
12155
tstart = self.get_sim_time()
12156
compass_learn_set = False
12157
while True:
12158
delta_t = self.get_sim_time_cached() - tstart
12159
if delta_t > 30:
12160
break
12161
if not compass_learn_set and delta_t > 10:
12162
self.set_parameter("COMPASS_LEARN", 3)
12163
compass_learn_set = True
12164
12165
self.check_attitudes_match()
12166
self.delay_sim_time(1)
12167
12168
self.do_RTL()
12169
self.context_pop()
12170
self.reboot_sitl()
12171
12172
def AP_Avoidance(self):
12173
'''ADSB-based avoidance'''
12174
self.set_parameters({
12175
"AVD_ENABLE": 1,
12176
"ADSB_TYPE": 1, # mavlink
12177
"AVD_F_ACTION": 2, # climb or descend
12178
})
12179
self.reboot_sitl()
12180
12181
self.wait_ready_to_arm()
12182
12183
here = self.mav.location()
12184
12185
self.context_push()
12186
12187
self.start_subtest("F_ALT_MIN zero - disabled, can't arm in face of threat")
12188
self.set_parameters({
12189
"AVD_F_ALT_MIN": 0,
12190
})
12191
self.wait_ready_to_arm()
12192
self.test_adsb_send_threatening_adsb_message(here)
12193
self.delay_sim_time(1)
12194
self.try_arm(result=False,
12195
expect_msg="ADSB threat detected")
12196
12197
self.wait_ready_to_arm(timeout=60)
12198
12199
self.context_pop()
12200
12201
self.start_subtest("F_ALT_MIN 16m relative - arm in face of threat")
12202
self.context_push()
12203
self.set_parameters({
12204
"AVD_F_ALT_MIN": int(16 + here.alt),
12205
})
12206
self.wait_ready_to_arm()
12207
self.test_adsb_send_threatening_adsb_message(here)
12208
# self.delay_sim_time(1)
12209
self.arm_vehicle()
12210
self.disarm_vehicle()
12211
self.context_pop()
12212
12213
def PAUSE_CONTINUE(self):
12214
'''Test MAV_CMD_DO_PAUSE_CONTINUE in AUTO mode'''
12215
self.load_mission(filename="copter_mission.txt", strict=False)
12216
self.set_parameter(name="AUTO_OPTIONS", value=3)
12217
self.change_mode(mode="AUTO")
12218
self.wait_ready_to_arm()
12219
self.arm_vehicle()
12220
12221
self.wait_current_waypoint(wpnum=3, timeout=500)
12222
self.send_pause_command()
12223
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
12224
self.send_resume_command()
12225
12226
self.wait_current_waypoint(wpnum=4, timeout=500)
12227
self.send_pause_command()
12228
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
12229
self.send_resume_command()
12230
12231
# sending a pause, or resume, to the aircraft twice, doesn't result in reporting a failure
12232
self.wait_current_waypoint(wpnum=5, timeout=500)
12233
self.send_pause_command()
12234
self.send_pause_command()
12235
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
12236
self.send_resume_command()
12237
self.send_resume_command()
12238
12239
self.wait_disarmed(timeout=500)
12240
12241
def PAUSE_CONTINUE_GUIDED(self):
12242
'''Test MAV_CMD_DO_PAUSE_CONTINUE in GUIDED mode'''
12243
self.start_subtest("Started test for Pause/Continue in GUIDED mode with LOCATION!")
12244
self.change_mode(mode="GUIDED")
12245
self.wait_ready_to_arm()
12246
self.arm_vehicle()
12247
self.set_parameter(name="GUID_TIMEOUT", value=120)
12248
self.user_takeoff(alt_min=30)
12249
12250
# send vehicle to global position target
12251
location = self.home_relative_loc_ne(n=300, e=0)
12252
target_typemask = MAV_POS_TARGET_TYPE_MASK.POS_ONLY
12253
self.mav.mav.set_position_target_global_int_send(
12254
0, # timestamp
12255
1, # target system_id
12256
1, # target component id
12257
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT, # relative altitude frame
12258
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE, # target typemask as pos only
12259
int(location.lat * 1e7), # lat
12260
int(location.lng * 1e7), # lon
12261
30, # alt
12262
0, # vx
12263
0, # vy
12264
0, # vz
12265
0, # afx
12266
0, # afy
12267
0, # afz
12268
0, # yaw
12269
0) # yawrate
12270
12271
self.wait_distance_to_home(distance_min=100, distance_max=150, timeout=120)
12272
self.send_pause_command()
12273
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
12274
self.send_resume_command()
12275
self.wait_location(loc=location, timeout=120)
12276
12277
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with LOCATION!")
12278
self.start_subtest("Started test for Pause/Continue in GUIDED mode with DESTINATION!")
12279
self.guided_achieve_heading(heading=270)
12280
12281
# move vehicle on x direction
12282
location = self.offset_location_ne(location=self.mav.location(), metres_north=0, metres_east=-300)
12283
self.mav.mav.set_position_target_global_int_send(
12284
0, # system time in milliseconds
12285
1, # target system
12286
1, # target component
12287
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT, # coordinate frame MAV_FRAME_BODY_NED
12288
MAV_POS_TARGET_TYPE_MASK.POS_ONLY, # type mask (pos only)
12289
int(location.lat*1e7), # position x
12290
int(location.lng*1e7), # position y
12291
30, # position z
12292
0, # velocity x
12293
0, # velocity y
12294
0, # velocity z
12295
0, # accel x
12296
0, # accel y
12297
0, # accel z
12298
0, # yaw
12299
0) # yaw rate
12300
12301
self.wait_location(loc=location, accuracy=200, timeout=120)
12302
self.send_pause_command()
12303
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
12304
self.send_resume_command()
12305
self.wait_location(loc=location, timeout=120)
12306
12307
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with DESTINATION!")
12308
self.start_subtest("Started test for Pause/Continue in GUIDED mode with VELOCITY!")
12309
12310
# give velocity command
12311
vx, vy, vz_up = (5, 5, 0)
12312
self.test_guided_local_velocity_target(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
12313
12314
self.wait_for_local_velocity(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
12315
self.send_pause_command()
12316
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
12317
self.send_resume_command()
12318
self.wait_for_local_velocity(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
12319
self.test_guided_local_velocity_target(vx=0, vy=0, vz_up=0, timeout=10)
12320
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
12321
12322
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with VELOCITY!")
12323
self.start_subtest("Started test for Pause/Continue in GUIDED mode with ACCELERATION!")
12324
12325
# give acceleration command
12326
ax, ay, az_up = (1, 1, 0)
12327
target_typemask = (MAV_POS_TARGET_TYPE_MASK.POS_IGNORE | MAV_POS_TARGET_TYPE_MASK.VEL_IGNORE |
12328
MAV_POS_TARGET_TYPE_MASK.YAW_IGNORE | MAV_POS_TARGET_TYPE_MASK.YAW_RATE_IGNORE)
12329
self.mav.mav.set_position_target_local_ned_send(
12330
0, # timestamp
12331
1, # target system_id
12332
1, # target component id
12333
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
12334
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
12335
0, # x
12336
0, # y
12337
0, # z
12338
0, # vx
12339
0, # vy
12340
0, # vz
12341
ax, # afx
12342
ay, # afy
12343
-az_up, # afz
12344
0, # yaw
12345
0, # yawrate
12346
)
12347
12348
self.wait_for_local_velocity(vx=5, vy=5, vz_up=0, timeout=10)
12349
self.send_pause_command()
12350
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
12351
self.send_resume_command()
12352
self.wait_for_local_velocity(vx=5, vy=5, vz_up=0, timeout=10)
12353
self.test_guided_local_velocity_target(vx=0, vy=0, vz_up=0, timeout=10)
12354
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
12355
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with ACCELERATION!")
12356
12357
# start pause/continue subtest with posvelaccel
12358
self.start_subtest("Started test for Pause/Continue in GUIDED mode with POSITION and VELOCITY and ACCELERATION!")
12359
self.guided_achieve_heading(heading=0)
12360
12361
# give posvelaccel command
12362
x, y, z_up = (-300, 0, 30)
12363
target_typemask = (MAV_POS_TARGET_TYPE_MASK.YAW_IGNORE | MAV_POS_TARGET_TYPE_MASK.YAW_RATE_IGNORE)
12364
self.mav.mav.set_position_target_local_ned_send(
12365
0, # timestamp
12366
1, # target system_id
12367
1, # target component id
12368
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
12369
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
12370
x, # x
12371
y, # y
12372
-z_up, # z
12373
0, # vx
12374
0, # vy
12375
0, # vz
12376
0, # afx
12377
0, # afy
12378
0, # afz
12379
0, # yaw
12380
0, # yawrate
12381
)
12382
12383
self.wait_distance_to_local_position(local_position=(x, y, -z_up), distance_min=400, distance_max=450, timeout=120)
12384
self.send_pause_command()
12385
self.wait_for_local_velocity(0, 0, 0, timeout=10)
12386
self.send_resume_command()
12387
self.wait_distance_to_local_position(local_position=(x, y, -z_up), distance_min=0, distance_max=10, timeout=120)
12388
12389
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with POSITION and VELOCITY and ACCELERATION!")
12390
self.do_RTL(timeout=120)
12391
12392
def DO_CHANGE_SPEED(self):
12393
'''Change speed during mission using waypoint items'''
12394
self.load_mission("mission.txt", strict=False)
12395
12396
self.set_parameters({
12397
"AUTO_OPTIONS": 3,
12398
"ATC_ANGLE_MAX": 45,
12399
})
12400
12401
self.change_mode('AUTO')
12402
self.wait_ready_to_arm()
12403
self.arm_vehicle()
12404
12405
self.wait_current_waypoint(1)
12406
self.wait_groundspeed(
12407
3.5, 4.5,
12408
minimum_duration=5,
12409
timeout=60,
12410
)
12411
12412
self.wait_current_waypoint(3)
12413
self.wait_groundspeed(
12414
14.5, 15.5,
12415
minimum_duration=10,
12416
timeout=60,
12417
)
12418
12419
self.wait_current_waypoint(5)
12420
self.wait_groundspeed(
12421
9.5, 11.5,
12422
minimum_duration=10,
12423
timeout=60,
12424
)
12425
12426
self.set_parameter("ATC_ANGLE_MAX", 60)
12427
self.wait_current_waypoint(7)
12428
self.wait_groundspeed(
12429
15.5, 16.5,
12430
minimum_duration=10,
12431
timeout=60,
12432
)
12433
12434
self.wait_disarmed()
12435
12436
def AUTO_LAND_TO_BRAKE(self):
12437
'''ensure terrain altitude is taken into account when braking'''
12438
self.set_parameters({
12439
"PLND_ACC_P_NSE": 2.500000,
12440
"PLND_ALT_MAX": 8.000000,
12441
"PLND_ALT_MIN": 0.750000,
12442
"PLND_BUS": -1,
12443
"PLND_CAM_POS_X": 0.000000,
12444
"PLND_CAM_POS_Y": 0.000000,
12445
"PLND_CAM_POS_Z": 0.000000,
12446
"PLND_ENABLED": 1,
12447
"PLND_EST_TYPE": 1,
12448
"PLND_LAG": 0.020000,
12449
"PLND_LAND_OFS_X": 0.000000,
12450
"PLND_LAND_OFS_Y": 0.000000,
12451
"PLND_OPTIONS": 0,
12452
"PLND_RET_BEHAVE": 0,
12453
"PLND_RET_MAX": 4,
12454
"PLND_STRICT": 1,
12455
"PLND_TIMEOUT": 4.000000,
12456
"PLND_TYPE": 4,
12457
"PLND_XY_DIST_MAX": 2.500000,
12458
"PLND_YAW_ALIGN": 0.000000,
12459
12460
"SIM_PLD_ALT_LMT": 15.000000,
12461
"SIM_PLD_DIST_LMT": 10.000000,
12462
"SIM_PLD_ENABLE": 1,
12463
"SIM_PLD_HEIGHT": 0,
12464
"SIM_PLD_LAT": -20.558929,
12465
"SIM_PLD_LON": -47.415035,
12466
"SIM_PLD_RATE": 100,
12467
"SIM_PLD_TYPE": 1,
12468
"SIM_PLD_YAW": 87,
12469
12470
"SIM_SONAR_SCALE": 12,
12471
})
12472
12473
self.set_analog_rangefinder_parameters()
12474
12475
self.load_mission('mission.txt')
12476
self.customise_SITL_commandline([
12477
"--home", self.sitl_home_string_from_mission("mission.txt"),
12478
])
12479
12480
self.set_parameter('AUTO_OPTIONS', 3)
12481
self.change_mode('AUTO')
12482
self.wait_ready_to_arm()
12483
self.arm_vehicle()
12484
12485
self.wait_current_waypoint(7)
12486
self.wait_altitude(10, 15, relative=True, timeout=60)
12487
self.change_mode('BRAKE')
12488
# monitor altitude here
12489
self.wait_altitude(10, 15, relative=True, minimum_duration=20)
12490
self.change_mode('AUTO')
12491
self.wait_disarmed()
12492
12493
def MAVLandedStateTakeoff(self):
12494
'''check EXTENDED_SYS_STATE message'''
12495
ex = None
12496
try:
12497
self.set_message_rate_hz(id=mavutil.mavlink.MAVLINK_MSG_ID_EXTENDED_SYS_STATE, rate_hz=1)
12498
self.wait_extended_sys_state(vtol_state=mavutil.mavlink.MAV_VTOL_STATE_MC,
12499
landed_state=mavutil.mavlink.MAV_LANDED_STATE_ON_GROUND, timeout=10)
12500
self.load_mission(filename="copter_mission.txt")
12501
self.set_parameter(name="AUTO_OPTIONS", value=3)
12502
self.change_mode(mode="AUTO")
12503
self.wait_ready_to_arm()
12504
self.arm_vehicle()
12505
self.wait_extended_sys_state(vtol_state=mavutil.mavlink.MAV_VTOL_STATE_MC,
12506
landed_state=mavutil.mavlink.MAV_LANDED_STATE_TAKEOFF, timeout=30)
12507
self.wait_extended_sys_state(vtol_state=mavutil.mavlink.MAV_VTOL_STATE_MC,
12508
landed_state=mavutil.mavlink.MAV_LANDED_STATE_IN_AIR, timeout=60)
12509
self.land_and_disarm()
12510
except Exception as e:
12511
self.print_exception_caught(e)
12512
ex = e
12513
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_EXTENDED_SYS_STATE, -1)
12514
if ex is not None:
12515
raise ex
12516
12517
def ATTITUDE_FAST(self):
12518
'''ensure that when ATTITUDE_FAST is set we get many messages'''
12519
12520
# enable fast logging (bit 1)
12521
# disable PID logging (bit 12) to avoid slowdowns in attitude logging in SITL
12522
log_bitmask_old = int(self.get_parameter('LOG_BITMASK'))
12523
log_bitmask_new = (log_bitmask_old | (1 << 0)) & ~(1 << 12)
12524
12525
path = self.generate_rate_sample_log(log_bitmask=log_bitmask_new)
12526
12527
self.check_dflog_message_rates(path, {
12528
'ANG': 400,
12529
})
12530
12531
def BaseLoggingRates(self):
12532
'''ensure messages come out at specific rates'''
12533
path = self.generate_rate_sample_log()
12534
self.check_dflog_message_rates(path, {
12535
"ATT": 10,
12536
"IMU": 25,
12537
})
12538
12539
def FETtecESC_flight(self):
12540
'''fly with servo outputs from FETtec ESC'''
12541
self.start_subtest("FETtec ESC flight")
12542
num_wp = self.load_mission("copter_mission.txt", strict=False)
12543
self.fly_loaded_mission(num_wp)
12544
12545
def FETtecESC_esc_power_checks(self):
12546
'''Make sure state machine copes with ESCs rebooting'''
12547
self.start_subtest("FETtec ESC reboot")
12548
self.wait_ready_to_arm()
12549
self.context_collect('STATUSTEXT')
12550
self.progress("Turning off an ESC off ")
12551
mask = int(self.get_parameter("SIM_FTOWESC_POW"))
12552
12553
for mot_id_to_kill in 1, 2:
12554
self.progress("Turning ESC=%u off" % mot_id_to_kill)
12555
self.set_parameter("SIM_FTOWESC_POW", mask & ~(1 << mot_id_to_kill))
12556
self.delay_sim_time(1)
12557
self.assert_prearm_failure("are not running")
12558
self.progress("Turning it back on")
12559
self.set_parameter("SIM_FTOWESC_POW", mask)
12560
self.wait_ready_to_arm()
12561
12562
self.progress("Turning ESC=%u off (again)" % mot_id_to_kill)
12563
self.set_parameter("SIM_FTOWESC_POW", mask & ~(1 << mot_id_to_kill))
12564
self.delay_sim_time(1)
12565
self.assert_prearm_failure("are not running")
12566
self.progress("Turning it back on")
12567
self.set_parameter("SIM_FTOWESC_POW", mask)
12568
self.wait_ready_to_arm()
12569
12570
self.progress("Turning all ESCs off")
12571
self.set_parameter("SIM_FTOWESC_POW", 0)
12572
self.delay_sim_time(1)
12573
self.assert_prearm_failure("are not running")
12574
self.progress("Turning them back on")
12575
self.set_parameter("SIM_FTOWESC_POW", mask)
12576
self.wait_ready_to_arm()
12577
12578
def fettec_assert_bad_mask(self, mask):
12579
'''assert the mask is bad for fettec driver'''
12580
self.start_subsubtest("Checking mask (%s) is bad" % (mask,))
12581
self.context_push()
12582
self.set_parameter("SERVO_FTW_MASK", mask)
12583
self.reboot_sitl()
12584
self.delay_sim_time(12) # allow accels/gyros to be happy
12585
tstart = self.get_sim_time()
12586
while True:
12587
if self.get_sim_time_cached() - tstart > 20:
12588
raise NotAchievedException("Expected mask to be only problem within 20 seconds")
12589
try:
12590
self.assert_prearm_failure("Invalid motor mask")
12591
break
12592
except NotAchievedException:
12593
self.delay_sim_time(1)
12594
self.context_pop()
12595
self.reboot_sitl()
12596
12597
def fettec_assert_good_mask(self, mask):
12598
'''assert the mask is bad for fettec driver'''
12599
self.start_subsubtest("Checking mask (%s) is good" % (mask,))
12600
self.context_push()
12601
self.set_parameter("SERVO_FTW_MASK", mask)
12602
self.reboot_sitl()
12603
self.wait_ready_to_arm()
12604
self.context_pop()
12605
self.reboot_sitl()
12606
12607
def FETtecESC_safety_switch(self):
12608
mot = self.find_first_set_bit(int(self.get_parameter("SERVO_FTW_MASK"))) + 1
12609
self.wait_esc_telem_rpm(mot, 0, 0)
12610
self.wait_ready_to_arm()
12611
self.context_push()
12612
self.set_parameter("DISARM_DELAY", 0)
12613
self.arm_vehicle()
12614
# we have to wait for a while for the arming tone to go out
12615
# before the motors will spin:
12616
self.wait_esc_telem_rpm(
12617
esc=mot,
12618
rpm_min=17640,
12619
rpm_max=17640,
12620
minimum_duration=2,
12621
timeout=5,
12622
)
12623
self.set_safetyswitch_on()
12624
self.wait_esc_telem_rpm(mot, 0, 0)
12625
self.set_safetyswitch_off()
12626
self.wait_esc_telem_rpm(
12627
esc=mot,
12628
rpm_min=17640,
12629
rpm_max=17640,
12630
minimum_duration=2,
12631
timeout=5,
12632
)
12633
self.context_pop()
12634
self.wait_disarmed()
12635
12636
def FETtecESC_btw_mask_checks(self):
12637
'''ensure prearm checks work as expected'''
12638
for bad_mask in [0b1000000000000000, 0b10100000000000000]:
12639
self.fettec_assert_bad_mask(bad_mask)
12640
for good_mask in [0b00001, 0b00101, 0b110000000000]:
12641
self.fettec_assert_good_mask(good_mask)
12642
12643
def FETtecESC(self):
12644
'''Test FETtecESC'''
12645
self.set_parameters({
12646
"SERIAL5_PROTOCOL": 38,
12647
"SERVO_FTW_MASK": 0b11101000,
12648
"SIM_FTOWESC_ENA": 1,
12649
"SERVO1_FUNCTION": 0,
12650
"SERVO2_FUNCTION": 0,
12651
"SERVO3_FUNCTION": 0,
12652
"SERVO4_FUNCTION": 33,
12653
"SERVO5_FUNCTION": 0,
12654
"SERVO6_FUNCTION": 34,
12655
"SERVO7_FUNCTION": 35,
12656
"SERVO8_FUNCTION": 36,
12657
"SIM_ESC_TELEM": 0,
12658
})
12659
self.customise_SITL_commandline(["--serial5=sim:fetteconewireesc"])
12660
self.FETtecESC_safety_switch()
12661
self.FETtecESC_esc_power_checks()
12662
self.FETtecESC_btw_mask_checks()
12663
self.FETtecESC_flight()
12664
12665
def PerfInfo(self):
12666
'''Test Scheduler PerfInfo output'''
12667
self.set_parameter('SCHED_OPTIONS', 1) # enable gathering
12668
# sometimes we need to trigger collection....
12669
content = self.fetch_file_via_ftp("@SYS/tasks.txt")
12670
self.delay_sim_time(5)
12671
content = self.fetch_file_via_ftp("@SYS/tasks.txt")
12672
self.progress("Got content (%s)" % str(content))
12673
12674
lines = content.split("\n")
12675
12676
if not lines[0].startswith("TasksV2"):
12677
raise NotAchievedException("Expected TasksV2 as first line first not (%s)" % lines[0])
12678
# last line is empty, so -2 here
12679
if not lines[-2].startswith("AP_Vehicle::update_arming"):
12680
raise NotAchievedException("Expected EFI last not (%s)" % lines[-2])
12681
12682
def RTL_TO_RALLY(self, target_system=1, target_component=1):
12683
'''Check RTL to rally point'''
12684
self.wait_ready_to_arm()
12685
rally_loc = self.home_relative_loc_ne(50, -25)
12686
rally_alt = 37
12687
items = [
12688
self.mav.mav.mission_item_int_encode(
12689
target_system,
12690
target_component,
12691
0, # seq
12692
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
12693
mavutil.mavlink.MAV_CMD_NAV_RALLY_POINT,
12694
0, # current
12695
0, # autocontinue
12696
0, # p1
12697
0, # p2
12698
0, # p3
12699
0, # p4
12700
int(rally_loc.lat * 1e7), # latitude
12701
int(rally_loc.lng * 1e7), # longitude
12702
rally_alt, # altitude
12703
mavutil.mavlink.MAV_MISSION_TYPE_RALLY),
12704
]
12705
self.upload_using_mission_protocol(
12706
mavutil.mavlink.MAV_MISSION_TYPE_RALLY,
12707
items
12708
)
12709
self.set_parameters({
12710
'RALLY_INCL_HOME': 0,
12711
})
12712
self.takeoff(10)
12713
self.change_mode('RTL')
12714
self.wait_location(rally_loc)
12715
self.assert_altitude(rally_alt, relative=True)
12716
self.progress("Ensuring we're descending")
12717
self.wait_altitude(20, 25, relative=True)
12718
self.change_mode('LOITER')
12719
self.progress("Flying home")
12720
self.clear_mission(mavutil.mavlink.MAV_MISSION_TYPE_RALLY)
12721
self.change_mode('RTL')
12722
self.wait_disarmed()
12723
self.assert_at_home()
12724
12725
def NoRCOnBootPreArmFailure(self):
12726
'''Ensure we can't arm with no RC on boot if THR_FS_VALUE set'''
12727
self.context_push()
12728
for rc_failure_mode in 1, 2:
12729
self.set_parameters({
12730
"SIM_RC_FAIL": rc_failure_mode,
12731
})
12732
self.reboot_sitl()
12733
if rc_failure_mode == 1:
12734
self.assert_prearm_failure("RC not found",
12735
other_prearm_failures_fatal=False)
12736
elif rc_failure_mode == 2:
12737
self.assert_prearm_failure("Throttle below failsafe",
12738
other_prearm_failures_fatal=False)
12739
self.context_pop()
12740
self.reboot_sitl()
12741
12742
def IMUConsistency(self):
12743
'''test IMUs must be consistent with one another'''
12744
self.wait_ready_to_arm()
12745
12746
self.start_subsubtest("prearm checks for accel inconsistency")
12747
self.context_push()
12748
self.set_parameters({
12749
"SIM_ACC1_BIAS_X": 5,
12750
})
12751
self.assert_prearm_failure("Accels inconsistent")
12752
self.context_pop()
12753
tstart = self.get_sim_time()
12754
self.wait_ready_to_arm()
12755
if self.get_sim_time() - tstart < 8:
12756
raise NotAchievedException("Should take 10 seconds to become armableafter IMU upset")
12757
12758
self.start_subsubtest("prearm checks for gyro inconsistency")
12759
self.context_push()
12760
self.set_parameters({
12761
"SIM_GYR1_BIAS_X": math.radians(10),
12762
})
12763
self.assert_prearm_failure("Gyros inconsistent")
12764
self.context_pop()
12765
tstart = self.get_sim_time()
12766
self.wait_ready_to_arm()
12767
if self.get_sim_time() - tstart < 8:
12768
raise NotAchievedException("Should take 10 seconds to become armableafter IMU upset")
12769
12770
def Sprayer(self):
12771
"""Test sprayer functionality."""
12772
self.context_push()
12773
12774
rc_ch = 9
12775
pump_ch = 5
12776
spinner_ch = 6
12777
pump_ch_min = 1050
12778
pump_ch_trim = 1520
12779
pump_ch_max = 1950
12780
spinner_ch_min = 975
12781
spinner_ch_trim = 1510
12782
spinner_ch_max = 1975
12783
12784
self.set_parameters({
12785
"SPRAY_ENABLE": 1,
12786
12787
"SERVO%u_FUNCTION" % pump_ch: 22,
12788
"SERVO%u_MIN" % pump_ch: pump_ch_min,
12789
"SERVO%u_TRIM" % pump_ch: pump_ch_trim,
12790
"SERVO%u_MAX" % pump_ch: pump_ch_max,
12791
12792
"SERVO%u_FUNCTION" % spinner_ch: 23,
12793
"SERVO%u_MIN" % spinner_ch: spinner_ch_min,
12794
"SERVO%u_TRIM" % spinner_ch: spinner_ch_trim,
12795
"SERVO%u_MAX" % spinner_ch: spinner_ch_max,
12796
12797
"SIM_SPR_ENABLE": 1,
12798
"SIM_SPR_PUMP": pump_ch,
12799
"SIM_SPR_SPIN": spinner_ch,
12800
12801
"RC%u_OPTION" % rc_ch: 15,
12802
"LOG_DISARMED": 1,
12803
})
12804
12805
self.reboot_sitl()
12806
12807
self.wait_ready_to_arm()
12808
self.arm_vehicle()
12809
12810
self.progress("test boot-up state - it's zero-output!")
12811
self.wait_servo_channel_value(spinner_ch, 0)
12812
self.wait_servo_channel_value(pump_ch, 0)
12813
12814
self.progress("Enable sprayer")
12815
self.set_rc(rc_ch, 2000)
12816
12817
self.progress("Testing zero-speed state")
12818
self.wait_servo_channel_value(spinner_ch, spinner_ch_min)
12819
self.wait_servo_channel_value(pump_ch, pump_ch_min)
12820
12821
self.progress("Testing turning it off")
12822
self.set_rc(rc_ch, 1000)
12823
self.wait_servo_channel_value(spinner_ch, spinner_ch_min)
12824
self.wait_servo_channel_value(pump_ch, pump_ch_min)
12825
12826
self.progress("Testing turning it back on")
12827
self.set_rc(rc_ch, 2000)
12828
self.wait_servo_channel_value(spinner_ch, spinner_ch_min)
12829
self.wait_servo_channel_value(pump_ch, pump_ch_min)
12830
12831
self.takeoff(30, mode='LOITER')
12832
12833
self.progress("Testing speed-ramping")
12834
self.set_rc(1, 1700) # start driving forward
12835
12836
# this is somewhat empirical...
12837
self.wait_servo_channel_value(
12838
pump_ch,
12839
1458,
12840
timeout=60,
12841
comparator=lambda x, y : abs(x-y) < 5
12842
)
12843
12844
self.progress("Turning it off again")
12845
self.set_rc(rc_ch, 1000)
12846
self.wait_servo_channel_value(spinner_ch, spinner_ch_min)
12847
self.wait_servo_channel_value(pump_ch, pump_ch_min)
12848
12849
self.start_subtest("Checking mavlink commands")
12850
self.progress("Starting Sprayer")
12851
self.run_cmd_int(mavutil.mavlink.MAV_CMD_DO_SPRAYER, p1=1)
12852
12853
self.progress("Testing speed-ramping")
12854
self.wait_servo_channel_value(
12855
pump_ch,
12856
1458,
12857
timeout=60,
12858
comparator=lambda x, y : abs(x-y) < 5
12859
)
12860
12861
self.start_subtest("Stopping Sprayer")
12862
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SPRAYER, p1=0)
12863
12864
self.wait_servo_channel_value(pump_ch, pump_ch_min)
12865
12866
self.disarm_vehicle(force=True)
12867
12868
self.context_pop()
12869
12870
self.reboot_sitl()
12871
12872
self.progress("Sprayer OK")
12873
12874
def tests1a(self):
12875
'''return list of all tests'''
12876
ret = super(AutoTestCopter, self).tests() # about 5 mins and ~20 initial tests from autotest/vehicle_test_suite.py
12877
ret.extend([
12878
self.NavDelayTakeoffAbsTime,
12879
self.NavDelayAbsTime,
12880
self.NavDelay,
12881
self.GuidedSubModeChange,
12882
self.MAV_CMD_CONDITION_YAW,
12883
self.LoiterToAlt,
12884
self.PayloadPlaceMission,
12885
self.PayloadPlaceMissionOpenGripper,
12886
self.PrecisionLoiterCompanion,
12887
self.Landing,
12888
self.PrecisionLanding,
12889
self.SetModesViaModeSwitch,
12890
self.BackupFence,
12891
self.SetModesViaAuxSwitch,
12892
self.AuxSwitchOptions,
12893
self.AuxFunctionsInMission,
12894
self.AutoTune,
12895
self.AutoTuneYawD,
12896
self.NoRCOnBootPreArmFailure,
12897
])
12898
return ret
12899
12900
def tests1b(self):
12901
'''return list of all tests'''
12902
ret = ([
12903
self.ThrowMode,
12904
self.BrakeMode,
12905
self.RecordThenPlayMission,
12906
self.ThrottleFailsafe,
12907
self.ThrottleFailsafePassthrough,
12908
self.GCSFailsafe,
12909
self.CustomController,
12910
self.WPArcs,
12911
self.WPArcs2,
12912
])
12913
return ret
12914
12915
def tests1c(self):
12916
'''return list of all tests'''
12917
ret = ([
12918
self.BatteryFailsafe,
12919
self.BatteryMissing,
12920
self.VibrationFailsafe,
12921
self.EK3AccelBias,
12922
self.StabilityPatch,
12923
self.OBSTACLE_DISTANCE_3D,
12924
self.AC_Avoidance_Proximity,
12925
self.AC_Avoidance_Proximity_AVOID_ALT_MIN,
12926
self.AC_Avoidance_Fence,
12927
self.AC_Avoidance_Beacon,
12928
self.AvoidanceAltFence,
12929
self.BaroWindCorrection,
12930
self.SetpointGlobalPos,
12931
self.ThrowDoubleDrop,
12932
self.SetpointGlobalVel,
12933
self.SetpointBadVel,
12934
self.SplineTerrain,
12935
self.TakeoffCheck,
12936
self.GainBackoffTakeoff,
12937
])
12938
return ret
12939
12940
def tests1d(self):
12941
'''return list of all tests'''
12942
ret = ([
12943
self.HorizontalFence,
12944
self.HorizontalAvoidFence,
12945
self.MaxAltFence,
12946
self.MaxAltFenceAvoid,
12947
self.MinAltFence,
12948
self.MinAltFenceAvoid,
12949
self.FenceFloorEnabledLanding,
12950
self.FenceFloorAutoDisableLanding,
12951
self.FenceFloorAutoEnableOnArming,
12952
self.FenceMargin,
12953
self.FenceUpload_MissionItem,
12954
self.AutoTuneSwitch,
12955
self.AutoTuneAux,
12956
self.GPSGlitchLoiter,
12957
self.GPSGlitchLoiter2,
12958
self.GPSGlitchAuto,
12959
self.ModeAltHold,
12960
self.ModeLoiter,
12961
self.SimpleMode,
12962
self.SuperSimpleCircle,
12963
self.ModeCircle,
12964
self.MagFail,
12965
self.OpticalFlow,
12966
self.OpticalFlowLocation,
12967
self.OpticalFlowLimits,
12968
self.OpticalFlowCalibration,
12969
self.MotorFail,
12970
self.ModeFlip,
12971
self.CopterMission,
12972
self.TakeoffAlt,
12973
self.SplineLastWaypoint,
12974
self.Gripper,
12975
self.TestLocalHomePosition,
12976
self.TestGripperMission,
12977
self.VisionPosition,
12978
self.ATTITUDE_FAST,
12979
self.BaseLoggingRates,
12980
self.BodyFrameOdom,
12981
self.GPSViconSwitching,
12982
])
12983
return ret
12984
12985
def tests1e(self):
12986
'''return list of all tests'''
12987
ret = ([
12988
self.BeaconPosition,
12989
self.RTLSpeed,
12990
self.Mount,
12991
self.MountYawVehicleForMountROI,
12992
self.MAV_CMD_DO_MOUNT_CONTROL,
12993
self.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
12994
self.AutoYawDO_MOUNT_CONTROL,
12995
self.MountPOIFromAuxFunction,
12996
self.Button,
12997
self.ShipTakeoff,
12998
self.RangeFinder,
12999
self.BaroDrivers,
13000
self.SurfaceTracking,
13001
self.Parachute,
13002
self.ParameterChecks,
13003
self.ManualThrottleModeChange,
13004
self.MANUAL_CONTROL,
13005
self.ModeZigZag,
13006
self.PosHoldTakeOff,
13007
self.ModeFollow,
13008
self.ModeFollow_with_FOLLOW_TARGET,
13009
self.RangeFinderDrivers,
13010
self.FlyRangeFinderMAVlink,
13011
self.FlyRangeFinderSITL,
13012
self.RangeFinderDriversMaxAlt_LightwareSerial,
13013
self.RangeFinderDriversMaxAlt_AinsteinLRD1,
13014
self.RangeFinderDriversMaxAlt_AinsteinLRD1_v19,
13015
self.RangeFinderDriversLongRange,
13016
self.RangeFinderSITLLongRange,
13017
self.MaxBotixI2CXL,
13018
self.MAVProximity,
13019
self.ParameterValidation,
13020
self.AltTypes,
13021
self.PAUSE_CONTINUE,
13022
self.PAUSE_CONTINUE_GUIDED,
13023
self.RichenPower,
13024
self.IE24,
13025
self.LoweheiserAuto,
13026
self.LoweheiserManual,
13027
self.MAVLandedStateTakeoff,
13028
self.Weathervane,
13029
self.MAV_CMD_AIRFRAME_CONFIGURATION,
13030
self.MAV_CMD_NAV_LOITER_UNLIM,
13031
self.MAV_CMD_NAV_RETURN_TO_LAUNCH,
13032
self.MAV_CMD_NAV_VTOL_LAND,
13033
self.clear_roi,
13034
self.ReadOnlyDefaults,
13035
])
13036
return ret
13037
13038
def tests2a(self):
13039
'''return list of all tests'''
13040
ret = ([
13041
# something about SITLCompassCalibration appears to fail
13042
# this one, so we put it first:
13043
self.FixedYawCalibration,
13044
13045
# we run this single 8min-and-40s test on its own, apart
13046
# from requiring FixedYawCalibration right before it
13047
# because without it, it fails to calibrate this
13048
# autotest appears to interfere with
13049
# FixedYawCalibration, no idea why.
13050
self.SITLCompassCalibration,
13051
])
13052
return ret
13053
13054
def ScriptMountPOI(self):
13055
'''test the MountPOI example script'''
13056
self.context_push()
13057
13058
self.install_terrain_handlers_context()
13059
self.set_parameters({
13060
"SCR_ENABLE": 1,
13061
"RC12_OPTION": 300,
13062
})
13063
self.setup_servo_mount()
13064
self.reboot_sitl()
13065
self.set_rc(6, 1300)
13066
self.install_applet_script_context('mount-poi.lua')
13067
self.reboot_sitl()
13068
self.wait_ready_to_arm()
13069
self.context_collect('STATUSTEXT')
13070
self.set_rc(12, 2000)
13071
self.wait_statustext('POI.*-35.*149', check_context=True, regex=True)
13072
self.set_rc(12, 1000)
13073
13074
self.context_pop()
13075
self.reboot_sitl()
13076
13077
def ScriptMountAllModes(self):
13078
'''test location from all mount modes using the scripting backend'''
13079
13080
# enable scripting and set mount type to scripting
13081
self.set_parameters({
13082
"SCR_ENABLE": 1,
13083
"MNT1_TYPE": 9,
13084
})
13085
self.reboot_sitl()
13086
13087
# install get-target-location-mount-backend.lua script
13088
self.install_example_script_context('get-target-location-mount-backend.lua')
13089
self.reboot_sitl()
13090
self.takeoff(50)
13091
13092
self.context_collect("STATUSTEXT")
13093
13094
self.start_subtest("Test RETRACT (mode 0)")
13095
self.run_cmd(
13096
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
13097
p7=mavutil.mavlink.MAV_MOUNT_MODE_RETRACT,
13098
)
13099
self.wait_statustext("Mode 0: No target", check_context=True)
13100
13101
self.start_subtest("Test NEUTRAL (mode 1)")
13102
self.run_cmd(
13103
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
13104
p7=mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL,
13105
)
13106
self.wait_statustext("Mode 1: No target", check_context=True)
13107
13108
self.start_subtest("Test MAVLINK_TARGETING (mode 2)")
13109
self.run_cmd(
13110
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
13111
p7=mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING,
13112
)
13113
self.wait_statustext("Mode 2: No target", check_context=True)
13114
13115
self.start_subtest("Test RC_TARGETING (mode 3)")
13116
self.run_cmd(
13117
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
13118
p7=mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING,
13119
)
13120
self.wait_statustext("Mode 3: No target", check_context=True)
13121
13122
self.start_subtest("Test GPS_POINT (mode 4)")
13123
roi_lat = -35.363150
13124
roi_lng = 149.165320
13125
roi_alt = 580.0
13126
13127
self.run_cmd_int(
13128
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
13129
p5=int(roi_lat * 1e7),
13130
p6=int(roi_lng * 1e7),
13131
p7=roi_alt,
13132
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
13133
)
13134
13135
expected_roi_lat = f"{roi_lat:.3f}"
13136
expected_roi_lng = f"{roi_lng:.3f}"
13137
13138
self.wait_statustext(
13139
rf"Mode 4:.*{expected_roi_lat}.*{expected_roi_lng}",
13140
check_context=True,
13141
regex=True,
13142
)
13143
13144
self.start_subtest("Test SYSID_TARGET (mode 5)")
13145
self.run_cmd(
13146
mavutil.mavlink.MAV_CMD_DO_SET_ROI_SYSID,
13147
p1=self.mav.source_system,
13148
)
13149
# Single-vehicle SITL, SYSID_TARGET has no target to track
13150
self.wait_statustext("Mode 5: No target", check_context=True)
13151
13152
self.start_subtest("Test HOME_LOCATION (mode 6)")
13153
loc_home = self.poll_home_position()
13154
13155
expected_lat = f"{loc_home.latitude/1e7:.6f}"
13156
expected_lng = f"{loc_home.longitude/1e7:.6f}"
13157
13158
self.run_cmd(
13159
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
13160
p7=mavutil.mavlink.MAV_MOUNT_MODE_HOME_LOCATION,
13161
)
13162
13163
self.wait_statustext(
13164
rf"Mode 6:.*{expected_lat}.*{expected_lng}",
13165
check_context=True,
13166
regex=True,
13167
)
13168
13169
self.do_RTL()
13170
13171
def ScriptCopterPosOffsets(self):
13172
'''test the copter-posoffset.lua example script'''
13173
self.context_push()
13174
13175
# enable scripting and arming/takingoff in Auto mode
13176
self.set_parameters({
13177
"SCR_ENABLE": 1,
13178
"AUTO_OPTIONS": 3,
13179
"RC12_OPTION": 300
13180
})
13181
self.reboot_sitl()
13182
13183
# install copter-posoffset script
13184
self.install_example_script_context('copter-posoffset.lua')
13185
self.reboot_sitl()
13186
13187
# create simple mission with a single takeoff command
13188
self.upload_simple_relhome_mission([
13189
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20)
13190
])
13191
13192
# switch to loiter to wait for position estimate (aka GPS lock)
13193
self.change_mode('LOITER')
13194
self.wait_ready_to_arm()
13195
13196
# arm and takeoff in Auto mode
13197
self.change_mode('AUTO')
13198
self.arm_vehicle()
13199
13200
# wait for vehicle to climb to at least 10m
13201
self.wait_altitude(8, 12, relative=True)
13202
13203
# add position offset to East and confirm vehicle moves
13204
self.set_parameter("PSC_OFS_POS_E", 20)
13205
self.set_rc(12, 2000)
13206
self.wait_distance(18)
13207
13208
# remove position offset and wait for vehicle to return home
13209
self.set_parameter("PSC_OFS_POS_E", 0)
13210
self.wait_distance_to_home(distance_min=0, distance_max=4, timeout=20)
13211
13212
# add velocity offset and confirm vehicle moves
13213
self.set_parameter("PSC_OFS_VEL_N", 5)
13214
self.wait_groundspeed(4.8, 5.2, minimum_duration=5, timeout=20)
13215
13216
# remove velocity offset and switch to RTL
13217
self.set_parameter("PSC_OFS_VEL_N", 0)
13218
self.set_rc(12, 1000)
13219
self.do_RTL()
13220
self.context_pop()
13221
self.reboot_sitl()
13222
13223
def AHRSTrimLand(self):
13224
'''test land detector with significant AHRS trim'''
13225
self.context_push()
13226
self.set_parameters({
13227
"SIM_ACC_TRIM_X": 0.12,
13228
"AHRS_TRIM_X": 0.12,
13229
})
13230
self.reboot_sitl()
13231
self.wait_ready_to_arm()
13232
self.takeoff(alt_min=20, mode='LOITER')
13233
self.do_RTL()
13234
self.context_pop()
13235
self.reboot_sitl()
13236
13237
def GainBackoffTakeoff(self):
13238
'''test gain backoff on takeoff'''
13239
self.context_push()
13240
self.progress("Test gains are fully backed off")
13241
self.set_parameters({
13242
"ATC_LAND_R_MULT": 0.0,
13243
"ATC_LAND_P_MULT": 0.0,
13244
"ATC_LAND_Y_MULT": 0.0,
13245
"GCS_PID_MASK" : 7,
13246
"LOG_BITMASK": 180222,
13247
})
13248
self.reboot_sitl()
13249
self.wait_ready_to_arm()
13250
self.change_mode('ALT_HOLD')
13251
13252
class ValidatePDZero(vehicle_test_suite.TestSuite.MessageHook):
13253
'''asserts correct values in PID_TUNING'''
13254
13255
def __init__(self, suite, axis):
13256
super(ValidatePDZero, self).__init__(suite)
13257
self.pid_tuning_count = 0
13258
self.p_sum = 0
13259
self.d_sum = 0
13260
self.i_sum = 0
13261
self.axis = axis
13262
13263
def hook_removed(self):
13264
if self.pid_tuning_count == 0:
13265
raise NotAchievedException("Did not get PID_TUNING")
13266
if self.i_sum == 0:
13267
raise ValueError("I sum is zero")
13268
print(f"ValidatePDZero: PID_TUNING count: {self.pid_tuning_count}")
13269
13270
def process(self, mav, m):
13271
if m.get_type() != 'PID_TUNING' or m.axis != self.axis:
13272
return
13273
self.pid_tuning_count += 1
13274
self.p_sum += m.P
13275
self.d_sum += m.D
13276
self.i_sum += m.I
13277
if self.p_sum > 0:
13278
raise ValueError("P sum is not zero")
13279
if self.d_sum > 0:
13280
raise ValueError("D sum is not zero")
13281
13282
self.progress("Check that PD values are zero")
13283
self.install_message_hook_context(ValidatePDZero(self, mavutil.mavlink.PID_TUNING_ROLL))
13284
self.install_message_hook_context(ValidatePDZero(self, mavutil.mavlink.PID_TUNING_PITCH))
13285
self.install_message_hook_context(ValidatePDZero(self, mavutil.mavlink.PID_TUNING_YAW))
13286
# until the context pop happens, all received PID_TUNINGS will be verified as good
13287
self.arm_vehicle()
13288
self.set_rc(3, 1500)
13289
self.delay_sim_time(2)
13290
self.set_rc(2, 1250)
13291
self.delay_sim_time(5)
13292
self.assert_receive_message('PID_TUNING', timeout=5)
13293
self.set_rc_default()
13294
self.zero_throttle()
13295
self.disarm_vehicle()
13296
self.context_pop()
13297
13298
self.context_push()
13299
self.progress("Test gains are not backed off")
13300
self.set_parameters({
13301
"ATC_LAND_R_MULT": 1.0,
13302
"ATC_LAND_P_MULT": 1.0,
13303
"ATC_LAND_Y_MULT": 1.0,
13304
"GCS_PID_MASK" : 7,
13305
"LOG_BITMASK": 180222,
13306
})
13307
self.reboot_sitl()
13308
self.wait_ready_to_arm()
13309
self.change_mode('ALT_HOLD')
13310
13311
class ValidatePDNonZero(vehicle_test_suite.TestSuite.MessageHook):
13312
'''asserts correct values in PID_TUNING'''
13313
13314
def __init__(self, suite, axis):
13315
super(ValidatePDNonZero, self).__init__(suite)
13316
self.pid_tuning_count = 0
13317
self.p_sum = 0
13318
self.d_sum = 0
13319
self.i_sum = 0
13320
self.axis = axis
13321
13322
def hook_removed(self):
13323
if self.pid_tuning_count == 0:
13324
raise NotAchievedException("Did not get PID_TUNING")
13325
if self.p_sum == 0:
13326
raise ValueError("P sum is zero")
13327
if self.i_sum == 0:
13328
raise ValueError("I sum is zero")
13329
print(f"ValidatePDNonZero: PID_TUNING count: {self.pid_tuning_count}")
13330
13331
def process(self, mav, m):
13332
if m.get_type() != 'PID_TUNING' or m.axis != self.axis:
13333
return
13334
self.pid_tuning_count += 1
13335
self.p_sum += m.P
13336
self.d_sum += m.D
13337
self.i_sum += m.I
13338
13339
self.progress("Check that PD values are non-zero")
13340
self.install_message_hook_context(ValidatePDNonZero(self, mavutil.mavlink.PID_TUNING_ROLL))
13341
self.install_message_hook_context(ValidatePDNonZero(self, mavutil.mavlink.PID_TUNING_PITCH))
13342
self.install_message_hook_context(ValidatePDNonZero(self, mavutil.mavlink.PID_TUNING_YAW))
13343
# until the context pop happens, all received PID_TUNINGS will be verified as good
13344
self.arm_vehicle()
13345
self.set_rc(3, 1500)
13346
self.delay_sim_time(2)
13347
self.set_rc(2, 1250)
13348
self.delay_sim_time(5)
13349
self.assert_receive_message('PID_TUNING', timeout=5)
13350
self.set_rc_default()
13351
self.zero_throttle()
13352
self.disarm_vehicle()
13353
13354
self.context_pop()
13355
self.reboot_sitl()
13356
13357
def SafetySwitch(self):
13358
'''test safety switch behaviour'''
13359
self.wait_ready_to_arm()
13360
13361
self.set_safetyswitch_on()
13362
self.assert_prearm_failure("safety switch")
13363
13364
self.set_safetyswitch_off()
13365
self.wait_ready_to_arm()
13366
13367
self.takeoff(2, mode='LOITER')
13368
self.set_safetyswitch_on()
13369
13370
self.wait_servo_channel_value(1, 0)
13371
self.set_safetyswitch_off()
13372
13373
self.change_mode('LAND')
13374
self.wait_disarmed()
13375
13376
# test turning safety on/off using explicit MAVLink command:
13377
self.run_cmd_int(mavutil.mavlink.MAV_CMD_DO_SET_SAFETY_SWITCH_STATE, mavutil.mavlink.SAFETY_SWITCH_STATE_SAFE)
13378
self.assert_prearm_failure("safety switch")
13379
self.run_cmd_int(mavutil.mavlink.MAV_CMD_DO_SET_SAFETY_SWITCH_STATE, mavutil.mavlink.SAFETY_SWITCH_STATE_DANGEROUS)
13380
self.wait_ready_to_arm()
13381
13382
def ArmSwitchAfterReboot(self):
13383
'''test that the arming switch does not trigger after a reboot'''
13384
self.wait_ready_to_arm()
13385
self.set_parameters({
13386
"RC8_OPTION": 153,
13387
})
13388
self.set_rc(8, 2000)
13389
self.wait_armed()
13390
self.disarm_vehicle()
13391
self.context_collect('STATUSTEXT')
13392
self.reboot_sitl()
13393
13394
tstart = self.get_sim_time()
13395
while True:
13396
if self.get_sim_time_cached() - tstart > 60:
13397
break
13398
if self.armed():
13399
raise NotAchievedException("Armed after reboot with switch high")
13400
armmsg = self.statustext_in_collections('Arm: ')
13401
if armmsg is not None:
13402
raise NotAchievedException("statustext(%s) means we tried to arm" % armmsg.text)
13403
self.progress("Did not arm via arming switfch after a reboot")
13404
13405
def GuidedYawRate(self):
13406
'''ensuer guided yaw rate is not affected by rate of sewt-attitude messages'''
13407
self.takeoff(30, mode='GUIDED')
13408
rates = {}
13409
for rate in 1, 10:
13410
# command huge yaw rate for a while
13411
tstart = self.get_sim_time()
13412
interval = 1/rate
13413
yawspeed_rads_sum = 0
13414
yawspeed_rads_count = 0
13415
last_sent = 0
13416
while True:
13417
self.drain_mav()
13418
tnow = self.get_sim_time_cached()
13419
if tnow - last_sent > interval:
13420
self.do_yaw_rate(60) # this is... unlikely
13421
last_sent = tnow
13422
if tnow - tstart < 5: # let it spin up to speed first
13423
continue
13424
yawspeed_rads_sum += self.mav.messages['ATTITUDE'].yawspeed
13425
yawspeed_rads_count += 1
13426
if tnow - tstart > 15: # 10 seconds of measurements
13427
break
13428
yawspeed_degs = math.degrees(yawspeed_rads_sum / yawspeed_rads_count)
13429
rates[rate] = yawspeed_degs
13430
self.progress("Input rate %u hz: average yaw rate %f deg/s" % (rate, yawspeed_degs))
13431
13432
if rates[10] < rates[1] * 0.95:
13433
raise NotAchievedException("Guided yaw rate slower for higher rate updates")
13434
13435
self.do_RTL()
13436
13437
def test_rplidar(self, sim_device_name, expected_distances):
13438
'''plonks a Copter with a RPLidarA2 in the middle of a simulated field
13439
of posts and checks that the measurements are what we expect.'''
13440
self.set_parameters({
13441
"SERIAL5_PROTOCOL": 11,
13442
"PRX1_TYPE": 5,
13443
})
13444
self.customise_SITL_commandline([
13445
"--serial5=sim:%s:" % sim_device_name,
13446
"--home", "51.8752066,14.6487840,0,0", # SITL has "posts" here
13447
])
13448
13449
self.wait_ready_to_arm()
13450
13451
wanting_distances = copy.copy(expected_distances)
13452
tstart = self.get_sim_time()
13453
timeout = 60
13454
while True:
13455
now = self.get_sim_time_cached()
13456
if now - tstart > timeout:
13457
raise NotAchievedException("Did not get all distances")
13458
m = self.mav.recv_match(type="DISTANCE_SENSOR",
13459
blocking=True,
13460
timeout=1)
13461
if m is None:
13462
continue
13463
self.progress("Got (%s)" % str(m))
13464
if m.orientation not in wanting_distances:
13465
continue
13466
if abs(m.current_distance - wanting_distances[m.orientation]) > 5:
13467
self.progress("Wrong distance orient=%u want=%u got=%u" %
13468
(m.orientation,
13469
wanting_distances[m.orientation],
13470
m.current_distance))
13471
continue
13472
self.progress("Correct distance for orient %u (want=%u got=%u)" %
13473
(m.orientation,
13474
wanting_distances[m.orientation],
13475
m.current_distance))
13476
del wanting_distances[m.orientation]
13477
if len(wanting_distances.items()) == 0:
13478
break
13479
13480
def RPLidarA2(self):
13481
'''test Raspberry Pi Lidar A2'''
13482
expected_distances = {
13483
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 276,
13484
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 256,
13485
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 1130,
13486
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 1286,
13487
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 626,
13488
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 971,
13489
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 762,
13490
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 792,
13491
}
13492
13493
self.test_rplidar("rplidara2", expected_distances)
13494
13495
def RPLidarA1(self):
13496
'''test Raspberry Pi Lidar A1'''
13497
return # we don't send distances when too long?
13498
expected_distances = {
13499
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 276,
13500
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 256,
13501
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 800,
13502
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 800,
13503
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 626,
13504
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 800,
13505
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 762,
13506
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 792,
13507
}
13508
13509
self.test_rplidar("rplidara1", expected_distances)
13510
13511
def BrakeZ(self):
13512
'''check jerk limit correct in Brake mode'''
13513
self.set_parameter('PSC_JERK_D', 3)
13514
self.takeoff(50, mode='GUIDED')
13515
vx, vy, vz_up = (0, 0, -1)
13516
self.test_guided_local_velocity_target(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
13517
13518
self.wait_for_local_velocity(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
13519
self.change_mode('BRAKE')
13520
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
13521
self.land_and_disarm()
13522
13523
def MISSION_START(self):
13524
'''test mavlink command MAV_CMD_MISSION_START'''
13525
self.upload_simple_relhome_mission([
13526
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 200),
13527
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
13528
])
13529
for command in self.run_cmd, self.run_cmd_int:
13530
self.change_mode('LOITER')
13531
self.set_current_waypoint(1)
13532
self.wait_ready_to_arm()
13533
self.arm_vehicle()
13534
self.change_mode('AUTO')
13535
command(mavutil.mavlink.MAV_CMD_MISSION_START)
13536
self.wait_altitude(20, 1000, relative=True)
13537
self.change_mode('RTL')
13538
self.wait_disarmed()
13539
13540
def DO_CHANGE_SPEED_in_guided(self):
13541
'''test Copter DO_CHANGE_SPEED handling in guided mode'''
13542
self.takeoff(20, mode='GUIDED')
13543
13544
new_loc = self.mav.location()
13545
new_loc_offset_n = 2000
13546
new_loc_offset_e = 0
13547
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
13548
13549
second_loc_offset_n = -1000
13550
second_loc_offset_e = 0
13551
second_loc = self.mav.location()
13552
self.location_offset_ne(second_loc, second_loc_offset_n, second_loc_offset_e)
13553
13554
# for run_cmd we fly away from home
13555
for (tloc, command) in (new_loc, self.run_cmd), (second_loc, self.run_cmd_int):
13556
self.run_cmd_int(
13557
mavutil.mavlink.MAV_CMD_DO_REPOSITION,
13558
p1=-1, # "default"
13559
p2=0, # flags; none supplied here
13560
p3=0, # loiter radius for planes, zero ignored
13561
p4=float("nan"), # nan means do whatever you want to do
13562
p5=int(tloc.lat * 1e7),
13563
p6=int(tloc.lng * 1e7),
13564
p7=tloc.alt,
13565
frame=mavutil.mavlink.MAV_FRAME_GLOBAL,
13566
)
13567
for speed in [2, 10, 4]:
13568
command(
13569
mavutil.mavlink.MAV_CMD_DO_CHANGE_SPEED,
13570
p1=1, # groundspeed,
13571
p2=speed,
13572
p3=-1, # throttle, -1 is no-change
13573
p4=0, # absolute value, not relative
13574
)
13575
self.wait_groundspeed(speed-0.2, speed+0.2, minimum_duration=10, timeout=20)
13576
13577
# we've made random changes to vehicle guided speeds above;
13578
# reboot vehicle to reset those:
13579
self.disarm_vehicle(force=True)
13580
self.reboot_sitl()
13581
13582
def _MAV_CMD_DO_FLIGHTTERMINATION(self, command):
13583
self.set_parameters({
13584
"MAV_GCS_SYSID": self.mav.source_system,
13585
"DISARM_DELAY": 0,
13586
})
13587
self.wait_ready_to_arm()
13588
self.arm_vehicle()
13589
self.context_collect('STATUSTEXT')
13590
command(mavutil.mavlink.MAV_CMD_DO_FLIGHTTERMINATION, p1=1)
13591
self.wait_disarmed()
13592
self.reboot_sitl()
13593
13594
def MAV_CMD_DO_FLIGHTTERMINATION(self):
13595
'''test MAV_CMD_DO_FLIGHTTERMINATION works on Copter'''
13596
self._MAV_CMD_DO_FLIGHTTERMINATION(self.run_cmd)
13597
self._MAV_CMD_DO_FLIGHTTERMINATION(self.run_cmd_int)
13598
13599
def MAV_CMD_NAV_LOITER_UNLIM(self):
13600
'''ensure MAV_CMD_NAV_LOITER_UNLIM via mavlink works'''
13601
for command in self.run_cmd, self.run_cmd_int:
13602
self.change_mode('STABILIZE')
13603
command(mavutil.mavlink.MAV_CMD_NAV_LOITER_UNLIM)
13604
self.wait_mode('LOITER')
13605
13606
def MAV_CMD_NAV_RETURN_TO_LAUNCH(self):
13607
'''ensure MAV_CMD_NAV_RETURN_TO_LAUNCH via mavlink works'''
13608
for command in self.run_cmd, self.run_cmd_int:
13609
self.change_mode('STABILIZE')
13610
command(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH)
13611
self.wait_mode('RTL')
13612
13613
def MAV_CMD_NAV_VTOL_LAND(self):
13614
'''ensure MAV_CMD_NAV_LAND via mavlink works'''
13615
for command in self.run_cmd, self.run_cmd_int:
13616
self.change_mode('STABILIZE')
13617
command(mavutil.mavlink.MAV_CMD_NAV_VTOL_LAND)
13618
self.wait_mode('LAND')
13619
self.change_mode('STABILIZE')
13620
command(mavutil.mavlink.MAV_CMD_NAV_LAND)
13621
self.wait_mode('LAND')
13622
13623
def clear_roi(self):
13624
'''ensure three commands that clear ROI are equivalent'''
13625
13626
self.upload_simple_relhome_mission([
13627
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
13628
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
13629
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 200, 0, 20), # directly North, i.e. 0 degrees
13630
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 400, 0, 20), # directly North, i.e. 0 degrees
13631
])
13632
13633
self.set_parameter("AUTO_OPTIONS", 3)
13634
self.change_mode('AUTO')
13635
self.wait_ready_to_arm()
13636
self.arm_vehicle()
13637
home_loc = self.mav.location()
13638
13639
cmd_ids = [
13640
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
13641
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
13642
mavutil.mavlink.MAV_CMD_DO_SET_ROI_NONE,
13643
]
13644
for command in self.run_cmd, self.run_cmd_int:
13645
for cmd_id in cmd_ids:
13646
self.wait_waypoint(2, 2)
13647
13648
# Set an ROI at the Home location, expect to point at Home
13649
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION, p5=home_loc.lat, p6=home_loc.lng, p7=home_loc.alt)
13650
self.wait_heading(180)
13651
13652
# Clear the ROI, expect to point at the next Waypoint
13653
self.progress("Clear ROI using %s(%d)" % (command.__name__, cmd_id))
13654
command(cmd_id)
13655
self.wait_heading(0)
13656
13657
self.wait_waypoint(4, 4)
13658
self.set_current_waypoint_using_mav_cmd_do_set_mission_current(seq=2)
13659
13660
self.land_and_disarm()
13661
13662
def start_flying_simple_relhome_mission(self, items):
13663
'''uploads items, changes mode to auto, waits ready to arm and arms
13664
vehicle. If the first item it a takeoff you can expect the
13665
vehicle to fly after this method returns
13666
'''
13667
13668
self.upload_simple_relhome_mission(items)
13669
13670
self.set_parameter("AUTO_OPTIONS", 3)
13671
self.change_mode('AUTO')
13672
self.wait_ready_to_arm()
13673
13674
self.arm_vehicle()
13675
13676
def _MAV_CMD_DO_LAND_START(self, run_cmd):
13677
alt = 5
13678
self.start_flying_simple_relhome_mission([
13679
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, alt),
13680
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 200, 0, alt),
13681
(mavutil.mavlink.MAV_CMD_NAV_LAND, 0, 0, 0),
13682
(mavutil.mavlink.MAV_CMD_DO_LAND_START, 0, 0, alt),
13683
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 200, 2000, alt),
13684
(mavutil.mavlink.MAV_CMD_NAV_LAND, 0, 0, 0),
13685
])
13686
13687
self.wait_current_waypoint(2)
13688
run_cmd(mavutil.mavlink.MAV_CMD_DO_LAND_START)
13689
self.wait_current_waypoint(5)
13690
# we pretend to be in RTL mode while doing this:
13691
self.wait_mode("AUTO_RTL")
13692
self.do_RTL()
13693
13694
def MAV_CMD_DO_LAND_START(self):
13695
'''test handling of mavlink-received MAV_CMD_DO_LAND_START command'''
13696
self._MAV_CMD_DO_LAND_START(self.run_cmd)
13697
self.zero_throttle()
13698
self._MAV_CMD_DO_LAND_START(self.run_cmd_int)
13699
13700
def _MAV_CMD_SET_EKF_SOURCE_SET(self, run_cmd):
13701
run_cmd(
13702
mavutil.mavlink.MAV_CMD_SET_EKF_SOURCE_SET,
13703
17,
13704
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
13705
)
13706
13707
self.change_mode('LOITER')
13708
self.wait_ready_to_arm()
13709
13710
run_cmd(mavutil.mavlink.MAV_CMD_SET_EKF_SOURCE_SET, 2)
13711
13712
self.assert_prearm_failure('Need Position Estimate')
13713
run_cmd(mavutil.mavlink.MAV_CMD_SET_EKF_SOURCE_SET, 1)
13714
13715
self.wait_ready_to_arm()
13716
13717
def MAV_CMD_SET_EKF_SOURCE_SET(self):
13718
'''test setting of source sets using mavlink command'''
13719
self._MAV_CMD_SET_EKF_SOURCE_SET(self.run_cmd)
13720
self._MAV_CMD_SET_EKF_SOURCE_SET(self.run_cmd_int)
13721
13722
def MAV_CMD_NAV_TAKEOFF(self):
13723
'''test issuing takeoff command via mavlink'''
13724
self.change_mode('GUIDED')
13725
self.wait_ready_to_arm()
13726
13727
self.arm_vehicle()
13728
self.run_cmd(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, p7=5)
13729
self.wait_altitude(4.5, 5.5, minimum_duration=5, relative=True)
13730
self.change_mode('LAND')
13731
self.wait_disarmed()
13732
13733
self.start_subtest("Check NAV_TAKEOFF is above home location, not current location")
13734
# reset home 20 metres above current location
13735
current_alt_abs = self.get_altitude(relative=False)
13736
13737
loc = self.mav.location()
13738
13739
home_z_ofs = 20
13740
self.run_cmd(
13741
mavutil.mavlink.MAV_CMD_DO_SET_HOME,
13742
p5=loc.lat,
13743
p6=loc.lng,
13744
p7=current_alt_abs + home_z_ofs,
13745
)
13746
13747
self.change_mode('GUIDED')
13748
self.arm_vehicle()
13749
takeoff_alt = 5
13750
self.run_cmd(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, p7=takeoff_alt)
13751
self.wait_altitude(
13752
current_alt_abs + home_z_ofs + takeoff_alt - 0.5,
13753
current_alt_abs + home_z_ofs + takeoff_alt + 0.5,
13754
minimum_duration=5,
13755
relative=False,
13756
)
13757
self.change_mode('LAND')
13758
self.wait_disarmed()
13759
13760
self.reboot_sitl() # unlock home position
13761
13762
def MAV_CMD_NAV_TAKEOFF_command_int(self):
13763
'''test issuing takeoff command via mavlink and command_int'''
13764
self.change_mode('GUIDED')
13765
self.wait_ready_to_arm()
13766
13767
self.start_subtest("Check NAV_TAKEOFF is above home location, not current location")
13768
# reset home 20 metres above current location
13769
current_alt_abs = self.get_altitude(relative=False)
13770
13771
loc = self.mav.location()
13772
13773
home_z_ofs = 20
13774
self.run_cmd(
13775
mavutil.mavlink.MAV_CMD_DO_SET_HOME,
13776
p5=loc.lat,
13777
p6=loc.lng,
13778
p7=current_alt_abs + home_z_ofs,
13779
)
13780
13781
self.change_mode('GUIDED')
13782
self.arm_vehicle()
13783
takeoff_alt = 5
13784
self.run_cmd_int(
13785
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
13786
p7=takeoff_alt,
13787
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
13788
)
13789
self.wait_altitude(
13790
current_alt_abs + home_z_ofs + takeoff_alt - 0.5,
13791
current_alt_abs + home_z_ofs + takeoff_alt + 0.5,
13792
minimum_duration=5,
13793
relative=False,
13794
)
13795
self.change_mode('LAND')
13796
self.wait_disarmed()
13797
13798
self.reboot_sitl() # unlock home position
13799
13800
def Ch6TuningWPSpeed(self):
13801
'''test waypoint speed can be changed via Ch6 tuning knob'''
13802
self.set_parameters({
13803
"RC6_OPTION": 219, # RC6 used for tuning
13804
"TUNE": 10, # 10 is waypoint speed
13805
"TUNE_MIN": 0.02, # 20cm/s
13806
"TUNE_MAX": 1000, # 10m/s
13807
"AUTO_OPTIONS": 3,
13808
})
13809
self.set_rc(6, 2000)
13810
self.reboot_sitl()
13811
13812
self.upload_simple_relhome_mission([
13813
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
13814
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 2000, 0, 20),
13815
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
13816
])
13817
self.change_mode('AUTO')
13818
13819
self.wait_ready_to_arm()
13820
13821
self.arm_vehicle()
13822
13823
self.wait_groundspeed(9.5, 10.5, minimum_duration=5)
13824
13825
self.set_rc(6, 1500)
13826
self.wait_groundspeed(4.5, 5.5, minimum_duration=5)
13827
13828
self.set_rc(6, 2000)
13829
self.wait_groundspeed(9.5, 10.5, minimum_duration=5)
13830
13831
self.set_rc(6, 1300)
13832
self.wait_groundspeed(2.5, 3.5, minimum_duration=5)
13833
13834
self.do_RTL()
13835
13836
def Ch6TuningLoitMaxXYSpeed(self):
13837
'''test loiter can be changed via Ch6 tuning knob'''
13838
self.set_parameters({
13839
"RC6_OPTION": 219, # RC6 used for tuning
13840
"TUNE": 60, # 60 is x/y loiter speed
13841
"TUNE_MIN": 0.2, # 0.2m/s
13842
"TUNE_MAX": 10, # 10m/s
13843
"AUTO_OPTIONS": 3,
13844
})
13845
self.set_rc(6, 2000)
13846
self.reboot_sitl()
13847
13848
self.takeoff(mode='LOITER')
13849
13850
self.set_rc(2, 1000)
13851
13852
self.wait_groundspeed(9.5, 10.5, minimum_duration=5)
13853
13854
self.set_rc(6, 1500)
13855
self.wait_groundspeed(4.5, 5.5, minimum_duration=5)
13856
13857
self.set_rc(6, 2000)
13858
self.wait_groundspeed(9.5, 10.5, minimum_duration=5)
13859
13860
self.set_rc(6, 1300)
13861
self.wait_groundspeed(2.5, 3.5, minimum_duration=5)
13862
13863
self.set_rc(2, 1500)
13864
13865
self.do_RTL()
13866
13867
def DualTuningChannels(self):
13868
'''check tuning channel options work independently'''
13869
RC6_MIN = 1100 # yes, this is strange!
13870
RC6_MAX = 1200 # yes, this is strange!
13871
TUNE_MIN = 0.25 # m/s
13872
TUNE_MAX = 10.0 # m/s
13873
13874
RC7_MIN = 1000
13875
RC7_MAX = 2000
13876
TUNE2_MIN = 100 # cm/s
13877
TUNE2_MAX = 500 # cm/s
13878
self.set_parameters({
13879
"RC6_OPTION": 219, # RC6 used for tuning
13880
"RC6_MIN": RC6_MIN,
13881
"RC6_MAX": RC6_MAX,
13882
"TUNE": 60, # 60 is x/y loiter speed
13883
"TUNE_MIN": TUNE_MIN,
13884
"TUNE_MAX": TUNE_MAX,
13885
13886
"RC7_OPTION": 220, # RC7 used for second param tuning
13887
"RC7_MIN": RC7_MIN,
13888
"RC7_MAX": RC7_MAX,
13889
"TUNE2": 28, # PSC_NE_VEL_I
13890
"TUNE2_MIN": TUNE2_MIN,
13891
"TUNE2_MAX": TUNE2_MAX,
13892
})
13893
13894
self.reboot_sitl()
13895
13896
self.set_rc(6, RC6_MIN)
13897
self.delay_sim_time(1)
13898
self.assert_parameter_value("LOIT_SPEED_MS", TUNE_MIN)
13899
13900
self.set_rc(6, RC6_MAX)
13901
self.delay_sim_time(1)
13902
self.assert_parameter_value("LOIT_SPEED_MS", TUNE_MAX)
13903
self.set_rc(6, RC6_MIN)
13904
self.delay_sim_time(1)
13905
self.assert_parameter_value("LOIT_SPEED_MS", TUNE_MIN)
13906
13907
self.set_rc(6, int((RC6_MIN+RC6_MAX)/2))
13908
self.delay_sim_time(1)
13909
# note that this check is also used below ("RC6 is unaffected")
13910
self.assert_parameter_value("LOIT_SPEED_MS", int((TUNE_MIN+TUNE_MAX)/2), epsilon=1)
13911
13912
self.set_rc(7, RC7_MIN)
13913
self.delay_sim_time(1)
13914
self.assert_parameter_value("PSC_NE_VEL_I", TUNE2_MIN)
13915
13916
self.set_rc(7, RC7_MAX)
13917
self.delay_sim_time(1)
13918
self.assert_parameter_value("PSC_NE_VEL_I", TUNE2_MAX)
13919
13920
# make sure RC6 is unaffected:
13921
self.assert_parameter_value("LOIT_SPEED_MS", int((TUNE_MIN+TUNE_MAX)/2), epsilon=1)
13922
13923
self.set_rc(7, int((RC7_MIN+RC7_MAX)/2))
13924
self.delay_sim_time(1)
13925
self.assert_parameter_value("PSC_NE_VEL_I", int((TUNE2_MIN+TUNE2_MAX)/2), epsilon=1)
13926
13927
def PILOT_THR_BHV(self):
13928
'''test the PILOT_THR_BHV parameter'''
13929
self.start_subtest("Test default behaviour, no disarm on land")
13930
self.set_parameters({
13931
"DISARM_DELAY": 0,
13932
})
13933
self.takeoff(2, mode='GUIDED')
13934
self.set_rc(3, 1500)
13935
self.change_mode('LOITER')
13936
self.set_rc(3, 1300)
13937
13938
maintain_armed = WaitAndMaintainArmed(self, minimum_duration=20)
13939
maintain_armed.run()
13940
13941
self.start_subtest("Test THR_BEHAVE_DISARM_ON_LAND_DETECT")
13942
self.set_parameters({
13943
"PILOT_THR_BHV": 4, # Disarm on land detection
13944
})
13945
self.zero_throttle()
13946
self.takeoff(2, mode='GUIDED')
13947
self.set_rc(3, 1500)
13948
self.change_mode('LOITER')
13949
self.set_rc(3, 1300)
13950
13951
self.wait_disarmed()
13952
13953
def CameraLogMessages(self):
13954
'''ensure Camera log messages are good'''
13955
self.set_parameter("RC12_OPTION", 9) # CameraTrigger
13956
self.set_parameter("CAM1_TYPE", 1) # Camera with servo trigger
13957
self.reboot_sitl() # needed for RC12_OPTION to take effect
13958
13959
gpis = []
13960
gps_raws = []
13961
attitudes = []
13962
13963
self.takeoff(10, mode='GUIDED')
13964
self.set_rc(12, 2000)
13965
gpis.append(self.assert_receive_message('GLOBAL_POSITION_INT'))
13966
gps_raws.append(self.assert_receive_message('GPS_RAW_INT'))
13967
attitudes.append(self.assert_receive_message('ATTITUDE'))
13968
self.set_rc(12, 1000)
13969
13970
self.fly_guided_move_local(0, 0, 20)
13971
13972
self.set_rc(12, 2000)
13973
gpis.append(self.assert_receive_message('GLOBAL_POSITION_INT'))
13974
gps_raws.append(self.assert_receive_message('GPS_RAW_INT'))
13975
attitudes.append(self.assert_receive_message('ATTITUDE'))
13976
self.set_rc(12, 1000)
13977
13978
self.change_mode('LOITER')
13979
self.set_rc(1, 1000)
13980
self.set_rc(2, 1000)
13981
self.delay_sim_time(5)
13982
self.set_rc(12, 2000)
13983
gpis.append(self.assert_receive_message('GLOBAL_POSITION_INT'))
13984
gps_raws.append(self.assert_receive_message('GPS_RAW_INT'))
13985
attitudes.append(self.assert_receive_message('ATTITUDE'))
13986
self.set_rc(12, 1000)
13987
self.set_rc(1, 1500)
13988
self.set_rc(2, 1500)
13989
13990
dfreader = self.dfreader_for_current_onboard_log()
13991
self.do_RTL()
13992
13993
for i in range(len(gpis)):
13994
gpi = gpis[i]
13995
gps_raw = gps_raws[i]
13996
attitude = attitudes[i]
13997
m = dfreader.recv_match(type="CAM")
13998
13999
things = [
14000
["absalt", gpi.alt*0.001, m.Alt],
14001
["relalt", gpi.relative_alt*0.001, m.RelAlt],
14002
["gpsalt", gps_raw.alt*0.001, m.GPSAlt], # use GPS_RAW here?
14003
["R", math.degrees(attitude.roll), m.R],
14004
["P", math.degrees(attitude.pitch), m.P],
14005
["Y", mavextra.wrap_360(math.degrees(attitude.yaw)), m.Y],
14006
]
14007
for (name, want, got) in things:
14008
if abs(got - want) > 1:
14009
raise NotAchievedException(f"Incorrect {name} {want=} {got=}")
14010
self.progress(f"{name} {want=} {got=}")
14011
14012
want = gpi.relative_alt*0.001
14013
got = m.RelAlt
14014
if abs(got - want) > 1:
14015
raise NotAchievedException(f"Incorrect relalt {want=} {got=}")
14016
14017
def LoiterToGuidedHomeVSOrigin(self):
14018
'''test moving from guided to loiter mode when home is a different alt
14019
to origin'''
14020
self.set_parameters({
14021
"TERRAIN_ENABLE": 1,
14022
"SIM_TERRAIN": 1,
14023
})
14024
self.takeoff(10, mode='GUIDED')
14025
here = self.mav.location()
14026
self.set_home(here)
14027
self.change_mode('LOITER')
14028
self.wait_altitude(here.alt-1, here.alt+1, minimum_duration=10)
14029
self.disarm_vehicle(force=True)
14030
self.reboot_sitl() # to "unstick" home
14031
14032
def GuidedModeThrust(self):
14033
'''test handling of option-bit-3, where mavlink commands are
14034
intrepreted as thrust not climb rate'''
14035
self.set_parameter('GUID_OPTIONS', 8)
14036
self.change_mode('GUIDED')
14037
self.wait_ready_to_arm()
14038
self.arm_vehicle()
14039
self.mav.mav.set_attitude_target_send(
14040
0, # time_boot_ms
14041
1, # target sysid
14042
1, # target compid
14043
0, # bitmask of things to ignore
14044
mavextra.euler_to_quat([0, 0, 0]), # att
14045
0, # roll rate (rad/s)
14046
0, # pitch rate (rad/s)
14047
0, # yaw rate (rad/s)
14048
0.5
14049
) # thrust, 0 to 1
14050
self.wait_altitude(0.5, 100, relative=True, timeout=10)
14051
self.do_RTL()
14052
14053
def AutoRTL(self):
14054
'''Test Auto RTL mode using do land start and return path start mission items'''
14055
alt = 50
14056
guided_loc = self.home_relative_loc_ne(1000, 0)
14057
guided_loc.alt += alt
14058
14059
# Arm, take off and fly to guided location
14060
self.takeoff(mode='GUIDED')
14061
self.fly_guided_move_to(guided_loc, timeout=240)
14062
14063
# Try auto RTL mode, should fail with no mission
14064
try:
14065
self.change_mode('AUTO_RTL', timeout=10)
14066
raise NotAchievedException("Should not change mode with no mission")
14067
except WaitModeTimeout:
14068
pass
14069
except Exception as e:
14070
raise e
14071
14072
# pymavlink does not understand the new return path command yet, at some point it will
14073
cmd_return_path_start = 188 # mavutil.mavlink.MAV_CMD_DO_RETURN_PATH_START
14074
14075
# Do land start and do return path should both fail as commands too
14076
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_LAND_START, want_result=mavutil.mavlink.MAV_RESULT_FAILED)
14077
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_FAILED)
14078
14079
# Load mission with do land start
14080
self.upload_simple_relhome_mission([
14081
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 1000, 0, alt), # 1
14082
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 750, 0, alt), # 2
14083
self.create_MISSION_ITEM_INT(mavutil.mavlink.MAV_CMD_DO_LAND_START), # 3
14084
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 500, 0, alt), # 4
14085
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 250, 0, alt), # 5
14086
])
14087
14088
# Return path should still fail
14089
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_FAILED)
14090
14091
# Do land start should jump to the waypoint following the item
14092
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_LAND_START, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
14093
self.drain_mav()
14094
self.assert_current_waypoint(4)
14095
14096
# Back to guided location
14097
self.change_mode('GUIDED')
14098
self.fly_guided_move_to(guided_loc)
14099
14100
# mode change to Auto RTL should do the same
14101
self.change_mode('AUTO_RTL')
14102
self.drain_mav()
14103
self.assert_current_waypoint(4)
14104
14105
# Back to guided location
14106
self.change_mode('GUIDED')
14107
self.fly_guided_move_to(guided_loc)
14108
14109
# Add a return path item
14110
self.upload_simple_relhome_mission([
14111
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 1250, 0, alt), # 1
14112
self.create_MISSION_ITEM_INT(cmd_return_path_start), # 2
14113
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 900, 0, alt), # 3
14114
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 750, 0, alt), # 4
14115
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 550, 0, alt), # 5
14116
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 500, 0, alt), # 6
14117
self.create_MISSION_ITEM_INT(mavutil.mavlink.MAV_CMD_DO_LAND_START), # 7
14118
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 250, 0, alt), # 8
14119
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, -250, 0, alt), # 9
14120
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, -500, 0, alt), # 10
14121
])
14122
14123
# Return path should now work
14124
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
14125
self.drain_mav()
14126
self.assert_current_waypoint(3)
14127
14128
# Back to guided location
14129
self.change_mode('GUIDED')
14130
self.fly_guided_move_to(guided_loc)
14131
14132
# mode change to Auto RTL should join the return path
14133
self.change_mode('AUTO_RTL')
14134
self.drain_mav()
14135
self.assert_current_waypoint(3)
14136
14137
# do land start should still work
14138
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_LAND_START, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
14139
self.drain_mav()
14140
self.assert_current_waypoint(8)
14141
14142
# Move a bit closer in guided
14143
return_path_test = self.home_relative_loc_ne(600, 0)
14144
return_path_test.alt += alt
14145
self.change_mode('GUIDED')
14146
self.fly_guided_move_to(return_path_test, timeout=100)
14147
14148
# check the mission is joined further along
14149
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
14150
self.drain_mav()
14151
self.assert_current_waypoint(5)
14152
14153
# fly over home
14154
home = self.home_relative_loc_ne(0, 0)
14155
home.alt += alt
14156
self.change_mode('GUIDED')
14157
self.fly_guided_move_to(home, timeout=140)
14158
14159
# Should never join return path after do land start
14160
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
14161
self.drain_mav()
14162
self.assert_current_waypoint(6)
14163
14164
# Done
14165
self.land_and_disarm()
14166
14167
def EK3_OGN_HGT_MASK(self):
14168
'''test baraometer-alt-compensation based on long-term GPS readings'''
14169
self.context_push()
14170
self.set_parameters({
14171
'EK3_OGN_HGT_MASK': 1, # compensate baro drift using GPS
14172
})
14173
self.reboot_sitl()
14174
14175
expected_alt = 10
14176
14177
self.change_mode('GUIDED')
14178
self.wait_ready_to_arm()
14179
current_alt = self.get_altitude()
14180
14181
expected_alt_abs = current_alt + expected_alt
14182
14183
self.takeoff(expected_alt, mode='GUIDED')
14184
self.delay_sim_time(5)
14185
14186
self.set_parameter("SIM_BARO_DRIFT", 0.01) # 1cm/second
14187
14188
def check_altitude(mav, m):
14189
m_type = m.get_type()
14190
epsilon = 10 # in metres
14191
if m_type == 'GPS_RAW_INT':
14192
got_gps_alt = m.alt * 0.001
14193
if abs(expected_alt_abs - got_gps_alt) > epsilon:
14194
raise NotAchievedException(f"Bad GPS altitude (got={got_gps_alt} want={expected_alt_abs})")
14195
elif m_type == 'GLOBAL_POSITION_INT':
14196
got_canonical_alt = m.relative_alt * 0.001
14197
if abs(expected_alt - got_canonical_alt) > epsilon:
14198
raise NotAchievedException(f"Bad canonical altitude (got={got_canonical_alt} want={expected_alt})")
14199
14200
self.install_message_hook_context(check_altitude)
14201
14202
self.delay_sim_time(1500)
14203
14204
self.disarm_vehicle(force=True)
14205
14206
self.context_pop()
14207
14208
self.reboot_sitl(force=True)
14209
14210
def GuidedForceArm(self):
14211
'''ensure Guided acts appropriately when force-armed'''
14212
self.set_parameters({
14213
"EK3_SRC2_VELXY": 5,
14214
"SIM_GPS1_ENABLE": 0,
14215
})
14216
self.load_default_params_file("copter-optflow.parm")
14217
self.reboot_sitl()
14218
self.delay_sim_time(30)
14219
self.change_mode('GUIDED')
14220
self.arm_vehicle(force=True)
14221
self.takeoff(20, mode='GUIDED')
14222
location = self.offset_location_ne(self.sim_location(), metres_north=0, metres_east=-300)
14223
self.progress("Ensure we don't move for 10 seconds")
14224
tstart = self.get_sim_time()
14225
startpos = self.sim_location_int()
14226
while True:
14227
now = self.get_sim_time_cached()
14228
if now - tstart > 10:
14229
break
14230
self.send_set_position_target_global_int(int(location.lat*1e7), int(location.lng*1e7), 10)
14231
dist = self.get_distance_int(startpos, self.sim_location_int())
14232
if dist > 10:
14233
raise NotAchievedException("Wandered too far from start position")
14234
self.delay_sim_time(1)
14235
14236
self.disarm_vehicle(force=True)
14237
self.reboot_sitl()
14238
14239
def EK3_OGN_HGT_MASK_climbing(self):
14240
'''check combination of height bits doesn't cause climb'''
14241
self.context_push()
14242
self.set_parameters({
14243
'EK3_OGN_HGT_MASK': 5,
14244
})
14245
self.reboot_sitl()
14246
14247
expected_alt = 10
14248
14249
self.change_mode('GUIDED')
14250
self.wait_ready_to_arm()
14251
current_alt = self.get_altitude()
14252
14253
expected_alt_abs = current_alt + expected_alt
14254
14255
self.takeoff(expected_alt, mode='GUIDED')
14256
self.delay_sim_time(5)
14257
14258
def check_altitude(mav, m):
14259
m_type = m.get_type()
14260
epsilon = 10 # in metres
14261
if m_type == 'GPS_RAW_INT':
14262
got_gps_alt = m.alt * 0.001
14263
if abs(expected_alt_abs - got_gps_alt) > epsilon:
14264
raise NotAchievedException(f"Bad GPS altitude (got={got_gps_alt} want={expected_alt_abs})")
14265
elif m_type == 'GLOBAL_POSITION_INT':
14266
if abs(expected_alt - m.relative_alt * 0.001) > epsilon:
14267
raise NotAchievedException("Bad canonical altitude")
14268
14269
self.install_message_hook_context(check_altitude)
14270
14271
self.delay_sim_time(1500)
14272
14273
self.disarm_vehicle(force=True)
14274
14275
self.context_pop()
14276
self.reboot_sitl(force=True)
14277
14278
def GuidedWeatherVane(self):
14279
'''check Copter Guided mode weathervane option'''
14280
self.set_parameters({
14281
'SIM_WIND_SPD': 10,
14282
'SIM_WIND_DIR': 90,
14283
'WVANE_ENABLE': 1,
14284
})
14285
self.takeoff(20, mode='GUIDED')
14286
self.guided_achieve_heading(0)
14287
14288
self.set_parameter("GUID_OPTIONS", 128)
14289
self.wait_heading(90, timeout=60, minimum_duration=10)
14290
self.do_RTL()
14291
14292
def Clamp(self):
14293
'''test Copter docking clamp'''
14294
clamp_ch = 11
14295
self.set_parameters({
14296
"SIM_CLAMP_CH": clamp_ch,
14297
})
14298
14299
self.takeoff(1, mode='LOITER')
14300
14301
self.context_push()
14302
self.context_collect('STATUSTEXT')
14303
self.progress("Ensure can't take off with clamp in place")
14304
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=2000)
14305
self.wait_statustext("SITL: Clamp: grabbed vehicle", check_context=True)
14306
self.arm_vehicle()
14307
self.set_rc(3, 2000)
14308
self.wait_altitude(0, 5, minimum_duration=5, relative=True)
14309
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=1000)
14310
self.wait_statustext("SITL: Clamp: released vehicle", check_context=True)
14311
self.wait_altitude(5, 5000, minimum_duration=1, relative=True)
14312
self.do_RTL()
14313
self.set_rc(3, 1000)
14314
self.change_mode('LOITER')
14315
self.context_pop()
14316
14317
self.progress("Same again for repeatability")
14318
self.context_push()
14319
self.context_collect('STATUSTEXT')
14320
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=2000)
14321
self.wait_statustext("SITL: Clamp: grabbed vehicle", check_context=True)
14322
self.arm_vehicle()
14323
self.set_rc(3, 2000)
14324
self.wait_altitude(0, 1, minimum_duration=5, relative=True)
14325
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=1000)
14326
self.wait_statustext("SITL: Clamp: released vehicle", check_context=True)
14327
self.wait_altitude(5, 5000, minimum_duration=1, relative=True)
14328
self.do_RTL()
14329
self.set_rc(3, 1000)
14330
self.change_mode('LOITER')
14331
self.context_pop()
14332
14333
here = self.mav.location()
14334
loc = self.offset_location_ne(here, 10, 0)
14335
self.takeoff(5, mode='GUIDED')
14336
self.send_do_reposition(loc, frame=mavutil.mavlink.MAV_FRAME_GLOBAL)
14337
self.wait_location(loc, timeout=120)
14338
self.land_and_disarm()
14339
14340
# explicitly set home so we RTL to the right spot
14341
self.set_home(here)
14342
14343
self.context_push()
14344
self.context_collect('STATUSTEXT')
14345
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=2000)
14346
self.wait_statustext("SITL: Clamp: missed vehicle", check_context=True)
14347
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=1000)
14348
self.context_pop()
14349
14350
self.takeoff(5, mode='GUIDED')
14351
self.do_RTL()
14352
14353
self.takeoff(5, mode='GUIDED')
14354
self.land_and_disarm()
14355
14356
self.context_push()
14357
self.context_collect('STATUSTEXT')
14358
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=2000)
14359
self.wait_statustext("SITL: Clamp: grabbed vehicle", check_context=True)
14360
self.context_pop()
14361
14362
self.reboot_sitl() # because we set home
14363
14364
def GripperReleaseOnThrustLoss(self):
14365
'''tests that gripper is released on thrust loss if option set'''
14366
14367
self.context_push()
14368
self.set_servo_gripper_parameters()
14369
self.reboot_sitl()
14370
14371
self.takeoff(30, mode='LOITER')
14372
self.context_push()
14373
self.context_collect('STATUSTEXT')
14374
self.set_parameters({
14375
"SIM_ENGINE_MUL": 0.5,
14376
"SIM_ENGINE_FAIL": 1 << 1, # motor 2
14377
"FLIGHT_OPTIONS": 4,
14378
})
14379
14380
self.wait_statustext("Gripper load released", regex=True, timeout=60)
14381
self.context_pop()
14382
14383
self.do_RTL()
14384
self.context_pop()
14385
self.reboot_sitl()
14386
14387
def GripperInitialPosition(self):
14388
'''test gripper initial position'''
14389
14390
self.set_servo_gripper_parameters()
14391
self.set_parameters({
14392
"RC8_OPTION": 19, # gripper
14393
})
14394
grip_neutral = 1423
14395
self.set_parameter('GRIP_NEUTRAL', grip_neutral)
14396
14397
# install a message hook which ensures that the output stays at neutral
14398
global seen_grip_neutral
14399
seen_grip_neutral = False
14400
14401
def hook(mav, m):
14402
global seen_grip_neutral
14403
if m.get_type() != 'SERVO_OUTPUT_RAW':
14404
return
14405
if m.servo8_raw == 0:
14406
# initial value
14407
if seen_grip_neutral:
14408
raise NotAchievedException("Saw 0 after seeing grip neutral")
14409
return
14410
if m.servo8_raw == grip_neutral:
14411
# expected value after autopilot boot
14412
seen_grip_neutral = True
14413
return
14414
raise NotAchievedException(f"Received bad value {m.servo8_raw=} for servo gripper")
14415
self.install_message_hook_context(hook)
14416
14417
self.reboot_sitl()
14418
self.wait_ready_to_arm()
14419
14420
if not seen_grip_neutral:
14421
raise NotAchievedException("Never saw grip neutral")
14422
14423
def REQUIRE_LOCATION_FOR_ARMING(self):
14424
'''check AP_Arming::Option::REQUIRE_LOCATION_FOR_ARMING works'''
14425
self.context_push()
14426
self.set_parameters({
14427
"SIM_GPS1_NUMSATS": 3, # EKF does not like < 6
14428
})
14429
self.reboot_sitl()
14430
self.change_mode('STABILIZE')
14431
self.wait_prearm_sys_status_healthy()
14432
self.assert_home_position_not_set()
14433
self.arm_vehicle()
14434
self.disarm_vehicle()
14435
self.change_mode('LOITER')
14436
self.assert_prearm_failure("waiting for home", other_prearm_failures_fatal=False)
14437
14438
self.change_mode('STABILIZE')
14439
self.set_parameters({
14440
"ARMING_NEED_LOC": 1,
14441
})
14442
self.assert_prearm_failure("Need Position Estimate", other_prearm_failures_fatal=False)
14443
self.context_pop()
14444
self.reboot_sitl()
14445
14446
def AutoContinueOnRCFailsafe(self):
14447
'''check LOITER when entered after RC failsafe is ignored in auto'''
14448
self.set_parameters({
14449
"FS_OPTIONS": 1, # 1 is "RC continue if in auto"
14450
})
14451
14452
self.upload_simple_relhome_mission([
14453
# N E U
14454
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 10),
14455
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 10),
14456
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 40, 0, 10),
14457
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 120, 0, 10),
14458
])
14459
14460
self.takeoff(mode='LOITER')
14461
self.set_rc(1, 1200)
14462
self.delay_sim_time(1) # build up some pilot desired stuff
14463
self.change_mode('AUTO')
14464
self.wait_waypoint(2, 2, max_dist=3)
14465
self.set_parameters({
14466
'SIM_RC_FAIL': 1,
14467
})
14468
# self.set_rc(1, 1500) # note we are still in RC fail!
14469
self.wait_waypoint(3, 3, max_dist=3)
14470
self.assert_mode_is('AUTO')
14471
self.change_mode('LOITER')
14472
self.wait_groundspeed(0, 0.1, minimum_duration=30, timeout=450)
14473
self.do_RTL()
14474
14475
def MissionRTLYawBehaviour(self):
14476
'''check end-of-mission yaw behaviour'''
14477
self.set_parameters({
14478
"AUTO_OPTIONS": 3,
14479
})
14480
14481
self.start_subtest("behaviour with WP_YAW_BEHAVE set to next-waypoint-except-RTL")
14482
self.upload_simple_relhome_mission([
14483
# N E U
14484
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 10),
14485
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 10),
14486
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
14487
])
14488
self.change_mode('AUTO')
14489
self.wait_ready_to_arm()
14490
original_heading = self.get_heading()
14491
if abs(original_heading) < 5:
14492
raise NotAchievedException(f"Bad original heading {original_heading}")
14493
self.arm_vehicle()
14494
self.wait_current_waypoint(3)
14495
self.wait_rtl_complete()
14496
self.wait_disarmed()
14497
if abs(self.get_heading()) > 5:
14498
raise NotAchievedException("Should have yaw zero without option")
14499
14500
# must change out of auto and back in again to reset state machine:
14501
self.change_mode('LOITER')
14502
self.change_mode('AUTO')
14503
14504
self.start_subtest("behaviour with WP_YAW_BEHAVE set to next-waypoint")
14505
self.upload_simple_relhome_mission([
14506
# N E U
14507
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 10),
14508
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 20, 20),
14509
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
14510
])
14511
self.set_parameters({
14512
"WP_YAW_BEHAVIOR": 1, # look at next waypoint (including in RTL)
14513
})
14514
self.change_mode('AUTO')
14515
self.wait_ready_to_arm()
14516
original_heading = self.get_heading()
14517
if abs(original_heading) > 1:
14518
raise NotAchievedException("Bad original heading")
14519
self.arm_vehicle()
14520
self.wait_current_waypoint(3)
14521
self.wait_rtl_complete()
14522
self.wait_disarmed()
14523
new_heading = self.get_heading()
14524
if abs(new_heading - original_heading) > 5:
14525
raise NotAchievedException(f"Should return to original heading want={original_heading} got={new_heading}")
14526
14527
def BatteryInternalUseOnly(self):
14528
'''batteries marked as internal use only should not appear over mavlink'''
14529
self.set_parameters({
14530
"BATT_MONITOR": 4, # 4 is analog volt+curr
14531
"BATT2_MONITOR": 4,
14532
})
14533
self.reboot_sitl()
14534
self.wait_message_field_values('BATTERY_STATUS', {
14535
"id": 0,
14536
})
14537
self.wait_message_field_values('BATTERY_STATUS', {
14538
"id": 1,
14539
})
14540
self.progress("Making battery private")
14541
self.set_parameters({
14542
"BATT_OPTIONS": 256,
14543
})
14544
self.wait_message_field_values('BATTERY_STATUS', {
14545
"id": 1,
14546
})
14547
for i in range(10):
14548
self.assert_received_message_field_values('BATTERY_STATUS', {
14549
"id": 1
14550
})
14551
14552
def MAV_CMD_MISSION_START_p1_p2(self):
14553
'''make sure we deny MAV_CMD_MISSION_START if either p1 or p2 non-zero'''
14554
self.upload_simple_relhome_mission([
14555
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
14556
])
14557
self.set_parameters({
14558
"AUTO_OPTIONS": 3,
14559
})
14560
self.change_mode('AUTO')
14561
self.wait_ready_to_arm()
14562
14563
self.run_cmd(
14564
mavutil.mavlink.MAV_CMD_MISSION_START,
14565
p1=1,
14566
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
14567
)
14568
14569
self.run_cmd(
14570
mavutil.mavlink.MAV_CMD_MISSION_START,
14571
p2=1,
14572
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
14573
)
14574
14575
self.run_cmd(
14576
mavutil.mavlink.MAV_CMD_MISSION_START,
14577
p1=1,
14578
p2=1,
14579
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
14580
)
14581
14582
def ScriptingAHRSSource(self):
14583
'''test ahrs-source.lua script'''
14584
self.install_example_script_context("ahrs-source.lua")
14585
self.set_parameters({
14586
"RC10_OPTION": 90,
14587
"SCR_ENABLE": 1,
14588
"SCR_USER1": 10, # something else
14589
"SCR_USER2": 10, # GPS something
14590
"SCR_USER3": 0.2, # ExtNav innovation
14591
})
14592
self.set_rc(10, 2000)
14593
self.reboot_sitl()
14594
self.context_collect('STATUSTEXT')
14595
self.set_rc(10, 1000)
14596
self.wait_statustext('Using EKF Source Set 1', check_context=True)
14597
self.set_rc(10, 1500)
14598
self.wait_statustext('Using EKF Source Set 2', check_context=True)
14599
self.set_rc(10, 2000)
14600
self.wait_statustext('Using EKF Source Set 3', check_context=True)
14601
14602
def CommonOrigin(self):
14603
"""Test common origin between EKF2 and EKF3"""
14604
self.context_push()
14605
14606
# start on EKF2
14607
self.set_parameters({
14608
'AHRS_EKF_TYPE': 2,
14609
'EK2_ENABLE': 1,
14610
'EK3_CHECK_SCALE': 1, # make EK3 slow to get origin
14611
})
14612
self.reboot_sitl()
14613
14614
self.context_collect('STATUSTEXT')
14615
14616
self.wait_statustext("EKF2 IMU0 origin set", timeout=60, check_context=True)
14617
self.wait_statustext("EKF2 IMU0 is using GPS", timeout=60, check_context=True)
14618
self.wait_statustext("EKF2 active", timeout=60, check_context=True)
14619
14620
self.context_collect('GPS_GLOBAL_ORIGIN')
14621
14622
# get EKF2 origin
14623
self.run_cmd(mavutil.mavlink.MAV_CMD_GET_HOME_POSITION)
14624
ek2_origin = self.assert_receive_message('GPS_GLOBAL_ORIGIN', check_context=True)
14625
14626
# switch to EKF3
14627
self.set_parameters({
14628
'SIM_GPS1_GLTCH_X' : 0.001, # about 100m
14629
'EK3_CHECK_SCALE' : 100,
14630
'AHRS_EKF_TYPE' : 3})
14631
14632
self.wait_statustext("EKF3 IMU0 is using GPS", timeout=60, check_context=True)
14633
self.wait_statustext("EKF3 active", timeout=60, check_context=True)
14634
14635
self.run_cmd(mavutil.mavlink.MAV_CMD_GET_HOME_POSITION)
14636
ek3_origin = self.assert_receive_message('GPS_GLOBAL_ORIGIN', check_context=True)
14637
14638
self.progress("Checking origins")
14639
if ek2_origin.time_usec == ek3_origin.time_usec:
14640
raise NotAchievedException("Did not get a new GPS_GLOBAL_ORIGIN message")
14641
14642
print(ek2_origin, ek3_origin)
14643
14644
if (ek2_origin.latitude != ek3_origin.latitude or
14645
ek2_origin.longitude != ek3_origin.longitude or
14646
ek2_origin.altitude != ek3_origin.altitude):
14647
raise NotAchievedException("Did not get matching EK2 and EK3 origins")
14648
14649
self.context_pop()
14650
14651
# restart GPS driver
14652
self.reboot_sitl()
14653
14654
def AHRSOriginRecorded(self):
14655
"""Test AHRS option to record and reuse origin"""
14656
self.context_push()
14657
14658
# Set AHRS_OPTIONS = 8 (UseRecordedOrigin)
14659
self.set_parameter('AHRS_OPTIONS', 8)
14660
self.set_parameter('LOG_DISARMED', 1)
14661
14662
# wait for vehicle to be ready to arm which means origin should have been written
14663
self.wait_ready_to_arm()
14664
14665
# confirm the AHRS_ORIG_LAT, LNG, ALT parameters are non-zero
14666
self.assert_parameter_value('AHRS_OPTIONS', 8)
14667
self.assert_parameter_value('AHRS_ORIGIN_LAT', -35, epsilon=1)
14668
self.assert_parameter_value('AHRS_ORIGIN_LON', 149, epsilon=1)
14669
self.assert_parameter_value('AHRS_ORIGIN_ALT', 584, epsilon=10)
14670
14671
# enable optical flow and rangefinder
14672
self.set_parameter("SIM_FLOW_ENABLE", 1)
14673
self.set_parameter("FLOW_TYPE", 10)
14674
self.set_analog_rangefinder_parameters()
14675
self.configure_EKFs_to_use_optical_flow_instead_of_GPS()
14676
self.reboot_sitl()
14677
14678
# wait for origin to be set
14679
self.wait_statustext("EKF3 IMU0 origin set")
14680
14681
# switch to Loiter, arm, takeoff and RTL
14682
self.change_mode("LOITER")
14683
self.wait_ready_to_arm(timeout=120, require_absolute=False, check_prearm_bit=True)
14684
self.arm_vehicle()
14685
self.takeoff(10, mode="LOITER")
14686
self.do_RTL()
14687
14688
# restore params to original values
14689
self.context_pop()
14690
14691
def ReadOnlyDefaults(self):
14692
'''test that defaults marked "readonly" can't be set'''
14693
defaults_filepath = tempfile.NamedTemporaryFile(mode='w', delete=False)
14694
defaults_filepath.write("""
14695
DISARM_DELAY 77 @READONLY
14696
RTL_ALT_M 123
14697
RTL_ALT_FINAL_M 129
14698
""")
14699
defaults_filepath.close()
14700
self.customise_SITL_commandline([
14701
], defaults_filepath=defaults_filepath.name)
14702
14703
self.context_collect('STATUSTEXT')
14704
self.send_set_parameter_direct("DISARM_DELAY", 88)
14705
14706
self.wait_statustext("Param write denied (DISARM_DELAY)")
14707
self.assert_parameter_value("DISARM_DELAY", 77)
14708
self.assert_parameter_value("RTL_ALT_M", 123)
14709
14710
self.start_subtest('Ensure something is writable....')
14711
self.set_parameter('RTL_ALT_FINAL_M', 101)
14712
14713
new_values_filepath = tempfile.NamedTemporaryFile(mode='w', delete=False)
14714
new_values_filepath.write("""
14715
DISARM_DELAY 99
14716
RTL_ALT_M 111
14717
""")
14718
new_values_filepath.close()
14719
14720
self.start_subtest("Ensure parameters can't be set via FTP either")
14721
mavproxy = self.start_mavproxy()
14722
# can't do two FTP things at once, so wait until parameters are received
14723
mavproxy.expect("Received .* parameters")
14724
self.mavproxy_load_module(mavproxy, 'ftp')
14725
mavproxy.send(f"param ftpload {new_values_filepath.name}\n")
14726
mavproxy.expect("Loaded")
14727
self.delay_sim_time(1)
14728
self.stop_mavproxy(mavproxy)
14729
14730
self.assert_parameter_value("DISARM_DELAY", 77)
14731
self.assert_parameter_value("RTL_ALT_M", 111)
14732
self.assert_parameter_value('RTL_ALT_FINAL_M', 101)
14733
14734
def ScriptingFlipMode(self):
14735
'''test adding custom mode from scripting'''
14736
# Really it would be nice to check for the AVAILABLE_MODES message, but pymavlink does not understand them yet.
14737
14738
# enable scripting and install flip script
14739
self.set_parameters({
14740
"SCR_ENABLE": 1,
14741
})
14742
self.install_example_script_context('Flip_Mode.lua')
14743
self.reboot_sitl()
14744
14745
# Takeoff in loiter
14746
self.takeoff(10, mode="LOITER")
14747
14748
# Try and switch to flip, should not be possible from loiter
14749
try:
14750
self.change_mode(100, timeout=10)
14751
except AutoTestTimeoutException:
14752
self.progress("PASS not able to enter from loiter")
14753
14754
# Should be alowd to enter from alt hold
14755
self.change_mode("ALT_HOLD")
14756
self.change_mode(100)
14757
14758
# Should return to previous mode after flipping
14759
self.wait_mode("ALT_HOLD")
14760
14761
# Test done
14762
self.land_and_disarm()
14763
14764
def ScriptingFlyVelocity(self):
14765
'''test flying a velocity vector using scripting'''
14766
14767
# enable scripting and install set-target-velocity script
14768
self.set_parameters({
14769
"SCR_ENABLE": 1,
14770
})
14771
self.install_example_script_context('set-target-velocity.lua')
14772
self.reboot_sitl()
14773
14774
# Should be allowed to enter from alt hold
14775
self.change_mode("ALT_HOLD")
14776
self.wait_ready_to_arm()
14777
self.arm_vehicle()
14778
self.set_rc(6, 2000)
14779
14780
# Should return to previous mode after flipping
14781
self.wait_mode("RTL", timeout=120)
14782
14783
# Test done
14784
self.land_and_disarm()
14785
14786
def RTLYaw(self):
14787
'''test that vehicle yaws to original heading on RTL'''
14788
# 0 is WP_YAW_BEHAVIOR_NONE
14789
# 1 is WP_YAW_BEHAVIOR_LOOK_AT_NEXT_WP
14790
# 2 is WP_YAW_BEHAVIOR_LOOK_AT_NEXT_WP_EXCEPT_RTL
14791
# 3 is WP_YAW_BEHAVIOR_LOOK_AHEAD
14792
for behaviour in 1, 3:
14793
self.set_parameters({
14794
'WP_YAW_BEHAVIOR': behaviour,
14795
})
14796
self.change_mode('GUIDED')
14797
original_heading = self.get_heading()
14798
target_heading = 100
14799
if original_heading - target_heading < 90:
14800
raise NotAchievedException("Bad initial heading")
14801
self.takeoff(10, mode='GUIDED')
14802
self.guided_achieve_heading(target_heading)
14803
self.change_mode('RTL')
14804
self.wait_heading(original_heading)
14805
self.wait_disarmed()
14806
14807
def CompassLearnCopyFromEKF(self):
14808
'''test compass learning whereby we copy learnt offsets from the EKF'''
14809
self.reboot_sitl()
14810
self.context_push()
14811
self.set_parameters({
14812
"SIM_MAG1_OFS_X": 1100,
14813
})
14814
self.assert_prearm_failure("Check mag field", other_prearm_failures_fatal=False)
14815
self.context_pop()
14816
self.wait_ready_to_arm()
14817
self.takeoff(30, mode='ALT_HOLD')
14818
# prevent EKF switching to good compass:
14819
self.set_parameters({
14820
'COMPASS_USE2': 0,
14821
'COMPASS_USE3': 0,
14822
})
14823
self.assert_parameter_value("COMPASS_OFS_X", 20, epsilon=30)
14824
# set the parameter so it gets reset at context pop time:
14825
self.set_parameter("COMPASS_OFS_X", 20)
14826
new_compass_ofs_x = 200
14827
self.set_parameters({
14828
"SIM_MAG1_OFS_X": new_compass_ofs_x,
14829
})
14830
self.set_parameter("COMPASS_LEARN", 2) # 2 is Copy-from-EKF
14831
14832
# commence silly flying to try to give the EKF as much
14833
# information as possible for it to converge its estimation;
14834
# there's a 5e-6 check before we consider the offsets good!
14835
self.set_rc(4, 1450)
14836
self.set_rc(1, 1450)
14837
for i in range(0, 5): # we descend through all of this:
14838
self.change_mode('LOITER')
14839
self.delay_sim_time(10)
14840
self.change_mode('ALT_HOLD')
14841
self.change_mode('FLIP')
14842
14843
self.set_parameter('ATC_ANGLE_MAX', 70)
14844
self.change_mode('ALT_HOLD')
14845
for j in 1000, 2000:
14846
for i in 1, 2, 4:
14847
self.set_rc(i, j)
14848
self.delay_sim_time(10)
14849
self.set_rc(1, 1500)
14850
self.set_rc(2, 1500)
14851
self.set_rc(4, 1500)
14852
14853
self.do_RTL()
14854
self.assert_parameter_value("COMPASS_OFS_X", new_compass_ofs_x, epsilon=30)
14855
self.reboot_sitl()
14856
self.assert_parameter_value("COMPASS_OFS_X", new_compass_ofs_x, epsilon=30)
14857
14858
def RudderDisarmMidair(self):
14859
'''check disarm behaviour mid-air'''
14860
self.change_mode('LOITER')
14861
self.wait_ready_to_arm()
14862
self.change_mode('STABILIZE')
14863
14864
# Set home current location, this gives a large home vs origin difference
14865
self.set_home(self.mav.location())
14866
14867
self.set_rc(4, 2000)
14868
self.wait_armed()
14869
self.set_rc(4, 1500)
14870
self.set_rc(3, 2000)
14871
self.wait_altitude(300, 20000, relative=True, timeout=10000)
14872
self.hover()
14873
self.set_rc(4, 1000)
14874
self.delay_sim_time(11)
14875
self.progress("Checking we are still armed")
14876
self.assert_armed()
14877
14878
self.set_rc(3, 2000)
14879
self.wait_altitude(300, 20000, relative=True)
14880
self.set_rc(3, 1000)
14881
self.set_rc(4, 1000)
14882
self.delay_sim_time(11)
14883
self.progress("Checking we disarm")
14884
self.wait_disarmed(timeout=5)
14885
self.set_rc(4, 1500)
14886
self.arm_vehicle()
14887
self.set_rc(3, 1600)
14888
self.delay_sim_time(10)
14889
14890
self.do_RTL(timeout=600)
14891
14892
def mission_NAV_LOITER_TURNS(self):
14893
'''test that loiter turns basically works'''
14894
self.upload_simple_relhome_mission([
14895
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
14896
self.create_MISSION_ITEM_INT(
14897
mavutil.mavlink.MAV_CMD_NAV_LOITER_TURNS,
14898
p1=1,
14899
p3=30,
14900
z=30, # circle is 10m higher than takeoff
14901
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
14902
),
14903
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
14904
])
14905
self.change_mode('AUTO')
14906
self.set_parameter('AUTO_OPTIONS', 3)
14907
self.wait_ready_to_arm()
14908
self.arm_vehicle()
14909
self.wait_disarmed()
14910
14911
def mission_NAV_LOITER_TURNS_off_center(self):
14912
'''test that loiter turns basically works - copter on edge of circle'''
14913
self.start_subtest("Start circle when on edge of circle")
14914
radius = 30
14915
self.wait_ready_to_arm()
14916
here = self.mav.location()
14917
circle_centre_loc = self.offset_location_ne(here, radius, 0)
14918
self.upload_simple_relhome_mission([
14919
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
14920
self.create_MISSION_ITEM_INT(
14921
mavutil.mavlink.MAV_CMD_NAV_LOITER_TURNS,
14922
p1=1,
14923
p3=radius,
14924
x=int(circle_centre_loc.lat*1e7),
14925
y=int(circle_centre_loc.lng*1e7),
14926
z=30, # circle is 10m higher than takeoff
14927
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
14928
),
14929
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
14930
])
14931
self.change_mode('AUTO')
14932
self.set_parameter('AUTO_OPTIONS', 3)
14933
self.wait_ready_to_arm()
14934
self.arm_vehicle()
14935
self.wait_disarmed()
14936
14937
def AHRSAutoTrim(self):
14938
'''calibrate AHRS trim using RC input'''
14939
self.progress("Making earth frame same as body frame") # because I'm lazy
14940
self.takeoff(5, mode='GUIDED')
14941
self.guided_achieve_heading(0)
14942
self.do_land()
14943
14944
self.set_parameters({
14945
'RC9_OPTION': 182,
14946
})
14947
14948
param_last_check_time = 0
14949
for mode in ['STABILIZE', 'ALT_HOLD']:
14950
self.set_parameters({
14951
'AHRS_TRIM_X': 0.1,
14952
'AHRS_TRIM_Y': -0.1,
14953
})
14954
self.takeoff(mode=mode)
14955
self.set_rc(9, 2000)
14956
tstart = self.get_sim_time()
14957
while True:
14958
now = self.get_sim_time_cached()
14959
if now - tstart > 30:
14960
raise ValueError(f"Failed to reduce trims in {mode}!")
14961
lpn = self.assert_receive_message('LOCAL_POSITION_NED')
14962
delta = 40
14963
roll_input = 1500
14964
if lpn.vx > 0:
14965
roll_input -= delta
14966
elif lpn.vx < 0:
14967
roll_input += delta
14968
14969
pitch_input = 1500
14970
if lpn.vy > 0:
14971
pitch_input += delta
14972
elif lpn.vy < 0:
14973
pitch_input -= delta
14974
self.set_rc_from_map({
14975
1: roll_input,
14976
2: pitch_input,
14977
}, quiet=True)
14978
14979
# check parameters once/second:
14980
if now - param_last_check_time > 1:
14981
param_last_check_time = now
14982
trim_x = self.get_parameter('AHRS_TRIM_X', verbose=False)
14983
trim_y = self.get_parameter('AHRS_TRIM_Y', verbose=False)
14984
self.progress(f"trim_x={trim_x} trim_y={trim_y}")
14985
if abs(trim_x) < 0.01 and abs(trim_y) < 0.01:
14986
self.progress("Good AHRS trims")
14987
self.progress(f"vx={lpn.vx} vy={lpn.vy}")
14988
if abs(lpn.vx) > 1 or abs(lpn.vy) > 1:
14989
raise NotAchievedException("Velocity after trimming?!")
14990
break
14991
self.context_collect('STATUSTEXT')
14992
self.set_rc(9, 1000)
14993
self.wait_statustext('Trim saved', check_context=True)
14994
self.context_stop_collecting('STATUSTEXT')
14995
self.do_land()
14996
self.set_rc_default()
14997
14998
self.progress("Landing should cancel the autotrim")
14999
self.takeoff(10, mode='STABILIZE')
15000
self.context_collect('STATUSTEXT')
15001
self.set_rc(9, 2000)
15002
self.wait_statustext('AutoTrim running', check_context=True)
15003
self.do_land()
15004
self.wait_statustext('AutoTrim cancelled', check_context=True)
15005
self.set_rc(9, 1000)
15006
15007
self.progress("Changing mode to LOITER")
15008
self.takeoff(10, mode='STABILIZE')
15009
self.context_collect('STATUSTEXT')
15010
self.set_rc(9, 2000)
15011
self.wait_statustext('AutoTrim running', check_context=True)
15012
self.change_mode('LOITER')
15013
self.wait_statustext('AutoTrim cancelled', check_context=True)
15014
self.do_land()
15015
self.set_rc(9, 1000)
15016
15017
def RTLStoppingDistanceSpeed(self):
15018
'''test stopping distance unaffected by RTL speed'''
15019
self.upload_simple_relhome_mission([
15020
# N E U
15021
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
15022
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 200, 0, 20),
15023
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
15024
])
15025
self.set_parameters({
15026
"AUTO_OPTIONS": 3,
15027
})
15028
self.context_push()
15029
self.set_parameters({
15030
'RTL_SPEED_MS': 1, # m/s
15031
})
15032
15033
self.change_mode('AUTO')
15034
self.wait_ready_to_arm()
15035
self.arm_vehicle()
15036
self.wait_current_waypoint(2)
15037
15038
self.progress("Waiting for vehicle to get up to speed")
15039
wpnav_speed = self.get_parameter('WPNAV_SPEED')
15040
self.wait_groundspeed(wpnav_speed/100-0.1, wpnav_speed/100+0.1)
15041
15042
rtl_start_pos = self.get_local_position_NED()
15043
15044
# we accelerate so hard we can miss the zero value!
15045
self.context_set_message_rate_hz('VFR_HUD', 50)
15046
15047
# note that there's going to be a significant latency involved
15048
# between taking rtl_start_pos and entering RTL, but we're
15049
# really interested in deviation to the east/west, and we are
15050
# travelling due North, ...
15051
self.change_mode('RTL')
15052
15053
self.progress("Waiting for vehicle to stop")
15054
self.wait_groundspeed(-0.3, 0.3)
15055
rtl_stopping_point_pos = self.get_local_position_NED()
15056
15057
self.progress("Checking vehicle deviation from track")
15058
y_delta = abs(rtl_start_pos.y - rtl_stopping_point_pos.y)
15059
self.progress(f"deviated {y_delta}m from track")
15060
if y_delta > 0.2:
15061
raise NotAchievedException(f"RTL deviated from track {y_delta}m")
15062
self.context_pop()
15063
self.change_mode('LOITER')
15064
self.do_RTL()
15065
15066
def ModeAllowsEntryWhenNoPilotInput(self):
15067
'''test we can't enter modes when no RC input'''
15068
self.upload_simple_relhome_mission([
15069
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
15070
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 2000, 0, 20),
15071
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
15072
])
15073
self.set_parameters({
15074
"AUTO_OPTIONS": 3,
15075
"FS_THR_ENABLE": 4, # continue in auto
15076
})
15077
self.change_mode('AUTO')
15078
self.wait_ready_to_arm()
15079
self.arm_vehicle()
15080
self.wait_current_waypoint(2)
15081
self.context_collect('STATUSTEXT')
15082
self.set_rc(3, 200)
15083
self.wait_statustext('Radio Failsafe', check_context=True)
15084
self.send_cmd_do_set_mode('STABILIZE')
15085
self.wait_statustext('in RC failsafe', check_context=True)
15086
self.hover()
15087
self.wait_statustext('Radio Failsafe Cleared', check_context=True)
15088
self.do_RTL()
15089
15090
def RCOverridesNoRCReceiver(self):
15091
'''test RC override timeout with no RC receiver present'''
15092
self.set_parameters({
15093
"MAV_GCS_SYSID": 250,
15094
"SIM_RC_FAIL": 1, # no-pulses
15095
})
15096
self.reboot_sitl()
15097
15098
rc_send_enabled = True
15099
rc1_value = 1500
15100
rc2_value = 1500
15101
rc3_value = 1000
15102
rc4_value = 1500
15103
15104
# register a hook which sends rcn_values as overrides
15105
def rc_override_sender(mav, message):
15106
if message.get_type() == 'ATTITUDE':
15107
if not rc_send_enabled:
15108
return
15109
self.mav.mav.rc_channels_override_send(
15110
mav.target_system, # target system
15111
1, # targe component
15112
rc1_value, # chan1_raw
15113
rc2_value, # chan2_raw
15114
rc3_value, # chan3_raw
15115
rc4_value, # chan4_raw
15116
65535, # chan5_raw
15117
65535, # chan6_raw
15118
65535, # chan7_raw
15119
65535 # chan8_raw
15120
)
15121
self.install_message_hook_context(rc_override_sender)
15122
self.do_set_mode_via_command_int('LOITER')
15123
self.wait_ready_to_arm()
15124
self.arm_vehicle()
15125
self.progress("Takeoff throttle")
15126
rc3_value = 1800
15127
self.delay_sim_time(1)
15128
self.wait_altitude(20, 30, relative=True)
15129
self.progress("Neutral throttle")
15130
rc3_value = 1600
15131
self.progress("yaw ccw")
15132
rc4_value = 1450
15133
self.wait_yaw_speed(math.radians(-30), accuracy=5, minimum_duration=10)
15134
self.progress("Kill overrides and wait for speed to get neutralised")
15135
rc_send_enabled = False
15136
self.wait_yaw_speed(0, 1, minimum_duration=10)
15137
self.do_set_mode_via_command_int('RTL')
15138
self.wait_disarmed()
15139
15140
def MISSION_OPTION_CLEAR_MISSION_AT_BOOT(self):
15141
'''check functionality of mission-clear-at-boot option'''
15142
self.upload_simple_relhome_mission([
15143
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
15144
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 20),
15145
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
15146
])
15147
if len(self.download_using_mission_protocol(mavutil.mavlink.MAV_MISSION_TYPE_MISSION)) != 4:
15148
raise NotAchievedException("No mission after upload?!")
15149
15150
self.reboot_sitl()
15151
if len(self.download_using_mission_protocol(mavutil.mavlink.MAV_MISSION_TYPE_MISSION)) != 4:
15152
raise NotAchievedException("No mission after reboot?!")
15153
15154
self.set_parameters({
15155
"MIS_OPTIONS": 1,
15156
})
15157
self.reboot_sitl()
15158
15159
if len(self.download_using_mission_protocol(mavutil.mavlink.MAV_MISSION_TYPE_MISSION)) != 0:
15160
raise NotAchievedException("Mission did not clear after reboot with option set")
15161
15162
def IgnorePilotYaw(self):
15163
'''test RTL_OPTIONS bit 2, "IgnorePilotYaw"'''
15164
self.takeoff(10, mode='GUIDED')
15165
self.fly_guided_move_local(10, 10, 10, timeout=100)
15166
15167
orig_yaw = self.assert_receive_message('VFR_HUD').heading
15168
15169
def ensure_heading_stays_constant(mav, m):
15170
print(m.get_type())
15171
if m.get_type() != "VFR_HUD":
15172
return
15173
new_yaw = m.heading
15174
if abs(orig_yaw - new_yaw) > 15:
15175
raise NotAchievedException(f"Vehicle yawed! ({orig_yaw=} {new_yaw=}")
15176
15177
self.context_push()
15178
self.install_message_hook_context(ensure_heading_stays_constant)
15179
15180
self.change_mode('RTL')
15181
15182
self.set_parameter('RTL_OPTIONS', 1 << 2)
15183
self.set_rc(4, 2000)
15184
self.delay_sim_time(5)
15185
self.context_pop()
15186
15187
self.wait_disarmed()
15188
15189
def PLDNoParameters(self):
15190
''''try enabling PLD with lat/lng/alt all zero'''
15191
self.set_parameter('SIM_PLD_LAT', 0)
15192
self.set_parameter('SIM_PLD_LON', 0)
15193
self.set_parameter('SIM_PLD_HEIGHT', 0)
15194
self.set_parameter('SIM_PLD_ENABLE', 1)
15195
self.wait_statustext('Set SIM_PLD_LAT, SIM_PLD_LAT and SIM_PLD_ALT')
15196
15197
def NoRC(self):
15198
'''check what happens if we fly with no RC'''
15199
self.set_parameters({
15200
"SIM_RC_FAIL": 1,
15201
"FS_THR_ENABLE": 0,
15202
"AUTO_OPTIONS": 3,
15203
})
15204
self.upload_simple_relhome_mission([
15205
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
15206
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
15207
])
15208
self.reboot_sitl()
15209
15210
# add a listener that verifies yaw rate remains zero:
15211
def verify_yaw(mav, m):
15212
if m.get_type() != 'ATTITUDE':
15213
return
15214
thresh_degs = 10
15215
current_degs = math.degrees(abs(m.yawspeed))
15216
if current_degs > thresh_degs:
15217
raise NotAchievedException(
15218
f"Excessive yaw: {current_degs} deg/s > {thresh_degs} deg/s"
15219
)
15220
self.install_message_hook_context(verify_yaw)
15221
15222
self.change_mode('AUTO')
15223
self.wait_ready_to_arm()
15224
self.arm_vehicle()
15225
self.wait_disarmed()
15226
15227
def WaitAndMaintainAttitude_RCFlight(self):
15228
'''just test WaitAndMaintainAttitude works'''
15229
WaitAndMaintainAttitude(self, 0, 0, epsilon=1).run()
15230
self.takeoff(20, mode='LOITER')
15231
15232
WaitAndMaintainAttitude(self, 0, 0, epsilon=1).run()
15233
15234
self.start_subtest("Full forward stick")
15235
self.set_rc(2, 1000)
15236
WaitAndMaintainAttitude(self, 0, -30, epsilon=1, minimum_duration=10, timeout=60).run()
15237
15238
self.start_subtest("Full left stick")
15239
self.set_rc(2, 1500)
15240
self.set_rc(1, 1000)
15241
WaitAndMaintainAttitude(self, -30, 0, epsilon=1).run()
15242
15243
self.start_subtest("Circular angle")
15244
self.set_rc(2, 1000)
15245
self.set_rc(1, 1000)
15246
# this is some sort of circular-angle shenanigans / frame
15247
# translation weirdness:
15248
WaitAndMaintainAttitude(self, -20, -23, epsilon=1, timeout=120).run()
15249
15250
self.start_subtest("Center the sticks")
15251
self.set_rc(2, 1500)
15252
self.set_rc(1, 1500)
15253
WaitAndMaintainAttitude(self, 0, 0, epsilon=1).run()
15254
15255
self.do_RTL()
15256
15257
def LuaParamSet(self):
15258
'''test param-set.lua applet'''
15259
self.set_parameters({
15260
'SCR_ENABLE': 1,
15261
})
15262
15263
self.context_push()
15264
15265
self.install_applet_script_context("param-set.lua")
15266
self.reboot_sitl()
15267
15268
# collect original parameter values for everything other than
15269
# SCR_ENABLE and PARAM_SET_ENABLE. This resolves an issue at
15270
# the end of the test where the popped context wants to set
15271
# PARAM_SET_ENABLE back to its original value (enabled!),
15272
# which can cause problems resetting
15273
orig_parameter_values = self.get_parameters([
15274
'RTL_ALT_M',
15275
'DISARM_DELAY',
15276
])
15277
15278
self.wait_ready_to_arm() # scripts will be ready by now!
15279
self.start_subtest("set RTL_ALT_M freely")
15280
self.context_collect("STATUSTEXT")
15281
self.set_parameter("RTL_ALT_M", 0.23)
15282
self.set_parameter("RTL_ALT_M", 0.28)
15283
# Ensure there are no scripting errors.
15284
error = self.statustext_in_collections("Internal Error")
15285
if error is not None:
15286
raise NotAchievedException("Script errored out in positive test.")
15287
self.context_stop_collecting("STATUSTEXT")
15288
15289
self.start_subtest("Unable to set DISARM_DELAY freely")
15290
self.context_push()
15291
self.context_collect('STATUSTEXT')
15292
self.context_collect('PARAM_ERROR')
15293
old_disarm_delay_value = self.get_parameter('DISARM_DELAY')
15294
self.send_set_parameter_direct('DISARM_DELAY', 78)
15295
self.wait_statustext('param-set: param set denied (DISARM_DELAY)', check_context=True)
15296
self.assert_received_message_field_values('PARAM_ERROR', {
15297
"target_system": 250,
15298
"target_component": 250,
15299
"param_id": 'DISARM_DELAY',
15300
"param_index": -1,
15301
"error": mavutil.mavlink.MAV_PARAM_ERROR_PERMISSION_DENIED,
15302
}, check_context=True, very_verbose=True)
15303
self.assert_parameter_value('DISARM_DELAY', old_disarm_delay_value)
15304
self.context_pop()
15305
15306
self.start_subtest("Disabling applet via parameter should allow freely setting DISARM_DELAY")
15307
self.set_parameter("PARAM_SET_ENABLE", 0)
15308
self.set_parameter("DISARM_DELAY", 56)
15309
15310
self.start_subtest("Re-enabling applet via parameter should stop freely setting DISARM_DELAY")
15311
self.context_push()
15312
self.context_collect('STATUSTEXT')
15313
self.context_collect('PARAM_ERROR')
15314
self.set_parameter("PARAM_SET_ENABLE", 1)
15315
old_disarm_delay_value = self.get_parameter('DISARM_DELAY')
15316
self.send_set_parameter_direct('DISARM_DELAY', 78)
15317
self.wait_statustext('param-set: param set denied (DISARM_DELAY)', check_context=True)
15318
self.assert_received_message_field_values('PARAM_ERROR', {
15319
"target_system": 250,
15320
"target_component": 250,
15321
"param_id": 'DISARM_DELAY',
15322
"param_index": -1,
15323
"error": mavutil.mavlink.MAV_PARAM_ERROR_PERMISSION_DENIED,
15324
}, check_context=True, very_verbose=True)
15325
self.assert_parameter_value('DISARM_DELAY', old_disarm_delay_value)
15326
self.context_pop()
15327
15328
self.start_subtest("Ensure that parameter values are persistent")
15329
self.set_parameter('DISARM_DELAY', 111)
15330
self.reboot_sitl()
15331
self.assert_parameter_value('DISARM_DELAY', 111)
15332
15333
# set parameters to their original value so the following
15334
# context pop should only need to set PARAM_SET_ENABLE back to
15335
# its default value of 1. That prevents permission errors
15336
# created by param-set.lua!
15337
self.set_parameters(orig_parameter_values)
15338
15339
self.context_pop()
15340
15341
def do_land(self):
15342
self.change_mode('LAND')
15343
self.wait_disarmed()
15344
15345
def PeriphMultiUARTTunnel(self):
15346
'''test peripheral multi-uart tunneling'''
15347
15348
self.progress("Building Periph")
15349
periph_board = 'sitl_periph_can_to_serial'
15350
periph_builddir = util.reltopdir('build-periph')
15351
util.build_SITL(
15352
'bin/AP_Periph',
15353
board=periph_board,
15354
clean=False,
15355
configure=True,
15356
debug=True,
15357
extra_configure_args=[
15358
'--out', periph_builddir,
15359
],
15360
# extra_defines={
15361
# },
15362
)
15363
15364
binary_path = ""
15365
15366
self.progress("Starting Periph simulation")
15367
self.context_push()
15368
original_speedup = self.speedup # FIXME: do this better
15369
self.speedup = 1
15370
periph_exp = None
15371
ex = None
15372
try:
15373
binary_path = pathlib.Path(periph_builddir, periph_board, 'bin', 'AP_Periph')
15374
periph_rundir = util.reltopdir('run-periph')
15375
if not os.path.exists(periph_rundir):
15376
os.mkdir(periph_rundir)
15377
periph_exp = util.start_SITL(
15378
binary_path,
15379
cwd=periph_rundir,
15380
stdout_prefix="periph",
15381
gdb=self.gdb,
15382
valgrind=self.valgrind,
15383
customisations=[
15384
'-I', str(1),
15385
'--serial0', 'mcast:',
15386
'--serial1', 'tcp:2',
15387
'--serial2', 'tcp:3',
15388
],
15389
speedup=self.speedup
15390
)
15391
self.expect_list_add(periph_exp)
15392
15393
self.progress("Reconfiguring for multicast")
15394
self.customise_SITL_commandline([
15395
"--serial5=mcast:",
15396
],
15397
model="octa-quad:@ROMFS/models/Callisto.json",
15398
defaults_filepath=self.model_defaults_filepath('Callisto'),
15399
wipe=True,
15400
)
15401
15402
self.set_parameters({
15403
'SERIAL5_PROTOCOL': 2,
15404
"CAN_P1_DRIVER": 1, # needed for multicast state!
15405
})
15406
self.reboot_sitl()
15407
self.set_parameters({
15408
"CAN_D1_UC_SER_EN": 1, # enable serial
15409
"CAN_D1_UC_S1_IDX": 1, # serial port number on CAN device
15410
"CAN_D1_UC_S1_NOD": 125, # FIXME: set this explicitly
15411
"CAN_D1_UC_S1_PRO": 2, # protocol to set on remote node
15412
15413
"CAN_D1_UC_S2_IDX": 2, # serial port number on CAN device
15414
"CAN_D1_UC_S2_NOD": 125, # FIXME: set this explicitly
15415
"CAN_D1_UC_S2_PRO": 2, # protocol to set on remote node
15416
})
15417
15418
self.reboot_sitl()
15419
15420
# uncomment this if you just want the test scenario set up for you:
15421
# self.delay_sim_time(100000)
15422
15423
self.progress("Connect to the serial port on the peripheral, which should be talking mavlink")
15424
self.drain_mav()
15425
mav2 = mavutil.mavlink_connection(
15426
"tcp:localhost:5772",
15427
robust_parsing=True,
15428
source_system=9,
15429
source_component=9,
15430
)
15431
self.assert_receive_message("HEARTBEAT", mav=mav2, very_verbose=True, timeout=5)
15432
self.drain_mav()
15433
15434
self.progress("Connect to the other serial port on the peripheral, which should also be talking mavlink")
15435
self.drain_mav()
15436
mav3 = mavutil.mavlink_connection(
15437
"tcp:localhost:5773",
15438
robust_parsing=True,
15439
source_system=10,
15440
source_component=10,
15441
)
15442
self.assert_receive_message("HEARTBEAT", mav=mav3, very_verbose=True, timeout=5)
15443
self.drain_mav()
15444
15445
# make sure we continue to get heartbeats:
15446
tstart = self.get_sim_time()
15447
while True:
15448
now = self.get_sim_time()
15449
if now - tstart > 30:
15450
break
15451
self.assert_receive_message('HEARTBEAT', mav=mav2, verbose=2)
15452
self.drain_mav()
15453
self.assert_receive_message('HEARTBEAT', mav=mav3, verbose=2)
15454
self.drain_mav()
15455
15456
except Exception as e:
15457
self.print_exception_caught(e)
15458
ex = e
15459
finally:
15460
if periph_exp is not None:
15461
self.progress("Stopping Periph")
15462
self.expect_list_remove(periph_exp)
15463
util.pexpect_close(periph_exp)
15464
self.speedup = original_speedup
15465
self.context_pop()
15466
self.reboot_sitl()
15467
if ex is not None:
15468
raise ex
15469
15470
def RCProtocolFailsafe(self):
15471
'''ensure we failsafe when the RC protocol failsafe is set'''
15472
self.takeoff(10, mode='LOITER')
15473
self.set_parameter('SIM_RC_FAIL', 3) # protocol failsafe
15474
self.wait_mode('RTL')
15475
self.wait_disarmed()
15476
15477
def LUAConfigProfile(self):
15478
'''test the config_profiles.lua example script'''
15479
self.customise_SITL_commandline(
15480
[],
15481
defaults_filepath=self.model_defaults_filepath('Callisto'),
15482
model="octa-quad:@ROMFS/models/Callisto.json",
15483
wipe=True,
15484
)
15485
self.install_example_script_context("config_profiles.lua")
15486
self.set_parameters({
15487
'SCR_ENABLE': 1,
15488
})
15489
self.context_push()
15490
15491
self.context_push()
15492
self.context_collect('STATUSTEXT')
15493
self.reboot_sitl()
15494
self.assert_prearm_failure('CFG: Reboot needed for config change', other_prearm_failures_fatal=False)
15495
self.context_pop()
15496
self.reboot_sitl()
15497
self.wait_ready_to_arm()
15498
15499
self.set_parameter("CFG_PAY_SEL", 1)
15500
self.wait_parameter_values({
15501
"GRIP_ENABLE": 1,
15502
"RC8_OPTION": 19,
15503
})
15504
self.set_parameter("CFG_PAY_SEL", 3) # avt gimbal
15505
self.wait_parameter_values({
15506
"GRIP_ENABLE": 0,
15507
"RC8_OPTION": 0,
15508
})
15509
self.set_parameter("CFG_PAY_SEL", 2) # hook slung
15510
self.wait_parameter_values({
15511
"GRIP_ENABLE": 1,
15512
"RC8_OPTION": 19,
15513
})
15514
self.progress("Apply default parameters")
15515
self.set_parameter("CFG_PAY_SEL", 0)
15516
self.wait_parameter_values({
15517
"GRIP_ENABLE": 0,
15518
"RC8_OPTION": 0,
15519
})
15520
15521
self.set_parameter("CFG_PAY_SEL", 22)
15522
self.wait_statustext("Invalid profile selected for PAY")
15523
15524
self.start_subtest("Test the different modes (nothing / apply / defaults)")
15525
15526
self.context_pop()
15527
15528
self.start_subtest("Testing of different validations")
15529
15530
def set_config_profiles_config_domains(new_domains):
15531
path = pathlib.Path(self.installed_script_path("config_profiles.lua"))
15532
content = path.read_text()
15533
15534
start_marker = '-- This is a marker for the start of the config_domains; it is used to swap these out for CI testing' # noqa: E501
15535
end_marker = '-- This is a marker for the end of the config_domains; it is used to swap these out for CI testing' # noqa: E501
15536
pattern = re.compile(
15537
rf"({re.escape(start_marker)})(.*?){re.escape(end_marker)}",
15538
re.DOTALL
15539
)
15540
15541
if not pattern.search(content):
15542
raise ValueError("Start or end marker not found in file")
15543
15544
replaced = pattern.sub(rf"\1\n{new_domains}\n{end_marker}", content)
15545
path.write_text(replaced)
15546
15547
def assert_prearm_failure_for_domains_string(domains_string, prearm_failure_reason):
15548
self.start_subtest(f"Testing domains for ({prearm_failure_reason})")
15549
set_config_profiles_config_domains(domains_string)
15550
self.context_push()
15551
self.context_collect('STATUSTEXT')
15552
self.reboot_sitl() # can't just restart scripting as we reuse indexes for different parameters
15553
# self.wait_statustext('config_profiles .* starting', regex=True, check_context=True)
15554
self.assert_prearm_failure(prearm_failure_reason, other_prearm_failures_fatal=False, timeout=120)
15555
self.context_pop()
15556
15557
domain_param_not_in_defaults = """
15558
-- a table of param indexes to help avoid annoying conflicts:
15559
local param_index = {
15560
["enable"] = 1,
15561
["pm_filter"] = 2,
15562
15563
["DMN_A"] = 10,
15564
}
15565
15566
local config_domains = {
15567
DOMAIN_A = {
15568
param_name = "DMN_A",
15569
param_sel_index = 3,
15570
all_param_defaults = { -- all parameters present in the params for each option
15571
},
15572
profiles = {
15573
[1] = {
15574
name = "Param Not in defaults",
15575
params = {
15576
["ACRO_BAL_PITCH"] = 1,
15577
},
15578
},
15579
},
15580
}
15581
}
15582
"""
15583
assert_prearm_failure_for_domains_string(domain_param_not_in_defaults, "Not in defaults")
15584
15585
domain_param_not_in_defaults = """
15586
local param_index = {
15587
["enable"] = 1,
15588
["pm_filter"] = 2,
15589
15590
["DMN_A"] = 10,
15591
["DMN_B"] = 11,
15592
}
15593
15594
local config_domains = {
15595
DOMAIN_A = {
15596
param_name = "DMN_A",
15597
param_sel_index = 5,
15598
all_param_defaults = { -- all parameters present in the params for each option
15599
["ACRO_BAL_PITCH"] = 1,
15600
},
15601
profiles = {
15602
[1] = {
15603
name = "exists in two domains",
15604
params = {
15605
["ACRO_BAL_PITCH"] = 1,
15606
},
15607
},
15608
},
15609
},
15610
DOMAIN_B = {
15611
param_name = "DMN_B",
15612
param_sel_index = 3,
15613
all_param_defaults = { -- all parameters present in the params for each option
15614
["ACRO_BAL_PITCH"] = 1,
15615
},
15616
profiles = {
15617
[1] = {
15618
name = "param in two domains",
15619
params = {
15620
["ACRO_BAL_PITCH"] = 1,
15621
},
15622
},
15623
},
15624
},
15625
}
15626
15627
"""
15628
assert_prearm_failure_for_domains_string(domain_param_not_in_defaults, "CFG: ACRO_BAL_PITCH exists in two")
15629
15630
domain_profile_param_same_as_domain_default_param_value = """
15631
local param_index = {
15632
["enable"] = 1,
15633
["pm_filter"] = 2,
15634
15635
["DMN_A"] = 10,
15636
}
15637
15638
local config_domains = {
15639
DOMAIN_A = {
15640
param_name = "DMN_A",
15641
param_sel_index = 5,
15642
all_param_defaults = { -- all parameters present in the params for each option
15643
["BATT_CAPACITY"] = 6,
15644
},
15645
profiles = {
15646
[1] = {
15647
name = "param value same as default",
15648
params = {
15649
["BATT_CAPACITY"] = 6,
15650
},
15651
},
15652
},
15653
},
15654
}
15655
15656
"""
15657
assert_prearm_failure_for_domains_string(
15658
domain_profile_param_same_as_domain_default_param_value,
15659
"has the same value as"
15660
)
15661
15662
domain_profile_param_must_be_set_when_mode_is_apply_defaults = """
15663
local param_index = {
15664
["enable"] = 1,
15665
["pm_filter"] = 2,
15666
15667
["DMN_A"] = 10,
15668
}
15669
15670
local config_domains = {
15671
DOMAIN_A = {
15672
param_name = "DMN_A",
15673
param_sel_index = 0,
15674
all_param_defaults = { -- all parameters present in the params for each option
15675
["BATT_CAPACITY"] = must_be_set,
15676
},
15677
profiles = {
15678
[1] = {
15679
name = "param must be set",
15680
params = {
15681
["BATT_CAPACITY"] = 6,
15682
},
15683
},
15684
},
15685
},
15686
}
15687
15688
"""
15689
assert_prearm_failure_for_domains_string(
15690
domain_profile_param_must_be_set_when_mode_is_apply_defaults,
15691
"BATT_CAPACITY in DMN_A is must-be-set but al"
15692
)
15693
15694
def ScriptParamRegistration(self):
15695
'''test parameter script registration'''
15696
self.set_parameters({
15697
'SCR_ENABLE': 1,
15698
})
15699
script_content = """
15700
local PARAM_TABLE_KEY = 10
15701
local PARAM_TABLE_PREFIX = "TST_"
15702
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 16), 'could not add param table')
15703
15704
-- add a parameter and bind it to a variable
15705
function bind_add_param(name, idx, default_value)
15706
assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', PARAM_TABLE_PREFIX .. name))
15707
return Parameter(PARAM_TABLE_PREFIX .. name)
15708
end
15709
15710
local PARAM_A = bind_add_param("A", 5, 22)
15711
local PARAM_B = bind_add_param("B", 1, 33)
15712
15713
function update()
15714
gcs:send_text(3, string.format("test script running"))
15715
return update, 1000
15716
end
15717
15718
return update, 1000
15719
""" # noqa: E501
15720
self.install_script_content_context("test.lua", script_content)
15721
self.reboot_sitl()
15722
self.wait_statustext('test script running')
15723
self.assert_parameter_value('TST_A', 22)
15724
self.assert_parameter_value('TST_B', 33)
15725
15726
all_params = self.fetch_all_parameters()
15727
for pname in "TST_A", "TST_B":
15728
if pname not in all_params:
15729
raise ValueError(f"{pname} not in fetched-all-parameters")
15730
15731
self.start_subtest("Remove parameter at runtime")
15732
script_content = """
15733
local PARAM_TABLE_KEY = 10
15734
local PARAM_TABLE_PREFIX = "TST_"
15735
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 16), 'could not add param table')
15736
15737
-- add a parameter and bind it to a variable
15738
function bind_add_param(name, idx, default_value)
15739
assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', PARAM_TABLE_PREFIX .. name))
15740
return Parameter(PARAM_TABLE_PREFIX .. name)
15741
end
15742
15743
local PARAM_B = bind_add_param("B", 1, 33)
15744
15745
function update()
15746
gcs:send_text(3, string.format("test script running"))
15747
return update, 1000
15748
end
15749
15750
return update, 1000
15751
""" # noqa: E501
15752
self.install_script_content_context("test.lua", script_content)
15753
self.scripting_restart()
15754
self.wait_statustext('restart')
15755
self.wait_statustext('test script running')
15756
self.assert_parameter_value('TST_B', 33)
15757
15758
all_params = self.fetch_all_parameters()
15759
for pname in ["TST_B"]:
15760
if pname not in all_params:
15761
raise ValueError(f"{pname} not in fetched-all-parameters")
15762
for pname in ["TST_A"]:
15763
if pname in all_params:
15764
raise ValueError(f"{pname} in fetched-all-parameters when it should have gone away")
15765
15766
def tests2b(self): # this block currently around 9.5mins here
15767
'''return list of all tests'''
15768
ret = ([
15769
self.MotorVibration,
15770
Test(self.DynamicNotches, attempts=4),
15771
self.PositionWhenGPSIsZero,
15772
self.DynamicRpmNotches, # Do not add attempts to this - failure is sign of a bug
15773
self.DynamicRpmNotchesRateThread,
15774
self.PIDNotches,
15775
self.mission_NAV_LOITER_TURNS,
15776
self.mission_NAV_LOITER_TURNS_off_center,
15777
self.StaticNotches,
15778
self.LuaParamSet,
15779
self.RefindGPS,
15780
Test(self.GyroFFT, attempts=1, speedup=8),
15781
Test(self.GyroFFTHarmonic, attempts=4, speedup=8),
15782
Test(self.GyroFFTAverage, attempts=1, speedup=8),
15783
Test(self.GyroFFTContinuousAveraging, attempts=4, speedup=8),
15784
self.WPYawBehaviour1RTL,
15785
self.GyroFFTPostFilter,
15786
self.GyroFFTMotorNoiseCheck,
15787
self.CompassReordering,
15788
self.SixCompassCalibrationAndReordering,
15789
self.CRSF,
15790
self.MotorTest,
15791
self.AltEstimation,
15792
self.EKFSource,
15793
self.GSF,
15794
self.GSF_reset,
15795
self.AP_Avoidance,
15796
self.RTL_ALT_FINAL_M,
15797
self.SMART_RTL,
15798
self.SMART_RTL_EnterLeave,
15799
self.SMART_RTL_Repeat,
15800
self.RTL_TO_RALLY,
15801
self.RTLYaw,
15802
self.FlyEachFrame,
15803
self.ScriptParamRegistration,
15804
self.GPSBlending,
15805
self.GPSWeightedBlending,
15806
self.GPSBlendingLog,
15807
self.GPSBlendingAffinity,
15808
self.DataFlash,
15809
Test(self.DataFlashErase, attempts=8),
15810
self.Callisto,
15811
self.PerfInfo,
15812
self.ModeAllowsEntryWhenNoPilotInput,
15813
self.Replay,
15814
self.FETtecESC,
15815
self.ProximitySensors,
15816
self.GroundEffectCompensation_touchDownExpected,
15817
self.GroundEffectCompensation_takeOffExpected,
15818
self.DO_CHANGE_SPEED,
15819
self.MISSION_START,
15820
self.AUTO_LAND_TO_BRAKE,
15821
self.WPNAV_SPEED,
15822
self.RTLStoppingDistanceSpeed,
15823
self.WPNAV_SPEED_UP,
15824
self.WPNAV_SPEED_DN,
15825
self.DO_WINCH,
15826
self.SensorErrorFlags,
15827
self.GPSForYaw,
15828
self.DefaultIntervalsFromFiles,
15829
self.GPSTypes,
15830
self.MultipleGPS,
15831
self.WatchAlts,
15832
self.GuidedEKFLaneChange,
15833
self.Sprayer,
15834
self.AutoContinueOnRCFailsafe,
15835
self.EK3_RNG_USE_HGT,
15836
self.NoRC,
15837
self.RCOverridesNoRCReceiver,
15838
self.TerrainDBPreArm,
15839
self.ThrottleGainBoost,
15840
self.ScriptMountPOI,
15841
self.ScriptMountAllModes,
15842
self.ScriptCopterPosOffsets,
15843
self.MountSolo,
15844
self.FlyMissionTwice,
15845
self.FlyMissionTwiceWithReset,
15846
self.MissionIndexValidity,
15847
self.InvalidJumpTags,
15848
self.IMUConsistency,
15849
self.AHRSTrimLand,
15850
self.IBus,
15851
self.WaitAndMaintainAttitude_RCFlight,
15852
self.GuidedYawRate,
15853
self.RudderDisarmMidair,
15854
self.NoArmWithoutMissionItems,
15855
self.DO_CHANGE_SPEED_in_guided,
15856
self.ArmSwitchAfterReboot,
15857
self.RPLidarA1,
15858
self.RPLidarA2,
15859
self.MISSION_OPTION_CLEAR_MISSION_AT_BOOT,
15860
self.SafetySwitch,
15861
self.RCProtocolFailsafe,
15862
self.BrakeZ,
15863
self.MAV_CMD_DO_FLIGHTTERMINATION,
15864
self.MAV_CMD_DO_LAND_START,
15865
self.MAV_CMD_DO_SET_GLOBAL_ORIGIN,
15866
self.MAV_CMD_SET_EKF_SOURCE_SET,
15867
self.MAV_CMD_NAV_TAKEOFF,
15868
self.MAV_CMD_NAV_TAKEOFF_command_int,
15869
self.Ch6TuningWPSpeed,
15870
self.DualTuningChannels,
15871
self.PILOT_THR_BHV,
15872
self.GPSForYawCompassLearn,
15873
self.CameraLogMessages,
15874
self.LoiterToGuidedHomeVSOrigin,
15875
self.GuidedModeThrust,
15876
self.CompassMot,
15877
self.AutoRTL,
15878
self.EK3_OGN_HGT_MASK_climbing,
15879
self.EK3_OGN_HGT_MASK,
15880
self.FarOrigin,
15881
self.GuidedForceArm,
15882
self.GuidedWeatherVane,
15883
self.LUAConfigProfile,
15884
self.Clamp,
15885
self.GripperReleaseOnThrustLoss,
15886
self.GripperInitialPosition,
15887
self.REQUIRE_LOCATION_FOR_ARMING,
15888
self.LoggingFormat,
15889
self.MissionRTLYawBehaviour,
15890
self.BatteryInternalUseOnly,
15891
self.MAV_CMD_MISSION_START_p1_p2,
15892
self.ScriptingAHRSSource,
15893
self.CommonOrigin,
15894
self.AHRSOriginRecorded,
15895
self.TestTetherStuck,
15896
self.ScriptingFlipMode,
15897
self.ScriptingFlyVelocity,
15898
self.EK3_EXT_NAV_vel_without_vert,
15899
self.CompassLearnCopyFromEKF,
15900
self.AHRSAutoTrim,
15901
self.Ch6TuningLoitMaxXYSpeed,
15902
self.IgnorePilotYaw,
15903
self.TestEKF3CompassFailover,
15904
self.test_EKF3_option_disable_lane_switch,
15905
self.PLDNoParameters,
15906
self.PeriphMultiUARTTunnel,
15907
self.EKF3SRCPerCore,
15908
])
15909
return ret
15910
15911
def testcan(self):
15912
ret = ([
15913
self.CANGPSCopterMission,
15914
self.TestLogDownloadMAVProxyCAN,
15915
])
15916
return ret
15917
15918
def BattCANSplitAuxInfo(self):
15919
'''test CAN battery periphs'''
15920
self.start_subtest("Swap UAVCAN backend at runtime")
15921
self.set_parameters({
15922
"CAN_P1_DRIVER": 1,
15923
"BATT_MONITOR": 4, # 4 is analog volt+curr
15924
"BATT2_MONITOR": 8, # 8 is UAVCAN_BatteryInfo
15925
"BATT_SERIAL_NUM": 0,
15926
"BATT2_SERIAL_NUM": 0,
15927
"BATT_OPTIONS": 128, # allow split auxinfo
15928
"BATT2_OPTIONS": 128, # allow split auxinfo
15929
})
15930
self.reboot_sitl()
15931
self.delay_sim_time(2)
15932
self.set_parameters({
15933
"BATT_MONITOR": 8, # 8 is UAVCAN_BatteryInfo
15934
"BATT2_MONITOR": 4, # 8 is UAVCAN_BatteryInfo
15935
})
15936
self.delay_sim_time(2)
15937
self.set_parameters({
15938
"BATT_MONITOR": 4, # 8 is UAVCAN_BatteryInfo
15939
"BATT2_MONITOR": 8, # 8 is UAVCAN_BatteryInfo
15940
})
15941
self.delay_sim_time(2)
15942
self.set_parameters({
15943
"BATT_MONITOR": 8, # 8 is UAVCAN_BatteryInfo
15944
"BATT2_MONITOR": 4, # 8 is UAVCAN_BatteryInfo
15945
})
15946
self.delay_sim_time(2)
15947
15948
def BattCANReplaceRuntime(self):
15949
'''test CAN battery periphs'''
15950
self.start_subtest("Replace UAVCAN backend at runtime")
15951
self.set_parameters({
15952
"CAN_P1_DRIVER": 1,
15953
"BATT_MONITOR": 11, # 4 is analog volt+curr
15954
})
15955
self.reboot_sitl()
15956
self.delay_sim_time(2)
15957
self.set_parameters({
15958
"BATT_MONITOR": 8, # 4 is UAVCAN batterinfo
15959
})
15960
self.delay_sim_time(2)
15961
15962
def testcanbatt(self):
15963
ret = ([
15964
self.BattCANReplaceRuntime,
15965
self.BattCANSplitAuxInfo,
15966
])
15967
return ret
15968
15969
def tests(self):
15970
ret = []
15971
ret.extend(self.tests1a())
15972
ret.extend(self.tests1b())
15973
ret.extend(self.tests1c())
15974
ret.extend(self.tests1d())
15975
ret.extend(self.tests1e())
15976
ret.extend(self.tests2a())
15977
ret.extend(self.tests2b())
15978
return ret
15979
15980
def disabled_tests(self):
15981
return {
15982
"Parachute": "See https://github.com/ArduPilot/ardupilot/issues/4702",
15983
"GroundEffectCompensation_takeOffExpected": "Flapping",
15984
"GroundEffectCompensation_touchDownExpected": "Flapping",
15985
"FlyMissionTwice": "See https://github.com/ArduPilot/ardupilot/pull/18561",
15986
"CompassMot": "Causes an arithmetic exception in the EKF",
15987
"SMART_RTL_EnterLeave": "Causes a panic",
15988
"SMART_RTL_Repeat": "Currently fails due to issue with loop detection",
15989
"RTLStoppingDistanceSpeed": "Currently fails due to vehicle going off-course",
15990
}
15991
15992
15993
class AutoTestCopterTests1a(AutoTestCopter):
15994
def tests(self):
15995
return self.tests1a()
15996
15997
15998
class AutoTestCopterTests1b(AutoTestCopter):
15999
def tests(self):
16000
return self.tests1b()
16001
16002
16003
class AutoTestCopterTests1c(AutoTestCopter):
16004
def tests(self):
16005
return self.tests1c()
16006
16007
16008
class AutoTestCopterTests1d(AutoTestCopter):
16009
def tests(self):
16010
return self.tests1d()
16011
16012
16013
class AutoTestCopterTests1e(AutoTestCopter):
16014
def tests(self):
16015
return self.tests1e()
16016
16017
16018
class AutoTestCopterTests2a(AutoTestCopter):
16019
def tests(self):
16020
return self.tests2a()
16021
16022
16023
class AutoTestCopterTests2b(AutoTestCopter):
16024
def tests(self):
16025
return self.tests2b()
16026
16027
16028
class AutoTestCAN(AutoTestCopter):
16029
16030
def tests(self):
16031
return self.testcan()
16032
16033
16034
class AutoTestBattCAN(AutoTestCopter):
16035
16036
def tests(self):
16037
return self.testcanbatt()
16038
16039