diff --git a/Tools/autotest/sim_vehicle.py b/Tools/autotest/sim_vehicle.py index 9262c3839b5..2fff93c3fad 100755 --- a/Tools/autotest/sim_vehicle.py +++ b/Tools/autotest/sim_vehicle.py @@ -333,6 +333,181 @@ def wait_unlimited(): vinfo = vehicleinfo.VehicleInfo() +def run_gui(cmd_opts): + try: + import wx + except ImportError: + progress("Error: wxPython could not be imported. GUI cannot start.") + sys.exit(1) + + app = wx.App(False) + config = wx.Config("ArduPilotSITL") + + locations = [] + default_location = "CMAC" + loc_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'locations.txt') + + if os.path.exists(loc_file): + with open(loc_file, 'r') as f: + for line in f: + line = line.strip() + if line and not line.startswith('#'): + parts = line.split('=') + if len(parts) > 0: + locations.append(parts[0].strip()) + + locations.sort() + if default_location not in locations: + locations.insert(0, default_location) + + target_location = cmd_opts.location or config.Read("last_location", default_location) + + default_vehicle = cmd_opts.vehicle + if default_vehicle and default_vehicle in vinfo.options: + available_vehicles = [default_vehicle] + if default_vehicle == 'ArduCopter' and 'Helicopter' in vinfo.options: + available_vehicles.append('Helicopter') + else: + available_vehicles = list(vinfo.options.keys()) + + frame = wx.Frame(None, title="ArduPilot SITL Launcher") + panel = wx.Panel(frame) + vbox = wx.BoxSizer(wx.VERTICAL) + + vbox.Add(wx.StaticText(panel, label="Select Vehicle:"), flag=wx.LEFT | wx.TOP, border=10) + vehicle_combo = wx.ComboBox(panel, choices=available_vehicles, style=wx.CB_READONLY) + if available_vehicles: + if len(available_vehicles) == 1: + vehicle_combo.SetSelection(0) + else: + saved_vehicle = config.Read("last_vehicle", "") + if saved_vehicle and saved_vehicle in available_vehicles: + vehicle_combo.SetStringSelection(saved_vehicle) + else: + vehicle_combo.SetSelection(0) + + vbox.Add(vehicle_combo, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=10) + + vbox.Add(wx.StaticText(panel, label="Starting Location (-L):"), flag=wx.LEFT | wx.TOP, border=10) + location_combo = wx.ComboBox(panel, choices=locations, style=wx.CB_DROPDOWN) + location_combo.AutoComplete(locations) + + if target_location in locations: + location_combo.SetSelection(locations.index(target_location)) + else: + location_combo.SetValue(target_location) + + vbox.Add(location_combo, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=10) + + vbox.Add(wx.StaticText(panel, label="Aircraft Scenario (--aircraft):"), flag=wx.LEFT | wx.TOP, border=10) + history_str = config.Read("aircraft_history", "") + aircraft_history = [h for h in history_str.split(',') if h] + + aircraft_combo = wx.ComboBox(panel, choices=aircraft_history, style=wx.CB_DROPDOWN) + aircraft_combo.AutoComplete(aircraft_history) + if cmd_opts.aircraft: + aircraft_combo.SetValue(cmd_opts.aircraft) + elif aircraft_history: + aircraft_combo.SetValue(aircraft_history[0]) + + vbox.Add(aircraft_combo, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=10) + + options_sizer = wx.BoxSizer(wx.VERTICAL) + + chk_mavproxy = wx.CheckBox(panel, label="Enable MAVProxy") + mavproxy_default = config.ReadBool("no_mavproxy", False) + should_enable = not cmd_opts.no_mavproxy and (cmd_opts.map or cmd_opts.console or not mavproxy_default) + chk_mavproxy.SetValue(should_enable) + + chk_map = wx.CheckBox(panel, label="Enable MAVProxy map") + chk_map.SetValue(cmd_opts.map or config.ReadBool("map", True)) + + chk_console = wx.CheckBox(panel, label="Enable MAVProxy console") + chk_console.SetValue(cmd_opts.console or config.ReadBool("console", True)) + + chk_osd = wx.CheckBox(panel, label="Enable OSD") + chk_osd.SetValue(getattr(cmd_opts, 'OSD', False) or config.ReadBool("osd", False)) + + chk_wipe = wx.CheckBox(panel, label="Reset to Default Parameters (-w)") + chk_wipe.SetValue(cmd_opts.wipe_eeprom or config.ReadBool("wipe", False)) + + def on_mavproxy(event): + enabled = chk_mavproxy.GetValue() + chk_map.Enable(enabled) + chk_console.Enable(enabled) + if not enabled: + chk_map.SetValue(False) + chk_console.SetValue(False) + if event: + event.Skip() + + chk_mavproxy.Bind(wx.EVT_CHECKBOX, on_mavproxy) + on_mavproxy(None) + + options_sizer.Add(chk_mavproxy, flag=wx.ALL, border=5) + options_sizer.Add(chk_map, flag=wx.LEFT, border=25) + options_sizer.Add(chk_console, flag=wx.LEFT, border=25) + options_sizer.Add(chk_osd, flag=wx.ALL, border=5) + options_sizer.Add(wx.StaticLine(panel), flag=wx.EXPAND | wx.TOP | wx.BOTTOM, border=10) + options_sizer.Add(chk_wipe, flag=wx.ALL, border=5) + + vbox.Add(options_sizer, flag=wx.LEFT | wx.RIGHT | wx.TOP | wx.EXPAND, border=10) + + is_cancelled = True + + def on_launch(event): + nonlocal is_cancelled + is_cancelled = False + + config.WriteBool("console", chk_console.GetValue()) + config.WriteBool("map", chk_map.GetValue()) + config.WriteBool("osd", chk_osd.GetValue()) + config.WriteBool("no_mavproxy", not chk_mavproxy.GetValue()) + config.WriteBool("wipe", chk_wipe.GetValue()) + config.Write("last_location", location_combo.GetValue()) + config.Write("last_vehicle", vehicle_combo.GetStringSelection()) + + new_aircraft = aircraft_combo.GetValue().strip() + if new_aircraft: + if new_aircraft in aircraft_history: + aircraft_history.remove(new_aircraft) + aircraft_history.insert(0, new_aircraft) + config.Write("aircraft_history", ",".join(aircraft_history[:10])) + + config.Flush() + + if available_vehicles: + cmd_opts.vehicle = vehicle_combo.GetStringSelection() + + selected_loc = location_combo.GetValue() + if selected_loc: + cmd_opts.location = selected_loc + + if new_aircraft: + cmd_opts.aircraft = new_aircraft + + cmd_opts.wipe_eeprom = chk_wipe.GetValue() + cmd_opts.console = chk_console.GetValue() + cmd_opts.map = chk_map.GetValue() + cmd_opts.OSD = chk_osd.GetValue() + cmd_opts.no_mavproxy = not chk_mavproxy.GetValue() + + frame.Close() + + btn_launch = wx.Button(panel, label="Launch SITL") + btn_launch.Bind(wx.EVT_BUTTON, on_launch) + vbox.Add(btn_launch, flag=wx.EXPAND | wx.ALL, border=15) + + panel.SetSizer(vbox) + vbox.Fit(frame) + + frame.Centre() + frame.Show() + app.MainLoop() + + return not is_cancelled + + def do_build(opts, frame_options): """Build sitl using waf""" progress("WAF build") @@ -1092,6 +1267,11 @@ parser.add_option("-P", "--param", "layered on top of the per-frame defaults the " "binary loads from its embedded vehicleinfo.json") +parser.add_option("--gui", + action='store_true', + default=False, + help="start GUI launcher") + group_build = optparse.OptionGroup(parser, "Build options") group_build.add_option("-N", "--no-rebuild", action='store_true', @@ -1447,6 +1627,35 @@ parser.add_option_group(group_completion) cmd_opts, cmd_args = parser.parse_args() +cwd = os.getcwd() +vehicle_dir_name = os.path.basename(cwd) +in_vehicle_dir = vehicle_dir_name in vinfo.options + +if len(sys.argv) == 1 and not in_vehicle_dir: + cmd_opts.gui = True + +if cmd_opts.gui: + if cmd_opts.vehicle is None: + cwd = os.getcwd() + while cwd: + bname = os.path.basename(cwd) + if not bname: + break + if bname in vinfo.options: + cmd_opts.vehicle = bname + break + cwd = os.path.dirname(cwd) + + launch_continued = run_gui(cmd_opts) + + if not launch_continued: + progress("Launch cancelled by user.") + sys.exit(0) + + if cmd_opts.vehicle == 'Helicopter': + cmd_opts.vehicle = 'ArduCopter' + cmd_opts.frame = 'heli' + if cmd_opts.list_vehicle: print(' '.join(vinfo.options.keys())) sys.exit(1)