CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

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