Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/autotest/helicopter.py
9742 views
1
'''
2
Fly Helicopter in SITL
3
4
AP_FLAKE8_CLEAN
5
'''
6
7
from arducopter import AutoTestCopter
8
9
import vehicle_test_suite
10
from vehicle_test_suite import NotAchievedException, AutoTestTimeoutException
11
12
from pymavlink import mavutil
13
from pysim import vehicleinfo
14
15
import copy
16
import operator
17
18
19
class AutoTestHelicopter(AutoTestCopter):
20
21
sitl_start_loc = mavutil.location(40.072842, -105.230575, 1586, 0) # Sparkfun AVC Location
22
23
def vehicleinfo_key(self):
24
return 'Helicopter'
25
26
def log_name(self):
27
return "HeliCopter"
28
29
def default_frame(self):
30
return "heli"
31
32
def sitl_start_location(self):
33
return self.sitl_start_loc
34
35
def default_speedup(self):
36
'''Heli seems to be race-free'''
37
return 100
38
39
def is_heli(self):
40
return True
41
42
def rc_defaults(self):
43
ret = super(AutoTestHelicopter, self).rc_defaults()
44
ret[8] = 1000
45
ret[3] = 1000 # collective
46
return ret
47
48
@staticmethod
49
def get_position_armable_modes_list():
50
'''filter THROW mode out of armable modes list; Heli is special-cased'''
51
ret = AutoTestCopter.get_position_armable_modes_list()
52
ret = filter(lambda x : x != "THROW", ret)
53
return ret
54
55
def loiter_requires_position(self):
56
self.progress("Skipping loiter-requires-position for heli; rotor runup issues")
57
58
def get_collective_out(self):
59
servo = self.assert_receive_message('SERVO_OUTPUT_RAW')
60
chan_pwm = (servo.servo1_raw + servo.servo2_raw + servo.servo3_raw)/3.0
61
return chan_pwm
62
63
def RotorRunup(self):
64
'''Test rotor runip'''
65
# Takeoff and landing in Loiter
66
TARGET_RUNUP_TIME = 10
67
self.zero_throttle()
68
self.change_mode('LOITER')
69
self.wait_ready_to_arm()
70
self.arm_vehicle()
71
servo = self.assert_receive_message('SERVO_OUTPUT_RAW')
72
coll = servo.servo1_raw
73
coll = coll + 50
74
self.set_parameter("H_RSC_RUNUP_TIME", TARGET_RUNUP_TIME)
75
self.progress("Initiate Runup by putting some throttle")
76
self.set_rc(8, 2000)
77
self.set_rc(3, 1700)
78
self.progress("Collective threshold PWM %u" % coll)
79
tstart = self.get_sim_time()
80
self.progress("Wait that collective PWM pass threshold value")
81
servo = self.assert_receive_message(
82
"SERVO_OUTPUT_RAW",
83
condition=f'SERVO_OUTPUT_RAW.servo1_raw>{coll}'
84
)
85
runup_time = self.get_sim_time() - tstart
86
self.progress("Collective is now at PWM %u" % servo.servo1_raw)
87
self.mav.wait_heartbeat()
88
if runup_time < TARGET_RUNUP_TIME:
89
self.zero_throttle()
90
self.set_rc(8, 1000)
91
self.disarm_vehicle()
92
self.mav.wait_heartbeat()
93
raise NotAchievedException("Takeoff initiated before runup time complete %u" % runup_time)
94
self.progress("Runup time %u" % runup_time)
95
self.zero_throttle()
96
self.land_and_disarm()
97
self.mav.wait_heartbeat()
98
99
# fly_avc_test - fly AVC mission
100
def AVCMission(self):
101
'''fly AVC mission'''
102
self.change_mode('STABILIZE')
103
self.wait_ready_to_arm()
104
105
self.arm_vehicle()
106
self.progress("Raising rotor speed")
107
self.set_rc(8, 2000)
108
109
# upload mission from file
110
self.progress("# Load copter_AVC2013_mission")
111
# load the waypoint count
112
num_wp = self.load_mission("copter_AVC2013_mission.txt", strict=False)
113
if not num_wp:
114
raise NotAchievedException("load copter_AVC2013_mission failed")
115
116
self.progress("Fly AVC mission from 1 to %u" % num_wp)
117
self.set_current_waypoint(1)
118
119
# wait for motor runup
120
self.delay_sim_time(20)
121
122
# switch into AUTO mode and raise throttle
123
self.change_mode('AUTO')
124
self.set_rc(3, 1500)
125
126
# fly the mission
127
self.wait_waypoint(0, num_wp-1, timeout=500)
128
129
# set throttle to minimum
130
self.zero_throttle()
131
132
# wait for disarm
133
self.wait_disarmed()
134
self.progress("MOTORS DISARMED OK")
135
136
self.progress("Lowering rotor speed")
137
self.set_rc(8, 1000)
138
139
self.progress("AVC mission completed: passed!")
140
141
def takeoff(self,
142
alt_min=30,
143
takeoff_throttle=1700,
144
require_absolute=True,
145
mode="STABILIZE",
146
timeout=120):
147
"""Takeoff get to 30m altitude."""
148
self.progress("TAKEOFF")
149
self.change_mode(mode)
150
if not self.armed():
151
self.wait_ready_to_arm(require_absolute=require_absolute, timeout=timeout)
152
self.zero_throttle()
153
self.arm_vehicle()
154
155
self.progress("Raising rotor speed")
156
self.set_rc(8, 2000)
157
self.progress("wait for rotor runup to complete")
158
if self.get_parameter("H_RSC_MODE") == 4:
159
self.context_collect('STATUSTEXT')
160
self.wait_statustext("Governor Engaged", check_context=True)
161
elif self.get_parameter("H_RSC_MODE") == 3:
162
self.wait_rpm(1, 1300, 1400)
163
else:
164
self.wait_servo_channel_value(8, 1659, timeout=10)
165
166
# wait for motor runup
167
self.delay_sim_time(20)
168
169
if mode == 'GUIDED':
170
self.user_takeoff(alt_min=alt_min)
171
else:
172
self.set_rc(3, takeoff_throttle)
173
self.wait_altitude(alt_min-1, alt_min+5, relative=True, timeout=timeout)
174
self.hover()
175
self.progress("TAKEOFF COMPLETE")
176
177
def FlyEachFrame(self):
178
'''Fly each supported internal frame'''
179
vinfo = vehicleinfo.VehicleInfo()
180
vinfo_options = vinfo.options[self.vehicleinfo_key()]
181
known_broken_frames = {
182
}
183
for frame in sorted(vinfo_options["frames"].keys()):
184
self.start_subtest("Testing frame (%s)" % str(frame))
185
if frame in known_broken_frames:
186
self.progress("Actually, no I'm not - it is known-broken (%s)" %
187
(known_broken_frames[frame]))
188
continue
189
frame_bits = vinfo_options["frames"][frame]
190
print("frame_bits: %s" % str(frame_bits))
191
if frame_bits.get("external", False):
192
self.progress("Actually, no I'm not - it is an external simulation")
193
continue
194
model = frame_bits.get("model", frame)
195
# the model string for Callisto has crap in it.... we
196
# should really have another entry in the vehicleinfo data
197
# to carry the path to the JSON.
198
actual_model = model.split(":")[0]
199
defaults = self.model_defaults_filepath(actual_model)
200
if not isinstance(defaults, list):
201
defaults = [defaults]
202
self.customise_SITL_commandline(
203
[],
204
defaults_filepath=defaults,
205
model=model,
206
wipe=True,
207
)
208
self.takeoff(10)
209
self.do_RTL()
210
211
def governortest(self):
212
'''Test Heli Internal Throttle Curve and Governor'''
213
self.customise_SITL_commandline(
214
[],
215
defaults_filepath=self.model_defaults_filepath('heli-gas'),
216
model="heli-gas",
217
wipe=True,
218
)
219
self.set_parameter("H_RSC_MODE", 4)
220
self.takeoff(10)
221
self.do_RTL()
222
223
def hover(self):
224
self.progress("Setting hover collective")
225
self.set_rc(3, 1500)
226
227
def PosHoldTakeOff(self):
228
"""ensure vehicle stays put until it is ready to fly"""
229
self.set_parameter("PILOT_TKOFF_ALT", 700)
230
self.change_mode('POSHOLD')
231
self.zero_throttle()
232
self.set_rc(8, 1000)
233
self.wait_ready_to_arm()
234
# Arm
235
self.arm_vehicle()
236
self.progress("Raising rotor speed")
237
self.set_rc(8, 2000)
238
self.progress("wait for rotor runup to complete")
239
self.wait_servo_channel_value(8, 1659, timeout=10)
240
self.delay_sim_time(20)
241
# check we are still on the ground...
242
max_relalt = 1 # metres
243
relative_alt = self.get_altitude(relative=True)
244
if abs(relative_alt) > max_relalt:
245
raise NotAchievedException("Took off prematurely (abs(%f)>%f)" %
246
(relative_alt, max_relalt))
247
248
self.progress("Pushing collective past half-way")
249
self.set_rc(3, 1600)
250
self.delay_sim_time(0.5)
251
self.hover()
252
253
# make sure we haven't already reached alt:
254
relative_alt = self.get_altitude(relative=True)
255
max_initial_alt = 1.5 # metres
256
if abs(relative_alt) > max_initial_alt:
257
raise NotAchievedException("Took off too fast (%f > %f" %
258
(abs(relative_alt), max_initial_alt))
259
260
self.progress("Monitoring takeoff-to-alt")
261
self.wait_altitude(6, 8, relative=True, minimum_duration=5)
262
self.progress("takeoff OK")
263
264
self.land_and_disarm()
265
266
def StabilizeTakeOff(self):
267
"""Fly stabilize takeoff"""
268
self.change_mode('STABILIZE')
269
self.set_rc(3, 1000)
270
self.set_rc(8, 1000)
271
self.wait_ready_to_arm()
272
self.arm_vehicle()
273
self.set_rc(8, 2000)
274
self.progress("wait for rotor runup to complete")
275
self.wait_servo_channel_value(8, 1659, timeout=10)
276
self.delay_sim_time(20)
277
# check we are still on the ground...
278
relative_alt = self.get_altitude(relative=True)
279
if abs(relative_alt) > 0.1:
280
raise NotAchievedException("Took off prematurely")
281
self.progress("Pushing throttle past half-way")
282
self.set_rc(3, 1650)
283
284
self.progress("Monitoring takeoff")
285
self.wait_altitude(6.9, 8, relative=True)
286
287
self.progress("takeoff OK")
288
289
self.land_and_disarm()
290
291
def SplineWaypoint(self, timeout=600):
292
"""ensure basic spline functionality works"""
293
self.load_mission("copter_spline_mission.txt", strict=False)
294
self.change_mode("LOITER")
295
self.wait_ready_to_arm()
296
self.arm_vehicle()
297
self.progress("Raising rotor speed")
298
self.set_rc(8, 2000)
299
self.delay_sim_time(20)
300
self.change_mode("AUTO")
301
self.set_rc(3, 1500)
302
self.wait_disarmed(timeout=600)
303
self.progress("Lowering rotor speed")
304
self.set_rc(8, 1000)
305
306
def Autorotation(self, timeout=600):
307
"""Check engine-out behaviour"""
308
self.context_push()
309
start_alt = 100 # metres
310
self.set_parameters({
311
"AROT_ENABLE": 1,
312
"H_RSC_AROT_ENBL": 1,
313
"H_COL_LAND_MIN" : -2.0
314
})
315
bail_out_time = self.get_parameter('H_RSC_AROT_RUNUP')
316
self.change_mode('POSHOLD')
317
self.set_rc(3, 1000)
318
self.set_rc(8, 1000)
319
self.wait_ready_to_arm()
320
self.arm_vehicle()
321
self.set_rc(8, 2000)
322
self.progress("wait for rotor runup to complete")
323
self.wait_servo_channel_value(8, 1659, timeout=10)
324
self.delay_sim_time(20)
325
self.set_rc(3, 2000)
326
self.wait_altitude(start_alt - 1,
327
(start_alt + 5),
328
relative=True,
329
timeout=timeout)
330
self.context_collect('STATUSTEXT')
331
332
# Reset collective to enter hover
333
self.set_rc(3, 1500)
334
335
# Change to the autorotation flight mode
336
self.progress("Triggering autorotate mode")
337
self.change_mode('AUTOROTATE')
338
339
# Disengage the interlock to remove power
340
self.set_rc(8, 1000)
341
342
# Ensure we have progressed through the mode's state machine
343
self.wait_statustext("Glide Phase", check_context=True)
344
345
self.progress("Testing bailout from autorotation")
346
self.set_rc(8, 2000)
347
# See if the output ramps to a value close to expected with the prescribed time
348
self.wait_servo_channel_value(8, 1659, timeout=bail_out_time+1, comparator=operator.ge)
349
350
# Successfully bailed out, disengage the interlock and allow autorotation to progress
351
self.set_rc(8, 1000)
352
self.wait_statustext(r"SIM Hit ground at ([0-9.]+) m/s",
353
check_context=True,
354
regex=True)
355
speed = float(self.re_match.group(1))
356
if speed > 30:
357
raise NotAchievedException("Hit too hard")
358
359
# Set throttle low to trip auto disarm
360
self.set_rc(3, 1000)
361
362
self.wait_disarmed()
363
self.context_pop()
364
365
def AutorotationPreArm(self):
366
"""Check autorotation pre-arms are working"""
367
self.context_push()
368
self.start_subtest("Check pass when autorotation mode not enabled")
369
self.set_parameters({
370
"AROT_ENABLE": 0,
371
"RPM1_TYPE": 0
372
})
373
self.reboot_sitl()
374
try:
375
self.wait_statustext("PreArm: AROT: RPM1 not enabled", timeout=50)
376
raise NotAchievedException("Received AROT prearm when not AROT not enabled")
377
except AutoTestTimeoutException:
378
# We want to hit the timeout on wait_statustext()
379
pass
380
381
self.start_subtest("Check pre-arm fails when autorotation mode enabled")
382
self.set_parameter("AROT_ENABLE", 1)
383
self.wait_statustext("PreArm: AROT: RPM1 not enabled", timeout=50)
384
self.set_parameter("RPM1_TYPE", 10) # reboot required to take effect
385
self.reboot_sitl()
386
387
self.start_subtest("Check pre-arm fails with bad HS_Sensor config")
388
self.context_push()
389
self.set_parameter("AROT_HS_SENSOR", -1)
390
self.wait_statustext("PreArm: AROT: RPM instance <0", timeout=50)
391
self.context_pop()
392
393
self.start_subtest("Check pre-arm fails with bad RSC config")
394
self.wait_statustext("PreArm: AROT: H_RSC_AROT_* not configured", timeout=50)
395
396
self.start_subtest("Check pre-arms clear with all issues corrected")
397
self.set_parameter("H_RSC_AROT_ENBL", 1)
398
self.wait_ready_to_arm()
399
400
self.context_pop()
401
402
def ManAutorotation(self, timeout=600):
403
"""Check autorotation power recovery behaviour"""
404
RSC_CHAN = 8
405
406
def check_rsc_output(self, throttle, timeout):
407
# Check we get a sensible throttle output
408
expected_pwm = int(throttle * 0.01 * 1000 + 1000)
409
410
# Help out the detection by accepting some margin
411
margin = 2
412
413
# See if the output ramps to a value close to expected with the prescribed time
414
self.wait_servo_channel_in_range(RSC_CHAN, expected_pwm-margin, expected_pwm+margin, timeout=timeout)
415
416
def TestAutorotationConfig(self, rsc_idle, arot_ramp_time, arot_idle, cool_down):
417
RAMP_TIME = 10
418
RUNUP_TIME = 15
419
AROT_RUNUP_TIME = arot_ramp_time + 4
420
RSC_SETPOINT = 66
421
self.set_parameters({
422
"H_RSC_AROT_ENBL": 1,
423
"H_RSC_AROT_RAMP": arot_ramp_time,
424
"H_RSC_AROT_RUNUP": AROT_RUNUP_TIME,
425
"H_RSC_AROT_IDLE": arot_idle,
426
"H_RSC_RAMP_TIME": RAMP_TIME,
427
"H_RSC_RUNUP_TIME": RUNUP_TIME,
428
"H_RSC_IDLE": rsc_idle,
429
"H_RSC_SETPOINT": RSC_SETPOINT,
430
"H_RSC_CLDWN_TIME": cool_down
431
})
432
433
# Check the RSC config so we know what to expect on the throttle output
434
if self.get_parameter("H_RSC_MODE") != 2:
435
self.set_parameter("H_RSC_MODE", 2)
436
self.reboot_sitl()
437
438
self.change_mode('POSHOLD')
439
self.set_rc(3, 1000)
440
self.set_rc(8, 1000)
441
self.wait_ready_to_arm()
442
self.arm_vehicle()
443
self.set_rc(8, 2000)
444
self.progress("wait for rotor runup to complete")
445
check_rsc_output(self, RSC_SETPOINT, RUNUP_TIME+1)
446
447
self.delay_sim_time(20)
448
self.set_rc(3, 2000)
449
self.wait_altitude(100,
450
105,
451
relative=True,
452
timeout=timeout)
453
self.context_collect('STATUSTEXT')
454
self.change_mode('STABILIZE')
455
456
self.progress("Triggering manual autorotation by disabling interlock")
457
self.set_rc(3, 1000)
458
self.set_rc(8, 1000)
459
460
self.wait_statustext(r"RSC: In Autorotation", check_context=True)
461
462
# Check we are using the correct throttle output. This should happen instantly on ramp down.
463
idle_thr = rsc_idle
464
if (arot_idle > 0):
465
idle_thr = arot_idle
466
467
check_rsc_output(self, idle_thr, 1)
468
469
self.progress("RSC is outputting correct idle throttle")
470
471
# Wait to establish autorotation.
472
self.delay_sim_time(2)
473
474
# Re-engage interlock to start bailout sequence
475
self.set_rc(8, 2000)
476
477
# Ensure we see the bailout state
478
self.wait_statustext("RSC: Bailing Out", check_context=True)
479
480
# Check we are back up to flight throttle. Autorotation ramp up time should be used
481
check_rsc_output(self, RSC_SETPOINT, arot_ramp_time+1)
482
483
# Give time for engine to power up
484
self.set_rc(3, 1400)
485
self.delay_sim_time(2)
486
487
self.progress("in-flight power recovery")
488
self.set_rc(3, 1500)
489
self.delay_sim_time(5)
490
491
# Initiate autorotation again
492
self.set_rc(3, 1000)
493
self.set_rc(8, 1000)
494
495
self.wait_statustext(r"SIM Hit ground at ([0-9.]+) m/s",
496
check_context=True,
497
regex=True)
498
speed = float(self.re_match.group(1))
499
if speed > 30:
500
raise NotAchievedException("Hit too hard")
501
502
# Check that cool down is still used correctly if set
503
# First wait until we are out of the autorotation state
504
self.wait_statustext("RSC: Autorotation Stopped")
505
if (cool_down > 0):
506
check_rsc_output(self, rsc_idle*1.5, cool_down)
507
508
# Verify RSC output resets to RSC_IDLE after land complete
509
check_rsc_output(self, rsc_idle, 20)
510
self.wait_disarmed()
511
512
# We test the bailout behavior of two different configs
513
# First we test config with a regular throttle curve
514
self.start_subtest("testing autorotation with throttle curve config")
515
self.context_push()
516
TestAutorotationConfig(self, rsc_idle=5.0, arot_ramp_time=2.0, arot_idle=0, cool_down=0)
517
518
# Now we test a config that would be used with an ESC with internal governor and an autorotation window
519
self.start_subtest("testing autorotation with ESC autorotation window config")
520
TestAutorotationConfig(self, rsc_idle=0.0, arot_ramp_time=0.0, arot_idle=20.0, cool_down=0)
521
522
# Check rsc output behavior when using the cool down feature
523
self.start_subtest("testing autorotation with cool down enabled and zero autorotation idle")
524
TestAutorotationConfig(self, rsc_idle=5.0, arot_ramp_time=2.0, arot_idle=0, cool_down=5.0)
525
526
self.start_subtest("testing that H_RSC_AROT_IDLE is used over RSC_IDLE when cool down is enabled")
527
TestAutorotationConfig(self, rsc_idle=5.0, arot_ramp_time=2.0, arot_idle=10, cool_down=5.0)
528
529
self.context_pop()
530
531
def mission_item_home(self, target_system, target_component):
532
'''returns a mission_item_int which can be used as home in a mission'''
533
return self.mav.mav.mission_item_int_encode(
534
target_system,
535
target_component,
536
0, # seq
537
mavutil.mavlink.MAV_FRAME_GLOBAL_INT,
538
mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
539
0, # current
540
0, # autocontinue
541
3, # p1
542
0, # p2
543
0, # p3
544
0, # p4
545
int(1.0000 * 1e7), # latitude
546
int(2.0000 * 1e7), # longitude
547
31.0000, # altitude
548
mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
549
550
def mission_item_takeoff(self, target_system, target_component):
551
'''returns a mission_item_int which can be used as takeoff in a mission'''
552
return self.mav.mav.mission_item_int_encode(
553
target_system,
554
target_component,
555
1, # seq
556
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
557
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
558
0, # current
559
0, # autocontinue
560
0, # p1
561
0, # p2
562
0, # p3
563
0, # p4
564
int(1.0000 * 1e7), # latitude
565
int(1.0000 * 1e7), # longitude
566
31.0000, # altitude
567
mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
568
569
def mission_item_rtl(self, target_system, target_component):
570
'''returns a mission_item_int which can be used as takeoff in a mission'''
571
return self.mav.mav.mission_item_int_encode(
572
target_system,
573
target_component,
574
1, # seq
575
mavutil.mavlink.MAV_FRAME_GLOBAL,
576
mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH,
577
0, # current
578
0, # autocontinue
579
0, # p1
580
0, # p2
581
0, # p3
582
0, # p4
583
0, # latitude
584
0, # longitude
585
0.0000, # altitude
586
mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
587
588
def scurve_nasty_mission(self, target_system=1, target_component=1):
589
'''returns a mission which attempts to give the SCurve library
590
indigestion. The same destination is given several times.'''
591
592
wp2_loc = self.mav.location()
593
wp2_offset_n = 20
594
wp2_offset_e = 30
595
self.location_offset_ne(wp2_loc, wp2_offset_n, wp2_offset_e)
596
597
wp2_by_three = self.mav.mav.mission_item_int_encode(
598
target_system,
599
target_component,
600
2, # seq
601
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
602
mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
603
0, # current
604
0, # autocontinue
605
3, # p1
606
0, # p2
607
0, # p3
608
0, # p4
609
int(wp2_loc.lat * 1e7), # latitude
610
int(wp2_loc.lng * 1e7), # longitude
611
31.0000, # altitude
612
mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
613
614
wp5_loc = self.mav.location()
615
wp5_offset_n = -20
616
wp5_offset_e = 30
617
self.location_offset_ne(wp5_loc, wp5_offset_n, wp5_offset_e)
618
619
wp5_by_three = self.mav.mav.mission_item_int_encode(
620
target_system,
621
target_component,
622
5, # seq
623
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
624
mavutil.mavlink.MAV_CMD_NAV_SPLINE_WAYPOINT,
625
0, # current
626
0, # autocontinue
627
3, # p1
628
0, # p2
629
0, # p3
630
0, # p4
631
int(wp5_loc.lat * 1e7), # latitude
632
int(wp5_loc.lng * 1e7), # longitude
633
31.0000, # altitude
634
mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
635
636
ret = copy.copy([
637
# slot 0 is home
638
self.mission_item_home(target_system=target_system, target_component=target_component),
639
# slot 1 is takeoff
640
self.mission_item_takeoff(target_system=target_system, target_component=target_component),
641
# now three spline waypoints right on top of one another:
642
copy.copy(wp2_by_three),
643
copy.copy(wp2_by_three),
644
copy.copy(wp2_by_three),
645
# now three MORE spline waypoints right on top of one another somewhere else:
646
copy.copy(wp5_by_three),
647
copy.copy(wp5_by_three),
648
copy.copy(wp5_by_three),
649
self.mission_item_rtl(target_system=target_system, target_component=target_component),
650
])
651
self.correct_wp_seq_numbers(ret)
652
return ret
653
654
def scurve_nasty_up_mission(self, target_system=1, target_component=1):
655
'''returns a mission which attempts to give the SCurve library
656
indigestion. The same destination is given several times but with differing altitudes.'''
657
658
wp2_loc = self.mav.location()
659
wp2_offset_n = 20
660
wp2_offset_e = 30
661
self.location_offset_ne(wp2_loc, wp2_offset_n, wp2_offset_e)
662
663
wp2 = self.mav.mav.mission_item_int_encode(
664
target_system,
665
target_component,
666
2, # seq
667
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
668
mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
669
0, # current
670
0, # autocontinue
671
3, # p1
672
0, # p2
673
0, # p3
674
0, # p4
675
int(wp2_loc.lat * 1e7), # latitude
676
int(wp2_loc.lng * 1e7), # longitude
677
31.0000, # altitude
678
mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
679
wp3 = copy.copy(wp2)
680
wp3.alt = 40
681
wp4 = copy.copy(wp2)
682
wp4.alt = 31
683
684
wp5_loc = self.mav.location()
685
wp5_offset_n = -20
686
wp5_offset_e = 30
687
self.location_offset_ne(wp5_loc, wp5_offset_n, wp5_offset_e)
688
689
wp5 = self.mav.mav.mission_item_int_encode(
690
target_system,
691
target_component,
692
5, # seq
693
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
694
mavutil.mavlink.MAV_CMD_NAV_SPLINE_WAYPOINT,
695
0, # current
696
0, # autocontinue
697
3, # p1
698
0, # p2
699
0, # p3
700
0, # p4
701
int(wp5_loc.lat * 1e7), # latitude
702
int(wp5_loc.lng * 1e7), # longitude
703
31.0000, # altitude
704
mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
705
wp6 = copy.copy(wp5)
706
wp6.alt = 41
707
wp7 = copy.copy(wp5)
708
wp7.alt = 51
709
710
ret = copy.copy([
711
# slot 0 is home
712
self.mission_item_home(target_system=target_system, target_component=target_component),
713
# slot 1 is takeoff
714
self.mission_item_takeoff(target_system=target_system, target_component=target_component),
715
wp2,
716
wp3,
717
wp4,
718
# now three MORE spline waypoints right on top of one another somewhere else:
719
wp5,
720
wp6,
721
wp7,
722
self.mission_item_rtl(target_system=target_system, target_component=target_component),
723
])
724
self.correct_wp_seq_numbers(ret)
725
return ret
726
727
def fly_mission_points(self, points):
728
'''takes a list of waypoints and flies them, expecting a disarm at end'''
729
self.check_mission_upload_download(points)
730
self.set_parameter("AUTO_OPTIONS", 3)
731
self.change_mode('AUTO')
732
self.set_rc(8, 1000)
733
self.wait_ready_to_arm()
734
self.arm_vehicle()
735
self.progress("Raising rotor speed")
736
self.set_rc(8, 2000)
737
self.wait_waypoint(0, len(points)-1)
738
self.wait_disarmed()
739
self.set_rc(8, 1000)
740
741
def NastyMission(self):
742
'''constructs and runs missions designed to test scurves'''
743
self.fly_mission_points(self.scurve_nasty_mission())
744
# hopefully we don't need this step forever:
745
self.progress("Restting mission state machine by changing into LOITER")
746
self.change_mode('LOITER')
747
self.fly_mission_points(self.scurve_nasty_up_mission())
748
749
def MountFailsafeAction(self):
750
"""Fly Mount Failsafe action"""
751
self.context_push()
752
753
self.progress("Setting up servo mount")
754
roll_servo = 12
755
pitch_servo = 11
756
yaw_servo = 10
757
open_servo = 9
758
roll_limit = 50
759
self.set_parameters({
760
"MNT1_TYPE": 1,
761
"SERVO%u_MIN" % roll_servo: 1000,
762
"SERVO%u_MAX" % roll_servo: 2000,
763
"SERVO%u_FUNCTION" % yaw_servo: 6, # yaw
764
"SERVO%u_FUNCTION" % pitch_servo: 7, # roll
765
"SERVO%u_FUNCTION" % roll_servo: 8, # pitch
766
"SERVO%u_FUNCTION" % open_servo: 9, # mount open
767
"MNT1_OPTIONS": 2, # retract
768
"MNT1_DEFLT_MODE": 3, # RC targeting
769
"MNT1_ROLL_MIN": -roll_limit,
770
"MNT1_ROLL_MAX": roll_limit,
771
})
772
773
self.reboot_sitl()
774
775
retract_roll = 25.0
776
self.set_parameter("MNT1_NEUTRAL_X", retract_roll)
777
self.progress("Killing RC")
778
self.set_parameter("SIM_RC_FAIL", 2)
779
self.delay_sim_time(10)
780
want_servo_channel_value = int(1500 + 500*retract_roll/roll_limit)
781
self.wait_servo_channel_value(roll_servo, want_servo_channel_value, epsilon=1)
782
783
self.progress("Resurrecting RC")
784
self.set_parameter("SIM_RC_FAIL", 0)
785
self.wait_servo_channel_value(roll_servo, 1500)
786
787
self.context_pop()
788
789
self.reboot_sitl()
790
791
def set_rc_default(self):
792
super(AutoTestHelicopter, self).set_rc_default()
793
self.progress("Lowering rotor speed")
794
self.set_rc(8, 1000)
795
796
def fly_mission(self, filename, strict=True):
797
num_wp = self.load_mission(filename, strict=strict)
798
self.change_mode("LOITER")
799
self.wait_ready_to_arm()
800
self.arm_vehicle()
801
self.set_rc(8, 2000) # Raise rotor speed
802
self.delay_sim_time(20)
803
self.change_mode("AUTO")
804
self.set_rc(3, 1500)
805
806
self.wait_waypoint(1, num_wp-1)
807
self.wait_disarmed()
808
self.set_rc(8, 1000) # Lower rotor speed
809
810
# FIXME move this & plane's version to common
811
def AirspeedDrivers(self, timeout=600):
812
'''Test AirSpeed drivers'''
813
814
# Copter's airspeed sensors are off by default
815
self.set_parameters({
816
"ARSPD_ENABLE": 1,
817
"ARSPD_TYPE": 2, # Analog airspeed driver
818
"ARSPD_PIN": 1, # Analog airspeed driver pin for SITL
819
})
820
# set the start location to CMAC to use same test script as other vehicles
821
822
self.sitl_start_loc = mavutil.location(-35.362881, 149.165222, 582.000000, 90.0) # CMAC
823
self.customise_SITL_commandline(["--home", "%s,%s,%s,%s"
824
% (-35.362881, 149.165222, 582.000000, 90.0)])
825
826
# insert listener to compare airspeeds:
827
airspeed = [None, None]
828
829
def check_airspeeds(mav, m):
830
m_type = m.get_type()
831
if (m_type == 'NAMED_VALUE_FLOAT' and
832
m.name == 'AS2'):
833
airspeed[1] = m.value
834
elif m_type == 'VFR_HUD':
835
airspeed[0] = m.airspeed
836
else:
837
return
838
if airspeed[0] is None or airspeed[1] is None:
839
return
840
delta = abs(airspeed[0] - airspeed[1])
841
if delta > 3:
842
raise NotAchievedException("Airspeed mismatch (as1=%f as2=%f)" % (airspeed[0], airspeed[1]))
843
844
airspeed_sensors = [
845
("MS5525", 3, 1),
846
("DLVR", 7, 2),
847
]
848
for (name, t, bus) in airspeed_sensors:
849
self.context_push()
850
if bus is not None:
851
self.set_parameter("ARSPD2_BUS", bus)
852
self.set_parameter("ARSPD2_TYPE", t)
853
self.reboot_sitl()
854
self.wait_ready_to_arm()
855
self.arm_vehicle()
856
857
self.install_message_hook_context(check_airspeeds)
858
self.fly_mission("ap1.txt", strict=False)
859
860
if airspeed[0] is None:
861
raise NotAchievedException("Never saw an airspeed1")
862
if airspeed[1] is None:
863
raise NotAchievedException("Never saw an airspeed2")
864
if not self.current_onboard_log_contains_message("ARSP"):
865
raise NotAchievedException("Expected ARSP log message")
866
self.disarm_vehicle()
867
self.context_pop()
868
869
def TurbineCoolDown(self, timeout=200):
870
"""Check Turbine Cool Down Feature"""
871
self.context_push()
872
# set option for Turbine
873
RAMP_TIME = 4
874
SETPOINT = 66
875
IDLE = 15
876
COOLDOWN_TIME = 5
877
self.set_parameters({"RC6_OPTION": 161,
878
"H_RSC_RAMP_TIME": RAMP_TIME,
879
"H_RSC_SETPOINT": SETPOINT,
880
"H_RSC_IDLE": IDLE,
881
"H_RSC_CLDWN_TIME": COOLDOWN_TIME})
882
self.set_rc(3, 1000)
883
self.set_rc(8, 1000)
884
885
self.progress("Starting turbine")
886
self.wait_ready_to_arm()
887
self.context_collect("STATUSTEXT")
888
self.arm_vehicle()
889
890
self.set_rc(6, 2000)
891
self.wait_statustext('Turbine startup', check_context=True)
892
893
# Engage interlock to run up to head speed
894
self.set_rc(8, 2000)
895
896
# Check throttle gets to setpoint
897
expected_thr = SETPOINT * 0.01 * 1000 + 1000 - 1 # servo end points are 1000 to 2000
898
self.wait_servo_channel_value(8, expected_thr, timeout=RAMP_TIME+1, comparator=operator.ge)
899
900
self.progress("Checking cool down behaviour, idle x 1.5")
901
self.set_rc(8, 1000)
902
tstart = self.get_sim_time()
903
expected_thr = IDLE * 1.5 * 0.01 * 1000 + 1000 + 1
904
self.wait_servo_channel_value(8, expected_thr, timeout=2, comparator=operator.le)
905
906
# Check that the throttle drops to idle after cool down time
907
expected_thr = IDLE * 0.01 * 1000 + 1000 + 1
908
self.wait_servo_channel_value(8, expected_thr, timeout=COOLDOWN_TIME+1, comparator=operator.le)
909
910
measured_time = self.get_sim_time() - tstart
911
if (abs(measured_time - COOLDOWN_TIME) > 1.0):
912
raise NotAchievedException('Throttle did not reduce to idle within H_RSC_CLDWN_TIME')
913
914
self.set_rc(6, 1000)
915
self.wait_disarmed(timeout=20)
916
self.context_pop()
917
918
def TurbineStart(self, timeout=200):
919
"""Check Turbine Start Feature"""
920
RAMP_TIME = 4
921
# set option for Turbine Start
922
self.set_parameter("RC6_OPTION", 161)
923
self.set_parameter("H_RSC_RAMP_TIME", RAMP_TIME)
924
self.set_parameter("H_RSC_SETPOINT", 66)
925
self.set_parameter("DISARM_DELAY", 0)
926
self.set_rc(3, 1000)
927
self.set_rc(8, 1000)
928
929
# check that turbine start doesn't activate while disarmed
930
self.progress("Checking Turbine Start doesn't activate while disarmed")
931
self.set_rc(6, 2000)
932
tstart = self.get_sim_time()
933
while self.get_sim_time() - tstart < 2:
934
servo = self.assert_receive_message('SERVO_OUTPUT_RAW')
935
if servo.servo8_raw > 1050:
936
raise NotAchievedException("Turbine Start activated while disarmed")
937
self.set_rc(6, 1000)
938
939
# check that turbine start doesn't activate armed with interlock enabled
940
self.progress("Checking Turbine Start doesn't activate while armed with interlock enabled")
941
self.wait_ready_to_arm()
942
self.arm_vehicle()
943
self.set_rc(8, 2000)
944
self.set_rc(6, 2000)
945
tstart = self.get_sim_time()
946
while self.get_sim_time() - tstart < 5:
947
servo = self.assert_receive_message('SERVO_OUTPUT_RAW')
948
if servo.servo8_raw > 1660:
949
raise NotAchievedException("Turbine Start activated with interlock enabled")
950
951
self.set_rc(8, 1000)
952
self.set_rc(6, 1000)
953
self.disarm_vehicle()
954
955
# check that turbine start activates as designed (armed with interlock disabled)
956
self.progress("Checking Turbine Start activates as designed (armed with interlock disabled)")
957
self.delay_sim_time(2)
958
self.arm_vehicle()
959
960
self.set_rc(6, 2000)
961
tstart = self.get_sim_time()
962
while True:
963
if self.get_sim_time() - tstart > 5:
964
raise AutoTestTimeoutException("Turbine Start did not activate")
965
servo = self.assert_receive_message('SERVO_OUTPUT_RAW')
966
if servo.servo8_raw > 1800:
967
break
968
969
self.wait_servo_channel_value(8, 1000, timeout=3)
970
self.set_rc(6, 1000)
971
972
# check that turbine start will not reactivate after interlock enabled
973
self.progress("Checking Turbine Start doesn't activate once interlock is enabled after start)")
974
self.set_rc(8, 2000)
975
self.set_rc(6, 2000)
976
tstart = self.get_sim_time()
977
while self.get_sim_time() - tstart < 5:
978
servo = self.assert_receive_message('SERVO_OUTPUT_RAW')
979
if servo.servo8_raw > 1660:
980
raise NotAchievedException("Turbine Start activated with interlock enabled")
981
self.set_rc(6, 1000)
982
self.set_rc(8, 1000)
983
self.disarm_vehicle()
984
985
def PIDNotches(self):
986
"""Use dynamic harmonic notch to control motor noise."""
987
self.progress("Flying with PID notches")
988
self.set_parameters({
989
"FILT1_TYPE": 1,
990
"FILT2_TYPE": 1,
991
"AHRS_EKF_TYPE": 10,
992
"INS_LOG_BAT_MASK": 3,
993
"INS_LOG_BAT_OPT": 0,
994
"INS_GYRO_FILTER": 100, # set the gyro filter high so we can observe behaviour
995
"LOG_BITMASK": 65535,
996
"LOG_DISARMED": 0,
997
"SIM_VIB_FREQ_X": 120, # roll
998
"SIM_VIB_FREQ_Y": 120, # pitch
999
"SIM_VIB_FREQ_Z": 180, # yaw
1000
"FILT1_NOTCH_FREQ": 120,
1001
"FILT2_NOTCH_FREQ": 180,
1002
"ATC_RAT_RLL_NEF": 1,
1003
"ATC_RAT_PIT_NEF": 1,
1004
"ATC_RAT_YAW_NEF": 2,
1005
"SIM_GYR1_RND": 5,
1006
})
1007
self.reboot_sitl()
1008
1009
self.hover_and_check_matched_frequency_with_fft(5, 20, 350, reverse=True, takeoff=True)
1010
1011
def AutoTune(self):
1012
"""Test autotune mode"""
1013
# test roll and pitch FF tuning
1014
self.set_parameters({
1015
"ATC_ANG_RLL_P": 4.5,
1016
"ATC_RAT_RLL_P": 0,
1017
"ATC_RAT_RLL_I": 0.1,
1018
"ATC_RAT_RLL_D": 0,
1019
"ATC_RAT_RLL_FF": 0.15,
1020
"ATC_ANG_PIT_P": 4.5,
1021
"ATC_RAT_PIT_P": 0,
1022
"ATC_RAT_PIT_I": 0.1,
1023
"ATC_RAT_PIT_D": 0,
1024
"ATC_RAT_PIT_FF": 0.15,
1025
"ATC_ANG_YAW_P": 4.5,
1026
"ATC_RAT_YAW_P": 0.18,
1027
"ATC_RAT_YAW_I": 0.024,
1028
"ATC_RAT_YAW_D": 0.003,
1029
"ATC_RAT_YAW_FF": 0.024,
1030
"AUTOTUNE_AXES": 3,
1031
"AUTOTUNE_SEQ": 1,
1032
})
1033
1034
# Conduct testing from althold
1035
self.takeoff(10, mode="ALT_HOLD")
1036
1037
# hold position in loiter
1038
self.change_mode('AUTOTUNE')
1039
1040
tstart = self.get_sim_time()
1041
self.wait_statustext('AutoTune: Success', timeout=1000)
1042
now = self.get_sim_time()
1043
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
1044
self.autotune_land_and_save_gains()
1045
1046
# test pitch rate P and Rate D tuning
1047
self.set_parameters({
1048
"AUTOTUNE_AXES": 2,
1049
"AUTOTUNE_SEQ": 2,
1050
"AUTOTUNE_GN_MAX": 1.8,
1051
})
1052
1053
# Conduct testing from althold
1054
self.takeoff(10, mode="ALT_HOLD")
1055
1056
# hold position in loiter
1057
self.change_mode('AUTOTUNE')
1058
1059
tstart = self.get_sim_time()
1060
self.wait_statustext('AutoTune: Success', timeout=1000)
1061
now = self.get_sim_time()
1062
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
1063
self.autotune_land_and_save_gains()
1064
1065
# test Roll rate P and Rate D tuning
1066
self.set_parameters({
1067
"AUTOTUNE_AXES": 1,
1068
"AUTOTUNE_SEQ": 2,
1069
"AUTOTUNE_GN_MAX": 1.6,
1070
})
1071
1072
# Conduct testing from althold
1073
self.takeoff(10, mode="ALT_HOLD")
1074
1075
# hold position in loiter
1076
self.change_mode('AUTOTUNE')
1077
1078
tstart = self.get_sim_time()
1079
self.wait_statustext('AutoTune: Success', timeout=1000)
1080
now = self.get_sim_time()
1081
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
1082
self.autotune_land_and_save_gains()
1083
1084
# test Roll and pitch angle P tuning
1085
self.set_parameters({
1086
"AUTOTUNE_AXES": 3,
1087
"AUTOTUNE_SEQ": 4,
1088
"AUTOTUNE_FRQ_MIN": 5,
1089
"AUTOTUNE_FRQ_MAX": 50,
1090
"AUTOTUNE_GN_MAX": 1.6,
1091
})
1092
1093
# Conduct testing from althold
1094
self.takeoff(10, mode="ALT_HOLD")
1095
1096
# hold position in loiter
1097
self.change_mode('AUTOTUNE')
1098
1099
tstart = self.get_sim_time()
1100
self.wait_statustext('AutoTune: Success', timeout=1000)
1101
now = self.get_sim_time()
1102
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
1103
self.autotune_land_and_save_gains()
1104
1105
# test yaw FF and rate P and Rate D
1106
self.set_parameters({
1107
"AUTOTUNE_AXES": 4,
1108
"AUTOTUNE_SEQ": 3,
1109
"AUTOTUNE_FRQ_MIN": 10,
1110
"AUTOTUNE_FRQ_MAX": 70,
1111
"AUTOTUNE_GN_MAX": 1.4,
1112
})
1113
1114
# Conduct testing from althold
1115
self.takeoff(10, mode="ALT_HOLD")
1116
1117
# hold position in loiter
1118
self.change_mode('AUTOTUNE')
1119
1120
tstart = self.get_sim_time()
1121
self.wait_statustext('AutoTune: Success', timeout=1000)
1122
now = self.get_sim_time()
1123
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
1124
self.autotune_land_and_save_gains()
1125
1126
# test yaw angle P tuning
1127
self.set_parameters({
1128
"AUTOTUNE_AXES": 4,
1129
"AUTOTUNE_SEQ": 4,
1130
"AUTOTUNE_FRQ_MIN": 5,
1131
"AUTOTUNE_FRQ_MAX": 50,
1132
"AUTOTUNE_GN_MAX": 1.5,
1133
})
1134
1135
# Conduct testing from althold
1136
self.takeoff(10, mode="ALT_HOLD")
1137
1138
# hold position in loiter
1139
self.change_mode('AUTOTUNE')
1140
1141
tstart = self.get_sim_time()
1142
self.wait_statustext('AutoTune: Success', timeout=1000)
1143
now = self.get_sim_time()
1144
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
1145
self.autotune_land_and_save_gains()
1146
1147
# tune check
1148
self.set_parameters({
1149
"AUTOTUNE_AXES": 7,
1150
"AUTOTUNE_SEQ": 16,
1151
"AUTOTUNE_FRQ_MIN": 10,
1152
"AUTOTUNE_FRQ_MAX": 80,
1153
})
1154
1155
# Conduct testing from althold
1156
self.takeoff(10, mode="ALT_HOLD")
1157
1158
# hold position in loiter
1159
self.change_mode('AUTOTUNE')
1160
1161
tstart = self.get_sim_time()
1162
self.wait_statustext('AutoTune: Success', timeout=1000)
1163
now = self.get_sim_time()
1164
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
1165
self.land_and_disarm()
1166
1167
def autotune_land_and_save_gains(self):
1168
self.set_rc(3, 1000)
1169
self.context_collect('STATUSTEXT')
1170
self.wait_statustext(r"SIM Hit ground at ([0-9.]+) m/s",
1171
check_context=True,
1172
regex=True)
1173
self.set_rc(8, 1000)
1174
self.wait_disarmed()
1175
1176
def land_and_disarm(self, **kwargs):
1177
super(AutoTestHelicopter, self).land_and_disarm(**kwargs)
1178
self.progress("Killing rotor speed")
1179
self.set_rc(8, 1000)
1180
1181
def do_RTL(self, **kwargs):
1182
super(AutoTestHelicopter, self).do_RTL(**kwargs)
1183
self.progress("Killing rotor speed")
1184
self.set_rc(8, 1000)
1185
1186
def tests(self):
1187
'''return list of all tests'''
1188
ret = vehicle_test_suite.TestSuite.tests(self)
1189
ret.extend([
1190
self.AVCMission,
1191
self.RotorRunup,
1192
self.PosHoldTakeOff,
1193
self.StabilizeTakeOff,
1194
self.SplineWaypoint,
1195
self.AutorotationPreArm,
1196
self.Autorotation,
1197
self.ManAutorotation,
1198
self.governortest,
1199
self.FlyEachFrame,
1200
self.AirspeedDrivers,
1201
self.TurbineStart,
1202
self.TurbineCoolDown,
1203
self.NastyMission,
1204
self.PIDNotches,
1205
self.AutoTune,
1206
self.MountFailsafeAction,
1207
])
1208
return ret
1209
1210
def disabled_tests(self):
1211
return {
1212
}
1213
1214