Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/autotest/sim_vehicle.py
9726 views
1
#!/usr/bin/env python3
2
3
"""
4
Framework to start a simulated vehicle and connect it to MAVProxy.
5
6
Peter Barker, April 2016
7
based on sim_vehicle.sh by Andrew Tridgell, October 2011
8
9
AP_FLAKE8_CLEAN
10
11
"""
12
13
import atexit
14
import datetime
15
import errno
16
import optparse
17
import os
18
import os.path
19
import re
20
import signal
21
import subprocess
22
import sys
23
import tempfile
24
import textwrap
25
import time
26
import shlex
27
import binascii
28
import math
29
30
from pysim import util
31
from pysim import vehicleinfo
32
33
34
# List of open terminal windows for macosx
35
windowID = []
36
37
autotest_dir = os.path.dirname(os.path.realpath(__file__))
38
root_dir = os.path.realpath(os.path.join(autotest_dir, '../..'))
39
40
try:
41
from pymavlink import mavextra
42
except ImportError:
43
sys.path.append(os.path.join(root_dir, "modules/mavlink"))
44
from pymavlink import mavextra
45
46
os.environ["SIM_VEHICLE_SESSION"] = binascii.hexlify(os.urandom(8)).decode()
47
48
49
class CompatError(Exception):
50
"""A custom exception class to hold state if we encounter the parse
51
error we are looking for"""
52
def __init__(self, error, opts, rargs):
53
Exception.__init__(self, error)
54
self.opts = opts
55
self.rargs = rargs
56
57
58
class CompatOptionParser(optparse.OptionParser):
59
"""An option parser which emulates the behaviour of the old
60
sim_vehicle.sh; if passed -C, the first argument not understood starts
61
a list of arguments that are passed straight to mavproxy
62
"""
63
64
class CustomFormatter(optparse.IndentedHelpFormatter):
65
def __init__(self, *args, **kwargs):
66
optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs)
67
68
# taken and modified from from optparse.py's format_option
69
def format_option_preserve_nl(self, option):
70
# The help for each option consists of two parts:
71
# * the opt strings and metavars
72
# eg. ("-x", or "-fFILENAME, --file=FILENAME")
73
# * the user-supplied help string
74
# eg. ("turn on expert mode", "read data from FILENAME")
75
#
76
# If possible, we write both of these on the same line:
77
# -x turn on expert mode
78
#
79
# But if the opt string list is too long, we put the help
80
# string on a second line, indented to the same column it would
81
# start in if it fit on the first line.
82
# -fFILENAME, --file=FILENAME
83
# read data from FILENAME
84
result = []
85
opts = self.option_strings[option]
86
opt_width = self.help_position - self.current_indent - 2
87
if len(opts) > opt_width:
88
opts = "%*s%s\n" % (self.current_indent, "", opts)
89
else: # start help on same line as opts
90
opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
91
result.append(opts)
92
if option.help:
93
help_text = self.expand_default(option)
94
tw = textwrap.TextWrapper(replace_whitespace=False,
95
initial_indent="",
96
subsequent_indent=" ",
97
width=self.help_width)
98
99
for line in help_text.split("\n"):
100
help_lines = tw.wrap(line)
101
for wline in help_lines:
102
result.extend(["%*s%s\n" % (self.help_position,
103
"",
104
wline)])
105
elif opts[-1] != "\n":
106
result.append("\n")
107
return "".join(result)
108
109
def format_option(self, option):
110
if str(option).find('frame') != -1:
111
return self.format_option_preserve_nl(option)
112
return optparse.IndentedHelpFormatter.format_option(self, option)
113
114
def __init__(self, *args, **kwargs):
115
formatter = CompatOptionParser.CustomFormatter()
116
optparse.OptionParser.__init__(self,
117
*args,
118
formatter=formatter,
119
**kwargs)
120
121
def error(self, error):
122
"""Override default error handler called by
123
optparse.OptionParser.parse_args when a parse error occurs;
124
raise a detailed exception which can be caught
125
"""
126
if error.find("no such option") != -1:
127
raise CompatError(error, self.values, self.rargs)
128
129
optparse.OptionParser.error(self, error)
130
131
def parse_args(self, args=None, values=None):
132
'''Wrap parse_args so we can catch the exception raised upon
133
discovering the known parameter parsing error
134
'''
135
136
try:
137
opts, args = optparse.OptionParser.parse_args(self)
138
except CompatError as e:
139
if not e.opts.sim_vehicle_sh_compatible:
140
print(e)
141
print("Perhaps you want --sim_vehicle_sh_compatible (-C)?")
142
sys.exit(1)
143
if e.opts.mavproxy_args:
144
print("--mavproxy-args not permitted in compat mode")
145
sys.exit(1)
146
147
args = []
148
opts = e.opts
149
mavproxy_args = [str(e)[16:]] # this trims "no such option" off
150
mavproxy_args.extend(e.rargs)
151
opts.ensure_value("mavproxy_args", " ".join(mavproxy_args))
152
153
return opts, args
154
155
156
def cygwin_pidof(proc_name):
157
""" Thanks to kata198 for this:
158
https://github.com/kata198/cygwin-ps-misc/blob/master/pidof
159
"""
160
pipe = subprocess.Popen("ps -ea | grep " + proc_name,
161
shell=True,
162
stdout=subprocess.PIPE)
163
output_lines = pipe.stdout.read().decode('utf-8').replace("\r", "").split("\n")
164
ret = pipe.wait()
165
pids = []
166
if ret != 0:
167
# No results
168
return []
169
for line in output_lines:
170
if not line:
171
continue
172
line_split = [item for item in line.split(' ') if item]
173
cmd = line_split[-1].split('/')[-1]
174
if cmd == proc_name:
175
try:
176
pid = int(line_split[0].strip())
177
except Exception:
178
pid = int(line_split[1].strip())
179
if pid not in pids:
180
pids.append(pid)
181
return pids
182
183
184
def under_cygwin():
185
"""Return if Cygwin binary exist"""
186
return os.path.exists("/usr/bin/cygstart")
187
188
189
def under_macos():
190
return sys.platform == 'darwin'
191
192
193
def under_vagrant():
194
return os.path.isdir("/vagrant")
195
196
197
def under_wsl2():
198
import platform
199
return 'microsoft-standard-WSL2' in platform.release()
200
201
202
def wsl2_host_ip():
203
if not under_wsl2() or cmd_opts.no_wsl2_network:
204
return None
205
206
pipe = subprocess.Popen("ip route show default | awk '{print $3}'",
207
shell=True,
208
stdout=subprocess.PIPE)
209
output_lines = pipe.stdout.read().decode('utf-8').strip(' \r\n')
210
ret = pipe.wait()
211
212
if ret != 0:
213
# Command exited with an error. The output it generated probably isn't what we're expecting
214
return None
215
216
if not output_lines:
217
# No output detected, maybe there's no nameserver or WSL2 has some abnormal firewalls/network settings?
218
return None
219
220
return str(output_lines)
221
222
223
def kill_tasks_cygwin(victims):
224
"""Shell out to ps -ea to find processes to kill"""
225
for victim in list(victims):
226
pids = cygwin_pidof(victim)
227
# progress("pids for (%s): %s" %
228
# (victim,",".join([ str(p) for p in pids])))
229
for apid in pids:
230
os.kill(apid, signal.SIGKILL)
231
232
233
def kill_tasks_macos():
234
for window in windowID:
235
cmd = ("osascript -e \'tell application \"Terminal\" to close "
236
"(window(get index of window id %s))\'" % window)
237
os.system(cmd)
238
239
240
def kill_tasks_psutil(victims):
241
"""Use the psutil module to kill tasks by name. Sadly, this module is
242
not available on Windows, but when it is we should be able to *just*
243
use this routine"""
244
import psutil
245
for proc in psutil.process_iter():
246
pdict = proc.as_dict(attrs=['environ', 'status'])
247
if pdict['status'] == psutil.STATUS_ZOMBIE:
248
continue
249
if pdict['environ'] is not None:
250
if pdict['environ'].get('SIM_VEHICLE_SESSION') == os.environ['SIM_VEHICLE_SESSION']:
251
proc.kill()
252
253
254
def kill_tasks_pkill(victims):
255
"""Shell out to pkill(1) to kill processed by name"""
256
for victim in victims: # pkill takes a single pattern, so iterate
257
cmd = ["pkill", victim[:15]] # pkill matches only first 15 characters
258
run_cmd_blocking("pkill", cmd, quiet=True)
259
260
261
def kill_tasks():
262
"""Clean up stray processes by name. This is a shotgun approach"""
263
progress("Killing tasks")
264
265
if cmd_opts.coverage:
266
import psutil
267
for proc in psutil.process_iter(['pid', 'name', 'environ']):
268
if proc.name() not in ["arducopter", "ardurover", "arduplane", "ardusub", "antennatracker"]:
269
# only kill vehicle that way
270
continue
271
if os.environ['SIM_VEHICLE_SESSION'] not in proc.environ().get('SIM_VEHICLE_SESSION'):
272
# only kill vehicle launched with sim_vehicle.py that way
273
continue
274
proc.terminate()
275
progress("Waiting SITL to exit cleanly and write coverage .gcda")
276
try:
277
proc.wait(timeout=30)
278
progress("Done")
279
except psutil.TimeoutExpired:
280
progress("SITL doesn't want to exit cleaning, killing ...")
281
proc.kill()
282
283
try:
284
victim_names = {
285
'JSBSim',
286
'lt-JSBSim',
287
'ArduPlane.elf',
288
'ArduCopter.elf',
289
'ArduSub.elf',
290
'Rover.elf',
291
'AntennaTracker.elf',
292
'JSBSIm.exe',
293
'MAVProxy.exe',
294
'runsim.py',
295
'AntennaTracker.elf',
296
'scrimmage',
297
'ardurover',
298
'arduplane',
299
'arducopter'
300
}
301
for vehicle in vinfo.options:
302
for frame in vinfo.options[vehicle]["frames"]:
303
frame_info = vinfo.options[vehicle]["frames"][frame]
304
if "waf_target" not in frame_info:
305
continue
306
exe_name = os.path.basename(frame_info["waf_target"])
307
victim_names.add(exe_name)
308
309
if under_cygwin():
310
return kill_tasks_cygwin(victim_names)
311
if under_macos() and os.environ.get('DISPLAY'):
312
# use special macos kill routine if Display is on
313
return kill_tasks_macos()
314
315
try:
316
kill_tasks_psutil(victim_names)
317
except ImportError:
318
kill_tasks_pkill(victim_names)
319
except Exception as e:
320
progress("kill_tasks failed: {}".format(str(e)))
321
322
323
def progress(text):
324
"""Display sim_vehicle progress text"""
325
print("SIM_VEHICLE: " + text)
326
327
328
def wait_unlimited():
329
"""Wait until signal received"""
330
while True:
331
time.sleep(600)
332
333
334
vinfo = vehicleinfo.VehicleInfo()
335
336
337
def do_build(opts, frame_options):
338
"""Build sitl using waf"""
339
progress("WAF build")
340
341
old_dir = os.getcwd()
342
os.chdir(root_dir)
343
344
waf_light = os.path.join(root_dir, "modules/waf/waf-light")
345
346
configure_target = frame_options.get('configure_target', 'sitl')
347
348
cmd_configure = [waf_light, "configure", "--board", configure_target]
349
if opts.debug:
350
cmd_configure.append("--debug")
351
352
if opts.coverage:
353
cmd_configure.append("--coverage")
354
355
if opts.enable_onvif and 'antennatracker' in frame_options["waf_target"]:
356
cmd_configure.append("--enable-onvif")
357
358
if opts.OSD:
359
cmd_configure.append("--enable-sfml")
360
cmd_configure.append("--sitl-osd")
361
362
if opts.OSDMSP:
363
cmd_configure.append("--osd")
364
365
if opts.rgbled:
366
cmd_configure.append("--enable-sfml")
367
cmd_configure.append("--sitl-rgbled")
368
369
if opts.tonealarm:
370
cmd_configure.append("--enable-sfml-audio")
371
372
if opts.math_check_indexes:
373
cmd_configure.append("--enable-math-check-indexes")
374
375
if opts.enable_ekf2:
376
cmd_configure.append("--enable-EKF2")
377
378
if opts.disable_ekf3:
379
cmd_configure.append("--disable-EKF3")
380
381
if opts.postype_single:
382
cmd_configure.append("--postype-single")
383
384
if opts.ekf_double:
385
cmd_configure.append("--ekf-double")
386
387
if opts.ekf_single:
388
cmd_configure.append("--ekf-single")
389
390
if opts.force_32bit:
391
cmd_configure.append("--force-32bit")
392
393
if opts.ubsan:
394
cmd_configure.append("--ubsan")
395
396
if opts.ubsan_abort:
397
cmd_configure.append("--ubsan-abort")
398
399
if opts.num_aux_imus:
400
cmd_configure.append("--num-aux-imus=%s" % opts.num_aux_imus)
401
402
for nv in opts.define:
403
cmd_configure.append("--define=%s" % nv)
404
405
if opts.enable_DDS:
406
cmd_configure.append("--enable-DDS")
407
408
if opts.disable_networking:
409
cmd_configure.append("--disable-networking")
410
411
if opts.enable_ppp:
412
cmd_configure.append("--enable-PPP")
413
414
if opts.enable_networking_tests:
415
cmd_configure.append("--enable-networking-tests")
416
417
pieces = [shlex.split(x) for x in opts.waf_configure_args]
418
for piece in pieces:
419
cmd_configure.extend(piece)
420
421
if not cmd_opts.no_configure:
422
run_cmd_blocking("Configure waf", cmd_configure, check=True)
423
424
if opts.clean:
425
run_cmd_blocking("Building clean", [waf_light, "clean"])
426
427
print(frame_options)
428
cmd_build = [waf_light, "build", "--target", frame_options["waf_target"]]
429
if opts.jobs is not None:
430
cmd_build += ['-j', str(opts.jobs)]
431
pieces = [shlex.split(x) for x in opts.waf_build_args]
432
for piece in pieces:
433
cmd_build.extend(piece)
434
435
_, sts = run_cmd_blocking("Building", cmd_build)
436
437
if sts != 0: # build failed
438
if opts.rebuild_on_failure:
439
progress("Build failed; cleaning and rebuilding")
440
run_cmd_blocking("Building clean", [waf_light, "clean"])
441
442
_, sts = run_cmd_blocking("Building", cmd_build)
443
if sts != 0:
444
progress("Build failed")
445
sys.exit(1)
446
else:
447
progress("Build failed")
448
sys.exit(1)
449
450
os.chdir(old_dir)
451
452
453
def do_build_parameters(vehicle):
454
# build succeeded
455
# now build parameters
456
progress("Building fresh parameter descriptions")
457
param_parse_path = os.path.join(
458
autotest_dir, "param_metadata/param_parse.py")
459
cmd_param_build = ["python", param_parse_path, '--vehicle', vehicle]
460
461
_, sts = run_cmd_blocking("Building fresh params", cmd_param_build)
462
if sts != 0:
463
progress("Parameter build failed")
464
sys.exit(1)
465
466
467
def get_user_locations_path():
468
'''The user locations.txt file is located by default in
469
$XDG_CONFIG_DIR/ardupilot/locations.txt. If $XDG_CONFIG_DIR is
470
not defined, we look in $HOME/.config/ardupilot/locations.txt. If
471
$HOME is not defined, we look in ./.config/ardupilot/locations.txt.'''
472
473
config_dir = os.environ.get(
474
'XDG_CONFIG_DIR',
475
os.path.join(os.environ.get('HOME', '.'), '.config'))
476
477
user_locations_path = os.path.join(
478
config_dir, 'ardupilot', 'locations.txt')
479
480
return user_locations_path
481
482
483
def find_offsets(instances, file_path):
484
offsets = {}
485
swarminit_filepath = os.path.join(autotest_dir, "swarminit.txt")
486
comment_regex = re.compile(r"\s*#.*")
487
for path in [file_path, swarminit_filepath]:
488
if os.path.isfile(path):
489
with open(path, 'r') as fd:
490
for line in fd:
491
line = re.sub(comment_regex, "", line)
492
line = line.rstrip("\n")
493
if len(line) == 0:
494
continue
495
(instance, offset) = line.split("=")
496
instance = (int)(instance)
497
if (instance not in offsets) and (instance in instances):
498
offsets[instance] = [(float)(x) for x in offset.split(",")]
499
continue
500
if len(offsets) == len(instances):
501
return offsets
502
if len(offsets) == len(instances):
503
return offsets
504
for instance in instances:
505
if instance not in offsets:
506
offsets[instance] = [90.0, 20.0 * instance, 0.0, None]
507
return offsets
508
509
510
def find_geocoder_location(locname):
511
'''find a location using geocoder and SRTM'''
512
try:
513
import geocoder
514
except ImportError:
515
print("geocoder not installed")
516
return None
517
# Step 1: Attempt the lookup
518
try:
519
j = geocoder.osm(locname)
520
except Exception as e:
521
# Handle network/blocked errors (like 403)
522
err_msg = str(e)
523
if '403' in err_msg:
524
print(f"geocoder access denied (HTTP 403) for '{locname}'")
525
else:
526
print(f"geocoder error: {err_msg}")
527
return None
528
# Step 2: Validate the response object (Handles status_code 403 if no exception was raised)
529
status_code = getattr(j, 'status_code', None)
530
if status_code == 403:
531
print(f"geocoder access denied (HTTP 403) for '{locname}'")
532
return None
533
534
if j is None or not hasattr(j, 'lat') or j.lat is None:
535
print(f"geocoder failed to find '{locname}'")
536
return None
537
# Step 3: Success logic
538
lat, lon = j.lat, j.lng
539
540
from MAVProxy.modules.mavproxy_map import srtm
541
downloader = srtm.SRTMDownloader()
542
downloader.loadFileList()
543
start = time.time()
544
alt = None
545
while time.time() - start < 5:
546
tile = downloader.getTile(int(math.floor(lat)), int(math.floor(lon)))
547
if tile:
548
alt = tile.getAltitudeFromLatLon(lat, lon)
549
break
550
if alt is None:
551
print(f"timed out getting altitude for '{locname}'")
552
return None
553
return [lat, lon, alt, 0.0]
554
555
556
def find_location_by_name(locname):
557
"""Search locations.txt for locname, return GPS coords"""
558
locations_userpath = os.environ.get('ARDUPILOT_LOCATIONS',
559
get_user_locations_path())
560
locations_filepath = os.path.join(autotest_dir, "locations.txt")
561
comment_regex = re.compile(r"\s*#.*")
562
for path in [locations_userpath, locations_filepath]:
563
if not os.path.isfile(path):
564
continue
565
with open(path, 'r') as fd:
566
for line in fd:
567
line = re.sub(comment_regex, "", line)
568
line = line.rstrip("\n")
569
if len(line) == 0:
570
continue
571
(name, loc) = line.split("=")
572
if name == locname:
573
return [(float)(x) for x in loc.split(",")]
574
575
# fallback to geocoder if available
576
loc = find_geocoder_location(locname)
577
if loc is None:
578
sys.exit(1)
579
return loc
580
581
582
def find_spawns(loc, offsets):
583
lat, lon, alt, heading = loc
584
spawns = {}
585
for k in offsets:
586
(x, y, z, head) = offsets[k]
587
if head is None:
588
head = heading
589
g = mavextra.gps_offset(lat, lon, x, y)
590
spawns[k] = ",".join([str(g[0]), str(g[1]), str(alt+z), str(head)])
591
return spawns
592
593
594
def progress_cmd(what, cmd):
595
"""Print cmd in a way a user could cut-and-paste to get the same effect"""
596
progress(what)
597
shell_text = "%s" % (" ".join(['"%s"' % x for x in cmd]))
598
progress(shell_text)
599
600
601
def run_cmd_blocking(what, cmd, quiet=False, check=False, **kw):
602
if not quiet:
603
progress_cmd(what, cmd)
604
605
try:
606
p = subprocess.Popen(cmd, **kw)
607
ret = os.waitpid(p.pid, 0)
608
except Exception as e:
609
print("[%s] An exception has occurred with command: '%s'" % (what, (' ').join(cmd)))
610
print(e)
611
sys.exit(1)
612
613
_, sts = ret
614
if check and sts != 0:
615
progress("(%s) exited with code %d" % (what, sts,))
616
sys.exit(1)
617
return ret
618
619
620
def run_in_terminal_window(name, cmd, **kw):
621
622
"""Execute the run_in_terminal_window.sh command for cmd"""
623
runme = [os.path.join(autotest_dir, "run_in_terminal_window.sh"), name]
624
runme.extend(cmd)
625
progress_cmd("Run " + name, runme)
626
627
if under_macos() and os.environ.get('DISPLAY'):
628
# on MacOS record the window IDs so we can close them later
629
out = subprocess.Popen(runme, stdout=subprocess.PIPE, **kw).communicate()[0]
630
out = out.decode('utf-8')
631
p = re.compile('tab 1 of window id (.*)')
632
633
tstart = time.time()
634
while time.time() - tstart < 5:
635
tabs = p.findall(out)
636
637
if len(tabs) > 0:
638
break
639
640
time.sleep(0.1)
641
# sleep for extra 2 seconds for application to start
642
time.sleep(2)
643
if len(tabs) > 0:
644
windowID.append(tabs[0])
645
else:
646
progress("Cannot find %s process terminal" % name)
647
else:
648
subprocess.Popen(runme, **kw)
649
650
651
tracker_serial0 = None # blemish
652
653
654
def start_antenna_tracker(opts):
655
"""Compile and run the AntennaTracker, add tracker to mavproxy"""
656
657
global tracker_serial0
658
progress("Preparing antenna tracker")
659
tracker_home = find_location_by_name(opts.tracker_location)
660
vehicledir = os.path.join(autotest_dir, "../../" + "AntennaTracker")
661
options = vinfo.options["AntennaTracker"]
662
tracker_default_frame = options["default_frame"]
663
tracker_frame_options = options["frames"][tracker_default_frame]
664
do_build(opts, tracker_frame_options)
665
tracker_instance = 1
666
oldpwd = os.getcwd()
667
os.chdir(vehicledir)
668
tracker_serial0 = "tcp:127.0.0.1:" + str(5760 + 10 * tracker_instance)
669
binary_basedir = "build/sitl"
670
exe = os.path.join(root_dir,
671
binary_basedir,
672
"bin/antennatracker")
673
run_in_terminal_window("AntennaTracker",
674
["nice",
675
exe,
676
"-I" + str(tracker_instance),
677
"--model=tracker",
678
"--home=" + ",".join([str(x) for x in tracker_home])])
679
os.chdir(oldpwd)
680
681
682
def start_CAN_Periph(opts, frame_info):
683
"""Compile and run the sitl_periph"""
684
685
progress("Preparing sitl_periph_universal")
686
options = vinfo.options["sitl_periph_universal"]['frames']['universal']
687
defaults_path = frame_info.get('periph_params_filename', None)
688
if defaults_path is None:
689
defaults_path = options.get('default_params_filename', None)
690
691
if not isinstance(defaults_path, list):
692
defaults_path = [defaults_path]
693
694
# add in path and make a comma separated list
695
defaults_path = ','.join([util.relcurdir(os.path.join(autotest_dir, p)) for p in defaults_path])
696
697
if not cmd_opts.no_rebuild:
698
do_build(opts, options)
699
exe = os.path.join(root_dir, 'build/sitl_periph_universal', 'bin/AP_Periph')
700
cmd = ["nice"]
701
cmd_name = "sitl_periph_universal"
702
if opts.valgrind:
703
cmd_name += " (valgrind)"
704
cmd.append("valgrind")
705
# adding this option allows valgrind to cope with the overload
706
# of operator new
707
cmd.append("--soname-synonyms=somalloc=nouserintercepts")
708
cmd.append("--track-origins=yes")
709
if opts.gdb or opts.gdb_stopped:
710
cmd_name += " (gdb)"
711
cmd.append("gdb")
712
gdb_commands_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
713
atexit.register(os.unlink, gdb_commands_file.name)
714
gdb_commands_file.write("set pagination off\n")
715
if not opts.gdb_stopped:
716
gdb_commands_file.write("r\n")
717
gdb_commands_file.close()
718
cmd.extend(["-x", gdb_commands_file.name])
719
cmd.append("--args")
720
cmd.append(exe)
721
if defaults_path is not None:
722
cmd.append("--defaults")
723
cmd.append(defaults_path)
724
run_in_terminal_window(cmd_name, cmd)
725
726
727
def start_vehicle(binary, opts, stuff, spawns=None):
728
"""Run the ArduPilot binary"""
729
730
cmd_name = opts.vehicle
731
cmd = []
732
if opts.valgrind:
733
cmd_name += " (valgrind)"
734
cmd.append("valgrind")
735
# adding this option allows valgrind to cope with the overload
736
# of operator new
737
cmd.append("--soname-synonyms=somalloc=nouserintercepts")
738
cmd.append("--track-origins=yes")
739
if opts.callgrind:
740
cmd_name += " (callgrind)"
741
cmd.append("valgrind")
742
cmd.append("--tool=callgrind")
743
if opts.gdb or opts.gdb_stopped:
744
cmd_name += " (gdb)"
745
cmd.append("gdb")
746
gdb_commands_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
747
atexit.register(os.unlink, gdb_commands_file.name)
748
749
for breakpoint in opts.breakpoint:
750
gdb_commands_file.write("b %s\n" % (breakpoint,))
751
if opts.disable_breakpoints:
752
gdb_commands_file.write("disable\n")
753
gdb_commands_file.write("set pagination off\n")
754
if not opts.gdb_stopped:
755
gdb_commands_file.write("r\n")
756
gdb_commands_file.close()
757
cmd.extend(["-x", gdb_commands_file.name])
758
cmd.append("--args")
759
elif opts.lldb or opts.lldb_stopped:
760
cmd_name += " (lldb)"
761
cmd.append("lldb")
762
lldb_commands_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
763
atexit.register(os.unlink, lldb_commands_file.name)
764
765
for breakpoint in opts.breakpoint:
766
lldb_commands_file.write("b %s\n" % (breakpoint,))
767
if not opts.lldb_stopped:
768
lldb_commands_file.write("process launch\n")
769
lldb_commands_file.close()
770
cmd.extend(["-s", lldb_commands_file.name])
771
cmd.append("--")
772
if opts.strace:
773
cmd_name += " (strace)"
774
cmd.append("strace")
775
strace_options = ['-o', binary + '.strace', '-s', '8000', '-ttt']
776
cmd.extend(strace_options)
777
778
cmd.append(binary)
779
if opts.wipe_eeprom:
780
cmd.append("-w")
781
cmd.extend(["--model", stuff["model"]])
782
cmd.extend(["--speedup", str(opts.speedup)])
783
if opts.sysid is not None:
784
cmd.extend(["--sysid", str(opts.sysid)])
785
if opts.slave is not None:
786
cmd.extend(["--slave", str(opts.slave)])
787
if opts.enable_fgview:
788
cmd.extend(["--enable-fgview"])
789
if opts.sitl_instance_args:
790
# this could be a lot better:
791
cmd.extend(opts.sitl_instance_args.split(" "))
792
if opts.mavlink_gimbal:
793
cmd.append("--gimbal")
794
path = None
795
if "default_params_filename" in stuff:
796
paths = stuff["default_params_filename"]
797
if not isinstance(paths, list):
798
paths = [paths]
799
paths = [util.relcurdir(os.path.join(autotest_dir, x)) for x in paths]
800
for x in paths:
801
if not os.path.isfile(x):
802
print("The parameter file (%s) does not exist" % (x,))
803
sys.exit(1)
804
if cmd_opts.count > 1 or opts.auto_sysid:
805
# we are in a subdirectory when using -n
806
paths = [os.path.join("..", x) for x in paths]
807
path = ",".join(paths)
808
progress("Using defaults from (%s)" % (path,))
809
if opts.flash_storage:
810
cmd.append("--set-storage-flash-enabled 1")
811
cmd.append("--set-storage-posix-enabled 0")
812
elif opts.fram_storage:
813
cmd.append("--set-storage-fram-enabled 1")
814
cmd.append("--set-storage-posix-enabled 0")
815
if opts.add_param_file:
816
for file in opts.add_param_file:
817
if not os.path.isfile(file):
818
print("The parameter file (%s) does not exist" %
819
(file,))
820
sys.exit(1)
821
822
if path is not None:
823
path += "," + str(file)
824
else:
825
path = str(file)
826
827
progress("Adding parameters from (%s)" % (str(file),))
828
if opts.param:
829
param_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
830
atexit.register(os.unlink, param_file.name)
831
param_dir = os.path.join(param_file.name)
832
single_params = []
833
for p in opts.param:
834
single_params.extend(p.split(','))
835
for sp in single_params:
836
sp.replace("=", " ")
837
sp = sp.strip()
838
param_file.write(f"{sp}\n")
839
if path is not None:
840
path += "," + str(param_dir)
841
else:
842
path = str(param_dir)
843
844
if opts.OSDMSP:
845
path += "," + os.path.join(root_dir, "libraries/AP_MSP/Tools/osdtest.parm")
846
path += "," + os.path.join(autotest_dir, "default_params/msposd.parm")
847
subprocess.Popen([os.path.join(root_dir, "libraries/AP_MSP/Tools/msposd.py")])
848
849
if path is not None and len(path) > 0:
850
cmd.extend(["--defaults", path])
851
852
if cmd_opts.start_time is not None:
853
# Parse start_time into a double precision number specifying seconds since 1900.
854
try:
855
start_time_UTC = time.mktime(datetime.datetime.strptime(cmd_opts.start_time, '%Y-%m-%d-%H:%M').timetuple())
856
except Exception:
857
print("Incorrect start time format - require YYYY-MM-DD-HH:MM (given %s)" % cmd_opts.start_time)
858
sys.exit(1)
859
860
cmd.append("--start-time=%d" % start_time_UTC)
861
862
cmd.append("--sim-address=%s" % cmd_opts.sim_address)
863
864
old_dir = os.getcwd()
865
for i, i_dir in zip(instances, instance_dir):
866
c = ["-I" + str(i)]
867
if spawns is not None:
868
c.extend(["--home", spawns[i]])
869
if opts.mcast:
870
c.extend(["--serial0", "mcast:"])
871
elif opts.udp:
872
c.extend(["--serial0", "udpclient:127.0.0.1:" + str(5760+i*10)])
873
if opts.auto_sysid:
874
if opts.sysid is not None:
875
raise ValueError("Can't use auto-sysid and sysid together")
876
sysid = i + 1
877
# Take 0-based logging into account
878
if sysid < 1 or sysid > 255:
879
raise ValueError("Invalid system id %d" % sysid)
880
c.extend(["--sysid", str(sysid)])
881
882
os.chdir(i_dir)
883
run_in_terminal_window(cmd_name, cmd + c)
884
os.chdir(old_dir)
885
886
887
def start_mavproxy(opts, stuff):
888
"""Run mavproxy"""
889
# FIXME: would be nice to e.g. "mavproxy.mavproxy(....).run"
890
# rather than shelling out
891
892
extra_cmd = ""
893
cmd = []
894
if under_cygwin():
895
cmd.append("/usr/bin/cygstart")
896
cmd.append("-w")
897
cmd.append("mavproxy.exe")
898
else:
899
cmd.append("mavproxy.py")
900
901
if opts.valgrind:
902
cmd.extend(['--retries', '10'])
903
else:
904
cmd.extend(['--retries', '5'])
905
906
if opts.mcast:
907
cmd.extend(["--master", "mcast:"])
908
909
# returns a valid IP of the host windows computer if we're WSL2.
910
# This is run before the loop so it only runs once
911
wsl2_host_ip_str = wsl2_host_ip()
912
913
for i in instances:
914
if not opts.no_extra_ports:
915
ports = [14550 + 10 * i]
916
for port in ports:
917
if under_vagrant():
918
# We're running inside of a vagrant guest; forward our
919
# mavlink out to the containing host OS
920
cmd.extend(["--out", "10.0.2.2:" + str(port)])
921
elif wsl2_host_ip_str:
922
# We're running WSL2; forward our
923
# mavlink out to the containing host Windows OS
924
cmd.extend(["--out", str(wsl2_host_ip_str) + ":" + str(port)])
925
else:
926
cmd.extend(["--out", "127.0.0.1:" + str(port)])
927
928
if not opts.mcast:
929
if opts.udp:
930
cmd.extend(["--master", ":" + str(5760 + 10 * i)])
931
else:
932
cmd.extend(["--master", "tcp:127.0.0.1:" + str(5760 + 10 * i)])
933
if stuff["sitl-port"] and not opts.no_rcin:
934
cmd.extend(["--sitl", "127.0.0.1:" + str(5501 + 10 * i)])
935
936
if opts.tracker:
937
cmd.extend(["--load-module", "tracker"])
938
# tracker_serial0 is set when we start the tracker...
939
extra_cmd += ("module load map;"
940
"tracker set port %s; "
941
"tracker start; "
942
"tracker arm;" % (tracker_serial0,))
943
944
if opts.mavlink_gimbal:
945
cmd.extend(["--load-module", "gimbal"])
946
947
if "extra_mavlink_cmds" in stuff:
948
extra_cmd += " " + stuff["extra_mavlink_cmds"]
949
950
# Parsing the arguments to pass to mavproxy, split args on space
951
# and "=" signs and ignore those signs within quotation marks
952
if opts.mavproxy_args:
953
# It would be great if this could be done with regex
954
mavargs = opts.mavproxy_args.split(" ")
955
# Find the arguments with '=' in them and split them up
956
for i, x in enumerate(mavargs):
957
if '=' in x:
958
mavargs[i] = x.split('=')[0]
959
mavargs.insert(i+1, x.split('=')[1])
960
# Use this flag to tell if parsing character in between a pair
961
# of quotation marks
962
inString = False
963
beginStringIndex = []
964
endStringIndex = []
965
# Iterate through the arguments, looking for the arguments
966
# that begin with a quotation mark and the ones that end with
967
# a quotation mark
968
for i, x in enumerate(mavargs):
969
if not inString and x[0] == "\"":
970
beginStringIndex.append(i)
971
mavargs[i] = x[1:]
972
inString = True
973
elif inString and x[-1] == "\"":
974
endStringIndex.append(i)
975
inString = False
976
mavargs[i] = x[:-1]
977
# Replace the list items with one string to be passed into mavproxy
978
for begin, end in zip(beginStringIndex, endStringIndex):
979
replacement = " ".join(mavargs[begin:end+1])
980
mavargs[begin] = replacement
981
mavargs = mavargs[0:begin+1] + mavargs[end+1:]
982
cmd.extend(mavargs)
983
984
# compatibility pass-through parameters (for those that don't want
985
# to use -C :-)
986
for out in opts.out:
987
cmd.extend(['--out', out])
988
if opts.map:
989
cmd.append('--map')
990
if opts.console:
991
cmd.append('--console')
992
if opts.aircraft is not None:
993
cmd.extend(['--aircraft', opts.aircraft])
994
if opts.moddebug:
995
cmd.append('--moddebug=%u' % opts.moddebug)
996
if opts.mavcesium:
997
cmd.extend(["--load-module", "cesium"])
998
999
if opts.fresh_params:
1000
# these were built earlier:
1001
path = os.path.join(os.getcwd(), "apm.pdef.xml")
1002
cmd.extend(['--load-module', 'param:{"xml-filepath":"%s"}' % path])
1003
1004
if len(extra_cmd):
1005
cmd.extend(['--cmd', extra_cmd])
1006
1007
# add Tools/mavproxy_modules to PYTHONPATH in autotest so we can
1008
# find random MAVProxy helper modules like sitl_calibration
1009
local_mp_modules_dir = os.path.abspath(
1010
os.path.join(__file__, '..', '..', 'mavproxy_modules'))
1011
env = dict(os.environ)
1012
old = env.get('PYTHONPATH', None)
1013
env['PYTHONPATH'] = local_mp_modules_dir
1014
if old is not None:
1015
env['PYTHONPATH'] += os.path.pathsep + old
1016
1017
run_cmd_blocking("Run MavProxy", cmd, env=env)
1018
progress("MAVProxy exited")
1019
1020
if opts.gdb:
1021
# in the case that MAVProxy exits (as opposed to being
1022
# killed), restart it if we are running under GDB. This
1023
# allows ArduPilot to stop (eg. via a very early panic call)
1024
# and have you debugging session not be killed.
1025
while True:
1026
progress("Running under GDB; restarting MAVProxy")
1027
run_cmd_blocking("Run MavProxy", cmd, env=env)
1028
progress("MAVProxy exited; sleeping 10")
1029
time.sleep(10)
1030
1031
1032
vehicle_options_string = '|'.join(vinfo.options.keys())
1033
1034
1035
def generate_frame_help():
1036
ret = ""
1037
for vehicle in vinfo.options:
1038
frame_options = sorted(vinfo.options[vehicle]["frames"].keys())
1039
frame_options_string = '|'.join(frame_options)
1040
ret += "%s: %s\n" % (vehicle, frame_options_string)
1041
return ret
1042
1043
1044
# map from some vehicle aliases back to directory names. APMrover2
1045
# was the old name / directory name for Rover.
1046
vehicle_map = {
1047
"APMrover2": "Rover",
1048
"Copter": "ArduCopter",
1049
"Plane": "ArduPlane",
1050
"Sub": "ArduSub",
1051
"Blimp" : "Blimp",
1052
"Rover": "Rover",
1053
}
1054
# add lower-case equivalents too
1055
for k in list(vehicle_map.keys()):
1056
vehicle_map[k.lower()] = vehicle_map[k]
1057
1058
# define and run parser
1059
parser = CompatOptionParser(
1060
"sim_vehicle.py",
1061
epilog=""
1062
"eeprom.bin in the starting directory contains the parameters for your "
1063
"simulated vehicle. Always start from the same directory. It is "
1064
"recommended that you start in the main vehicle directory for the vehicle "
1065
"you are simulating, for example, start in the ArduPlane directory to "
1066
"simulate ArduPlane")
1067
1068
vehicle_choices = list(vinfo.options.keys())
1069
1070
# add vehicle aliases to argument parser options:
1071
for c in vehicle_map.keys():
1072
vehicle_choices.append(c)
1073
1074
parser.add_option("-v", "--vehicle",
1075
type='choice',
1076
default=None,
1077
help="vehicle type (%s)" % vehicle_options_string,
1078
choices=vehicle_choices)
1079
parser.add_option("-f", "--frame", type='string', default=None, help="""set vehicle frame type
1080
1081
%s""" % (generate_frame_help()))
1082
1083
parser.add_option("--vehicle-binary",
1084
default=None,
1085
help="vehicle binary path")
1086
1087
parser.add_option("-C", "--sim_vehicle_sh_compatible",
1088
action='store_true',
1089
default=False,
1090
help="be compatible with the way sim_vehicle.sh works; "
1091
"make this the first option")
1092
1093
parser.add_option("-P", "--param",
1094
default=None,
1095
action='append',
1096
help="set some param with the format PARAM=VALUE")
1097
1098
group_build = optparse.OptionGroup(parser, "Build options")
1099
group_build.add_option("-N", "--no-rebuild",
1100
action='store_true',
1101
default=False,
1102
help="don't rebuild before starting ardupilot")
1103
group_build.add_option("--no-configure",
1104
action='store_true',
1105
default=False,
1106
help="don't run waf configure before building")
1107
group_build.add_option("-D", "--debug",
1108
action='store_true',
1109
default=False,
1110
help="build with debugging")
1111
group_build.add_option("-c", "--clean",
1112
action='store_true',
1113
default=False,
1114
help="do a make clean before building")
1115
group_build.add_option("-j", "--jobs",
1116
default=None,
1117
type='int',
1118
help="number of processors to use during build "
1119
"(default for waf : number of processor, for make : 1)")
1120
group_build.add_option("-b", "--build-target",
1121
default=None,
1122
type='string',
1123
help="override SITL build target")
1124
group_build.add_option("--enable-math-check-indexes",
1125
default=False,
1126
action="store_true",
1127
dest="math_check_indexes",
1128
help="enable checking of math indexes")
1129
group_build.add_option("", "--force-32bit",
1130
default=False,
1131
action='store_true',
1132
dest="force_32bit",
1133
help="compile sitl using 32-bit")
1134
group_build.add_option("", "--configure-define",
1135
default=[],
1136
action='append',
1137
dest="define",
1138
help="create a preprocessor define")
1139
group_build.add_option("", "--rebuild-on-failure",
1140
dest="rebuild_on_failure",
1141
action='store_true',
1142
default=False,
1143
help="if build fails, do not clean and rebuild")
1144
group_build.add_option("", "--waf-configure-arg",
1145
action="append",
1146
dest="waf_configure_args",
1147
type="string",
1148
default=[],
1149
help="extra arguments to pass to waf in configure step")
1150
group_build.add_option("", "--waf-build-arg",
1151
action="append",
1152
dest="waf_build_args",
1153
type="string",
1154
default=[],
1155
help="extra arguments to pass to waf in its build step")
1156
group_build.add_option("", "--coverage",
1157
action='store_true',
1158
default=False,
1159
help="use coverage build")
1160
group_build.add_option("", "--ubsan",
1161
default=False,
1162
action='store_true',
1163
dest="ubsan",
1164
help="compile sitl with undefined behaviour sanitiser")
1165
group_build.add_option("", "--ubsan-abort",
1166
default=False,
1167
action='store_true',
1168
dest="ubsan_abort",
1169
help="compile sitl with undefined behaviour sanitiser and abort on error")
1170
group_build.add_option("--num-aux-imus",
1171
dest="num_aux_imus",
1172
default=0,
1173
type='int',
1174
help='number of auxiliary IMUs to simulate')
1175
parser.add_option_group(group_build)
1176
1177
group_sim = optparse.OptionGroup(parser, "Simulation options")
1178
group_sim.add_option("-I", "--instance",
1179
default=0,
1180
type='int',
1181
help="instance of simulator")
1182
group_sim.add_option("-n", "--count",
1183
type='int',
1184
default=1,
1185
help="vehicle count; if this is specified, -I is used as a base-value")
1186
group_sim.add_option("-i", "--instances",
1187
default=None,
1188
type='string',
1189
help="a space delimited list of instances to spawn; if specified, overrides -I and -n.")
1190
group_sim.add_option("-V", "--valgrind",
1191
action='store_true',
1192
default=False,
1193
help="enable valgrind for memory access checking (slow!)")
1194
group_sim.add_option("", "--callgrind",
1195
action='store_true',
1196
default=False,
1197
help="enable valgrind for performance analysis (slow!!)")
1198
group_sim.add_option("-T", "--tracker",
1199
action='store_true',
1200
default=False,
1201
help="start an antenna tracker instance")
1202
group_sim.add_option("", "--enable-onvif",
1203
action="store_true",
1204
help="enable onvif camera control sim using AntennaTracker")
1205
group_sim.add_option("", "--can-peripherals",
1206
action='store_true',
1207
default=False,
1208
help="start a DroneCAN peripheral instance")
1209
group_sim.add_option("-A", "--sitl-instance-args",
1210
type='string',
1211
default=None,
1212
help="pass arguments to SITL instance")
1213
group_sim.add_option("-G", "--gdb",
1214
action='store_true',
1215
default=False,
1216
help="use gdb for debugging ardupilot")
1217
group_sim.add_option("-g", "--gdb-stopped",
1218
action='store_true',
1219
default=False,
1220
help="use gdb for debugging ardupilot (no auto-start)")
1221
group_sim.add_option("--lldb",
1222
action='store_true',
1223
default=False,
1224
help="use lldb for debugging ardupilot")
1225
group_sim.add_option("--lldb-stopped",
1226
action='store_true',
1227
default=False,
1228
help="use ldb for debugging ardupilot (no auto-start)")
1229
group_sim.add_option("-d", "--delay-start",
1230
default=0,
1231
type='float',
1232
help="delay start of mavproxy by this number of seconds")
1233
group_sim.add_option("-B", "--breakpoint",
1234
type='string',
1235
action="append",
1236
default=[],
1237
help="add a breakpoint at given location in debugger")
1238
group_sim.add_option("--disable-breakpoints",
1239
default=False,
1240
action='store_true',
1241
help="disable all breakpoints before starting")
1242
group_sim.add_option("-M", "--mavlink-gimbal",
1243
action='store_true',
1244
default=False,
1245
help="enable MAVLink gimbal")
1246
group_sim.add_option("-L", "--location", type='string',
1247
default=None,
1248
help="use start location from "
1249
"Tools/autotest/locations.txt")
1250
group_sim.add_option("-l", "--custom-location",
1251
type='string',
1252
default=None,
1253
help="set custom start location (lat,lon,alt,heading)")
1254
group_sim.add_option("-S", "--speedup",
1255
default=1,
1256
type='int',
1257
help="set simulation speedup (1 for wall clock time)")
1258
group_sim.add_option("-t", "--tracker-location",
1259
default='CMAC_PILOTSBOX',
1260
type='string',
1261
help="set antenna tracker start location")
1262
group_sim.add_option("-w", "--wipe-eeprom",
1263
action='store_true',
1264
default=False, help="wipe EEPROM and reload parameters")
1265
group_sim.add_option("-m", "--mavproxy-args",
1266
default=None,
1267
type='string',
1268
help="additional arguments to pass to mavproxy.py")
1269
group_sim.add_option("", "--scrimmage-args",
1270
default=None,
1271
type='string',
1272
help="arguments used to populate SCRIMMAGE mission (comma-separated). "
1273
"Currently visual_model, motion_model, and terrain are supported. "
1274
"Usage: [instance=]argument=value...")
1275
group_sim.add_option("", "--strace",
1276
action='store_true',
1277
default=False,
1278
help="strace the ArduPilot binary")
1279
group_sim.add_option("", "--model",
1280
type='string',
1281
default=None,
1282
help="Override simulation model to use")
1283
group_sim.add_option("", "--use-dir",
1284
type='string',
1285
default=None,
1286
help="Store SITL state and output in named directory")
1287
group_sim.add_option("", "--no-mavproxy",
1288
action='store_true',
1289
default=False,
1290
help="Don't launch MAVProxy")
1291
group_sim.add_option("", "--fresh-params",
1292
action='store_true',
1293
dest='fresh_params',
1294
default=False,
1295
help="Generate and use local parameter help XML")
1296
group_sim.add_option("", "--mcast",
1297
action="store_true",
1298
default=False,
1299
help="Use multicasting at default 239.255.145.50:14550")
1300
group_sim.add_option("", "--udp",
1301
action="store_true",
1302
default=False,
1303
help="Use UDP on 127.0.0.1:5760")
1304
group_sim.add_option("", "--osd",
1305
action='store_true',
1306
dest='OSD',
1307
default=False,
1308
help="Enable SITL OSD")
1309
group_sim.add_option("", "--osdmsp",
1310
action='store_true',
1311
dest='OSDMSP',
1312
default=False,
1313
help="Enable SITL OSD using MSP")
1314
group_sim.add_option("", "--tonealarm",
1315
action='store_true',
1316
dest='tonealarm',
1317
default=False,
1318
help="Enable SITL ToneAlarm")
1319
group_sim.add_option("", "--rgbled",
1320
action='store_true',
1321
dest='rgbled',
1322
default=False,
1323
help="Enable SITL RGBLed")
1324
group_sim.add_option("", "--add-param-file",
1325
type='string',
1326
action="append",
1327
default=None,
1328
help="Add a parameters file to use")
1329
group_sim.add_option("", "--no-extra-ports",
1330
action='store_true',
1331
dest='no_extra_ports',
1332
default=False,
1333
help="Disable setup of UDP 14550 and 14551 output")
1334
group_sim.add_option("", "--no-wsl2-network",
1335
action='store_true',
1336
dest='no_wsl2_network',
1337
default=False,
1338
help="Disable setup of WSL2 network for output")
1339
group_sim.add_option("-Z", "--swarm",
1340
type='string',
1341
default=None,
1342
help="Specify path of swarminit.txt for shifting spawn location")
1343
group_sim.add_option("", "--auto-offset-line",
1344
type="string",
1345
default=None,
1346
help="Argument of form BEARING,DISTANCE. When running multiple instances, form a line along bearing with an interval of DISTANCE", # NOQA
1347
)
1348
group_sim.add_option("--flash-storage",
1349
action='store_true',
1350
help="use flash storage emulation")
1351
group_sim.add_option("--fram-storage",
1352
action='store_true',
1353
help="use fram storage emulation")
1354
group_sim.add_option("--enable-ekf2",
1355
action='store_true',
1356
help="disable EKF2 in build")
1357
group_sim.add_option("--disable-ekf3",
1358
action='store_true',
1359
help="disable EKF3 in build")
1360
group_sim.add_option("", "--start-time",
1361
default=None,
1362
type='string',
1363
help="specify simulation start time in format YYYY-MM-DD-HH:MM in your local time zone")
1364
group_sim.add_option("", "--sysid",
1365
type='int',
1366
default=None,
1367
help="Set MAV_SYSID")
1368
group_sim.add_option("--postype-single",
1369
action='store_true',
1370
help="force single precision postype_t")
1371
group_sim.add_option("--ekf-double",
1372
action='store_true',
1373
help="use double precision in EKF")
1374
group_sim.add_option("--ekf-single",
1375
action='store_true',
1376
help="use single precision in EKF")
1377
group_sim.add_option("", "--slave",
1378
type='int',
1379
default=0,
1380
help="Set the number of JSON slave")
1381
group_sim.add_option("", "--auto-sysid",
1382
default=False,
1383
action='store_true',
1384
help="Set MAV_SYSID based upon instance number")
1385
group_sim.add_option("", "--sim-address",
1386
type=str,
1387
default="127.0.0.1",
1388
help="IP address of the simulator. Defaults to localhost")
1389
group_sim.add_option("--enable-DDS", action='store_true',
1390
help="Enable the dds client to connect with ROS2/DDS")
1391
group_sim.add_option("--disable-networking", action='store_true',
1392
help="Disable networking APIs")
1393
group_sim.add_option("--enable-ppp", action='store_true',
1394
help="Enable PPP networking")
1395
group_sim.add_option("--enable-networking-tests", action='store_true',
1396
help="Enable networking tests")
1397
group_sim.add_option("--enable-fgview", action='store_true',
1398
help="Enable FlightGear output")
1399
1400
parser.add_option_group(group_sim)
1401
1402
1403
# special-cased parameters for mavproxy, because some people's fingers
1404
# have long memories, and they don't want to use -C :-)
1405
group = optparse.OptionGroup(parser,
1406
"Compatibility MAVProxy options "
1407
"(consider using --mavproxy-args instead)")
1408
group.add_option("", "--out",
1409
default=[],
1410
type='string',
1411
action="append",
1412
help="create an additional mavlink output")
1413
group.add_option("", "--map",
1414
default=False,
1415
action='store_true',
1416
help="load map module on startup")
1417
group.add_option("", "--mavcesium",
1418
default=False,
1419
action='store_true',
1420
help="load MAVCesium module on startup")
1421
1422
group.add_option("", "--console",
1423
default=False,
1424
action='store_true',
1425
help="load console module on startup")
1426
group.add_option("", "--aircraft",
1427
default=None,
1428
help="store state and logs in named directory")
1429
group.add_option("", "--moddebug",
1430
default=0,
1431
type=int,
1432
help="mavproxy module debug")
1433
group.add_option("", "--no-rcin",
1434
action='store_true',
1435
help="disable mavproxy rcin")
1436
parser.add_option_group(group)
1437
1438
group_completion = optparse.OptionGroup(parser, "Completion helpers")
1439
group_completion.add_option("", "--list-vehicle",
1440
action='store_true',
1441
help="List the vehicles")
1442
group_completion.add_option("", "--list-frame",
1443
type='string',
1444
default=None,
1445
help="List the vehicle frames")
1446
parser.add_option_group(group_completion)
1447
1448
cmd_opts, cmd_args = parser.parse_args()
1449
1450
if cmd_opts.list_vehicle:
1451
print(' '.join(vinfo.options.keys()))
1452
sys.exit(1)
1453
if cmd_opts.list_frame:
1454
frame_options = sorted(vinfo.options[cmd_opts.list_frame]["frames"].keys())
1455
frame_options_string = ' '.join(frame_options)
1456
print(frame_options_string)
1457
sys.exit(1)
1458
1459
# clean up processes at exit:
1460
atexit.register(kill_tasks)
1461
1462
progress("Start")
1463
1464
if cmd_opts.sim_vehicle_sh_compatible and cmd_opts.jobs is None:
1465
cmd_opts.jobs = 1
1466
1467
# validate parameters
1468
if cmd_opts.valgrind and (cmd_opts.gdb or cmd_opts.gdb_stopped or cmd_opts.lldb or cmd_opts.lldb_stopped):
1469
print("May not use valgrind with gdb or lldb")
1470
sys.exit(1)
1471
1472
if cmd_opts.valgrind and cmd_opts.callgrind:
1473
print("May not use valgrind with callgrind")
1474
sys.exit(1)
1475
1476
if cmd_opts.strace and (cmd_opts.gdb or cmd_opts.gdb_stopped or cmd_opts.lldb or cmd_opts.lldb_stopped):
1477
print("May not use strace with gdb or lldb")
1478
sys.exit(1)
1479
1480
if (cmd_opts.gdb or cmd_opts.gdb_stopped) and (cmd_opts.lldb or cmd_opts.lldb_stopped):
1481
print("May not use lldb with gdb")
1482
sys.exit(1)
1483
1484
if cmd_opts.instance < 0:
1485
print("May not specify a negative instance ID")
1486
sys.exit(1)
1487
1488
if cmd_opts.count < 1:
1489
print("May not specify a count less than 1")
1490
sys.exit(1)
1491
1492
if cmd_opts.strace and cmd_opts.valgrind:
1493
print("valgrind and strace almost certainly not a good idea")
1494
1495
if cmd_opts.strace and cmd_opts.callgrind:
1496
print("callgrind and strace almost certainly not a good idea")
1497
1498
if cmd_opts.sysid and cmd_opts.auto_sysid:
1499
print("Cannot use auto-sysid together with sysid")
1500
sys.exit(1)
1501
1502
# magically determine vehicle type (if required):
1503
if cmd_opts.vehicle is None:
1504
cwd = os.getcwd()
1505
cmd_opts.vehicle = os.path.basename(cwd)
1506
1507
if cmd_opts.vehicle not in vinfo.options:
1508
# try in parent directories, useful for having config in subdirectories
1509
cwd = os.getcwd()
1510
while cwd:
1511
bname = os.path.basename(cwd)
1512
if not bname:
1513
break
1514
if bname in vinfo.options:
1515
cmd_opts.vehicle = bname
1516
break
1517
cwd = os.path.dirname(cwd)
1518
1519
if cmd_opts.vehicle in vehicle_map:
1520
cmd_opts.vehicle = vehicle_map[cmd_opts.vehicle]
1521
elif cmd_opts.vehicle.lower() in vehicle_map:
1522
cmd_opts.vehicle = vehicle_map[cmd_opts.vehicle.lower()]
1523
1524
# try to validate vehicle
1525
if cmd_opts.vehicle not in vinfo.options:
1526
progress('''
1527
** Is (%s) really your vehicle type?
1528
Perhaps you could try -v %s
1529
You could also try changing directory to e.g. the ArduCopter subdirectory
1530
''' % (cmd_opts.vehicle, vehicle_options_string))
1531
sys.exit(1)
1532
1533
# determine frame options (e.g. build type might be "sitl")
1534
if cmd_opts.frame is None:
1535
cmd_opts.frame = vinfo.options[cmd_opts.vehicle]["default_frame"]
1536
1537
frame_infos = vinfo.options_for_frame(cmd_opts.frame,
1538
cmd_opts.vehicle,
1539
cmd_opts)
1540
1541
vehicle_dir = os.path.realpath(os.path.join(root_dir, cmd_opts.vehicle))
1542
if not os.path.exists(vehicle_dir):
1543
print("vehicle directory (%s) does not exist" % (vehicle_dir,))
1544
sys.exit(1)
1545
1546
if cmd_opts.instances is not None:
1547
instances = set()
1548
for i in cmd_opts.instances.split(' '):
1549
i = (int)(i)
1550
if i < 0:
1551
print("May not specify a negative instance ID")
1552
sys.exit(1)
1553
instances.add(i)
1554
instances = sorted(instances) # to list
1555
else:
1556
instances = range(cmd_opts.instance, cmd_opts.instance + cmd_opts.count)
1557
1558
if cmd_opts.instance == 0:
1559
kill_tasks()
1560
1561
if cmd_opts.tracker:
1562
start_antenna_tracker(cmd_opts)
1563
1564
if cmd_opts.can_peripherals or frame_infos.get('periph_params_filename', None) is not None:
1565
start_CAN_Periph(cmd_opts, frame_infos)
1566
1567
if cmd_opts.custom_location:
1568
location = [(float)(x) for x in cmd_opts.custom_location.split(",")]
1569
progress("Starting up at %s" % (location,))
1570
elif cmd_opts.location is not None:
1571
location = find_location_by_name(cmd_opts.location)
1572
progress("Starting up at %s (%s)" % (location, cmd_opts.location))
1573
else:
1574
progress("Starting up at SITL location")
1575
location = None
1576
if cmd_opts.swarm is not None:
1577
offsets = find_offsets(instances, cmd_opts.swarm)
1578
elif cmd_opts.auto_offset_line is not None:
1579
if location is None:
1580
raise ValueError("location needed for auto-offset-line")
1581
(bearing, metres) = cmd_opts.auto_offset_line.split(",")
1582
bearing = float(bearing)
1583
metres = float(metres)
1584
dist = 0
1585
offsets = {}
1586
for x in instances:
1587
offsets[x] = [dist*math.sin(math.radians(bearing)), dist*math.cos(math.radians(bearing)), 0, 0]
1588
dist += metres
1589
else:
1590
offsets = {x: [0.0, 0.0, 0.0, None] for x in instances}
1591
if location is not None:
1592
spawns = find_spawns(location, offsets)
1593
else:
1594
spawns = None
1595
1596
if cmd_opts.use_dir is not None:
1597
base_dir = os.path.realpath(cmd_opts.use_dir)
1598
try:
1599
os.makedirs(base_dir)
1600
except OSError as exception:
1601
if exception.errno != errno.EEXIST:
1602
raise
1603
os.chdir(base_dir)
1604
else:
1605
base_dir = os.getcwd()
1606
instance_dir = []
1607
if len(instances) == 1:
1608
instance_dir.append(base_dir)
1609
else:
1610
for i in instances:
1611
i_dir = os.path.join(base_dir, str(i))
1612
try:
1613
os.makedirs(i_dir)
1614
except OSError as exception:
1615
if exception.errno != errno.EEXIST:
1616
raise
1617
finally:
1618
instance_dir.append(i_dir)
1619
1620
if True:
1621
if not cmd_opts.no_rebuild: # i.e. we should rebuild
1622
do_build(cmd_opts, frame_infos)
1623
1624
if cmd_opts.fresh_params:
1625
do_build_parameters(cmd_opts.vehicle)
1626
1627
if cmd_opts.vehicle_binary is not None:
1628
vehicle_binary = cmd_opts.vehicle_binary
1629
else:
1630
binary_basedir = "build/sitl"
1631
vehicle_binary = os.path.join(root_dir,
1632
binary_basedir,
1633
frame_infos["waf_target"])
1634
1635
if not os.path.exists(vehicle_binary):
1636
print("Vehicle binary (%s) does not exist" % (vehicle_binary,))
1637
sys.exit(1)
1638
1639
start_vehicle(vehicle_binary,
1640
cmd_opts,
1641
frame_infos,
1642
spawns=spawns)
1643
1644
1645
if cmd_opts.delay_start:
1646
progress("Sleeping for %f seconds" % (cmd_opts.delay_start,))
1647
time.sleep(float(cmd_opts.delay_start))
1648
1649
tmp = None
1650
if cmd_opts.frame in ['scrimmage-plane', 'scrimmage-copter']:
1651
# import only here so as to avoid jinja dependency in whole script
1652
from jinja2 import Environment, FileSystemLoader
1653
from tempfile import mkstemp
1654
entities = []
1655
config = {}
1656
config['plane'] = cmd_opts.vehicle == 'ArduPlane'
1657
if location is not None:
1658
config['lat'] = location[0]
1659
config['lon'] = location[1]
1660
config['alt'] = location[2]
1661
entities = {}
1662
for i in instances:
1663
(x, y, z, heading) = offsets[i]
1664
entities[i] = {
1665
'x': x, 'y': y, 'z': z, 'heading': heading,
1666
'to_ardupilot_port': 9003 + i * 10,
1667
'from_ardupilot_port': 9002 + i * 10,
1668
'to_ardupilot_ip': '127.0.0.1'
1669
}
1670
if cmd_opts.scrimmage_args is not None:
1671
scrimmage_args = cmd_opts.scrimmage_args.split(',')
1672
global_opts = ['terrain']
1673
instance_opts = ['motion_model', 'visual_model']
1674
for arg in scrimmage_args:
1675
arg = arg.split('=', 2)
1676
if len(arg) == 2:
1677
k, v = arg
1678
if k in global_opts:
1679
config[k] = v
1680
elif k in instance_opts:
1681
for i in entities:
1682
# explicit instance args take precedence; don't overwrite
1683
if k not in entities[i]:
1684
entities[i][k] = v
1685
elif len(arg) == 3:
1686
i, k, v = arg
1687
try:
1688
i = int(i)
1689
except ValueError:
1690
continue
1691
if i in entities and k in instance_opts:
1692
entities[i][k] = v
1693
config['entities'] = list(entities.values())
1694
env = Environment(loader=FileSystemLoader(os.path.join(autotest_dir, 'template')))
1695
mission = env.get_template('scrimmage.xml.j2').render(**config)
1696
tmp = mkstemp()
1697
atexit.register(os.remove, tmp[1])
1698
1699
with os.fdopen(tmp[0], 'w') as fd:
1700
fd.write(mission)
1701
run_in_terminal_window('SCRIMMAGE', ['scrimmage', tmp[1]])
1702
1703
1704
if cmd_opts.delay_start:
1705
progress("Sleeping for %f seconds" % (cmd_opts.delay_start,))
1706
time.sleep(float(cmd_opts.delay_start))
1707
1708
try:
1709
if cmd_opts.no_mavproxy:
1710
time.sleep(3) # output our message after run_in_terminal_window.sh's
1711
progress("Waiting for SITL to exit")
1712
wait_unlimited()
1713
else:
1714
start_mavproxy(cmd_opts, frame_infos)
1715
except KeyboardInterrupt:
1716
progress("Keyboard Interrupt received ...")
1717
1718
sys.exit(0)
1719
1720