Docs Run the car Workspace repo
Docs menu

Command reference

One copy-paste block for every mode of the car, with the output that shows it is working.

Each section gives the terminals a mode needs, the commands for each, and the output that shows the mode is working. Run the commands on the car’s Jetson, over SSH or at its desktop.

Set up every terminal

Run these two lines in every new terminal before any ros2 command. The first loads ROS 2 Jazzy, and the second loads the workspace’s packages.

source /opt/ros/jazzy/setup.bash
source ~/racerbot-ws/install/setup.bash

Both commands print nothing when they succeed. If a later ros2 command reports Package ... not found, that terminal skipped this step.

Safety rules for every mode

  • Hold LB on the F710 (XInput mode) whenever the car should move. Releasing LB commands zero speed in every mode, including the autonomous ones.
  • Run anything new or changed with the wheels off the ground first.
  • Never run teleop_launch.py alongside an autonomous mode. Its commands win at the mux, so the autonomous mode cannot drive.
  • Keep one person beside the battery switch whenever the car drives itself.

Check the hardware

Run the doctor before a session. It checks each device from USB up to its ROS topic, reads the battery voltage from the VESC, and never publishes or opens a device.

ros2 run racerbot_doctor doctor

The last line reads 0 FAIL when the hardware is ready. Each failure names the first layer that is broken, for example a missing /dev/sensors/vesc link or a LiDAR that publishes too slowly.

Drive by hand

Terminal 1 starts the hardware. Leave it running for every mode below unless a section says otherwise. Bringup never moves the car by itself.

ros2 launch f1tenth_stack bringup_launch.py

Terminal 2 starts manual control:

ros2 launch f1tenth_stack teleop_launch.py

Hold LB, then use the left stick for speed and the right stick for steering. Full stick commands 5 m/s.

Car 1Car 2
Full-stick steering0.38 rad0.436 rad

Follow the gap

gap_follow drives without a map. See Follow the gap for how it works.

Terminal 2, with bringup running in Terminal 1:

ros2 launch gap_follow gap_follow_launch.py

The terminal prints a decision line every second, such as DRIVE [gap_follow] selected preferred depth 2.00m gap .... The car drives while LB is held.

Add an option to the end of the command when needed:

Option Effect
max_speed:=1.0 Sets the top speed for this run. Must be positive and at most 5.0 m/s.
opponent_awareness:=false Turns off tracking and following other cars, which is on by default. Tested only in the simulator.
steering_damping:=false Turns off the gyro steering term, which is on by default.
imu:=false Starts without the IMU stack. Refused if any IMU feature is on.
fused_speed:=true Uses the IMU-fused speed on /odom/fused. Safety checks use the more cautious of it and the wheel speed.
imu_understeer:=true Caps speed when the car turns much less than commanded
imu_traction:=true Holds the speed command while the wheels spin. Only ever lowers speed.
imu_impact_stop:=true Latches speed 0 after a large acceleration spike. Release and press LB to resume. Threshold provisional.
imu_all:=true Turns on the four features above. An explicit false on one of them wins.

The IMU features have been run only in the simulator, and their thresholds were measured on car 1. Keep them off on car 2 until its BNO085 mount yaw is measured. See IMU features before trying one on the car.

Map a course

map_course drives laps with gap_follow while slam_toolbox builds a map. It then saves the map and a racing line, stops the car, shuts itself down and prints the commands to race on that map. It requires bringup to be running and refuses to start without it.

Put the car on the track, pointing in the direction it will lap.

Terminal 2, with bringup running in Terminal 1:

ros2 run racerbot_launch map_course

map_course maps on the gyro by default, so keep the car still for the first 3 seconds while the IMU measures its gyro bias. Then hold LB for the whole run. Releasing LB pauses mapping, and holding it again continues.

The run is working when the terminal shows Closed mapping lap 1/3, 2/3 and 3/3. The car then stops past the start line, Saved occupancy map successfully. appears, and the terminal ends with a block that starts MAPPING FINISHED. and contains the race commands. Before them, map_course checks the map against the run’s scans and prints GO: or NO-GO:. Do not race a NO-GO map.

Leave the car where it stopped. That spot is the start pose for racing on this map. To race the map on another day, mark the spot and direction on the floor before moving the car.

Option Effect
mapping_max_speed:=1.5 Lower speed for a course nobody has driven yet. Default 3.5 m/s.
laps:=4 A different number of laps, at least 2
record_bag:=false Skips the rosbag of the run in ~/racerbot-ws/data/<id>/racerbot_runs/. A bag lets the run be replayed and analyzed. Default true.
analysis:=true Runs the summary, IMU report and SLAM report after mapping. They take minutes.
gyro_odom:=false imu_lap_turns:=false Maps on the steering model instead of the gyro

Every option is on Map a course. If the terminal reports the hardware is not running, start bringup in Terminal 1 first. Ctrl+C stops the run, keeps what it mapped and still runs the map check. If the last lap fails the quality check, the run drives up to two more laps.

Race on a saved map

Racing localizes the car on the saved map with the particle filter, then follows the racing line with pure_pursuit. The car must be where mapping left it, or back on the marked spot facing the same way. The particle filter starts from that pose and searches only about 0.5 m and 20° around it. This mode has been tested only in the simulator.

Terminal 2, with bringup running in Terminal 1. Replace <id> with rb1 or rb2, and <run> with the folder name from the end of mapping. The printout at the end of mapping already contains these commands with the folder filled in.

ros2 launch racerbot_launch race_launch.py run_dir:=$HOME/racerbot-ws/data/<id>/racerbot_auto/<run>

The default steering law is pure pursuit. Choose another with steering_law:=:

ros2 launch racerbot_launch race_launch.py run_dir:=$HOME/racerbot-ws/data/<id>/racerbot_auto/<run> steering_law:=stanley
ros2 launch racerbot_launch race_launch.py run_dir:=$HOME/racerbot-ws/data/<id>/racerbot_auto/<run> steering_law:=lqr
ros2 launch racerbot_launch race_launch.py run_dir:=$HOME/racerbot-ws/data/<id>/racerbot_auto/<run> steering_law:=mpc

The mode is working when Particle filter is at the start pose; racing can begin. appears. The race controller starts only after that line, and the car then drives the racing line while LB is held. If the filter never reaches the start pose, the car is not on the mapping spot or the map is wrong. Move the car and run the command again.

To find the newest run folders and print a run’s commands again:

ls -t ~/racerbot-ws/data/<id>/racerbot_auto | head -3
cat ~/racerbot-ws/data/<id>/racerbot_auto/<run>/commands.txt

Map and race in one run

This launch maps the course and hands control to pure pursuit as soon as lap 2 passes the quality check, with no restart. It starts the hardware too, so run it instead of bringup.

Terminal 1:

ros2 launch racerbot_launch auto_map_race_launch.py

The terminal shows lap messages, a handover summary, then Transition complete: pure pursuit now has drive control.

Dashboard

The dashboard watches and tunes the car from a browser at dashboard.sfuracerbot.ca, behind a sign-in with a team email. It publishes to no ROS topic, so it can run alongside any mode, in any spare terminal.

ros2 launch racerbot_launch dashboard_launch.py

The log reports that the server is listening on port 8080, and the site lists the car as CAR ONLINE.

To add the camera feed, start the car’s camera, which camera_launch.py chooses from the car profile:

ros2 launch racerbot_launch camera_launch.py
Car 1Car 2
Camera it startsUSB webcam, through webcam_launch.pyRealSense D435i, through realsense_camera_launch.py
Dashboard site IDrb1rb2

Record a run

Recording saves the logs, decisions and stop reasons of a run for analysis afterwards. Bringup records every autonomy run by itself: when a driving node starts, the bringup terminal prints recording ... -> ~/racerbot-ws/data/<id>/racerbot_runs/<ts>, and when it stops, saved run .... <id> is rb1 or rb2. Manual driving is not recorded, and record:=false on bringup turns automatic recording off.

To record a manual drive, start the recorder in its own terminal first. Bringup’s recorder then stays out of the way.

ros2 launch race_diagnostics record_run.py

After the run, summarize and analyze it in any terminal:

ros2 run race_diagnostics summarize_run ~/racerbot-ws/data/<id>/racerbot_runs/<ts>
python3 ~/racerbot-ws/tools/run_analysis/analyze_run.py ~/racerbot-ws/data/<id>/racerbot_runs/<ts>

The analysis writes analysis/report.md in the run folder, with a reason for every stop.

Simulator

The simulator runs the stack without the car. Never run it on the car while the drivers are running: it fakes LB and a second /scan. It refuses to start while it sees the real drivers on the ROS graph.

ros2 launch racerbot_sim sim_auto_map_race_launch.py

This runs the full map-then-race flow in simulation. Two faster checks run gap_follow’s logic without ROS:

python3 ~/racerbot-ws/tools/f1tenth_sim/sweep_gap.py --track indoor --seeds 6
python3 ~/racerbot-ws/tools/f1tenth_sim/eval_gap_multi.py --track indoor_wide --cars 3 --speeds 1.8,3.5,3.5 --set enable_opponent_awareness=true

Each line of output reads pass N/N when every run finished, with contact 0 for one car or wall 0 car-car 0 for several.

Stop everything

Press Ctrl+C in each terminal, control layer first and bringup last, and turn the battery off after every process has exited. If a process does not exit, stop it by name:

pkill -f "joy_node|joy_teleop|vesc_driver_node|urg_node_driver|ackermann_mux|ackermann_to_vesc_node|vesc_to_odom_node|static_transform_publisher|gap_follow_node|slam_toolbox|particle_filter|pure_pursuit_node|waypoint_recorder_node"

Saved files

Contents Location
Maps, racing lines and commands.txt from mapping runs (<id> is rb1 or rb2) ~/racerbot-ws/data/<id>/racerbot_auto/<YYYYMMDD-HHMMSS>/
The same, from the simulator ~/racerbot-ws/data/<id>/racerbot_sim/auto/<YYYYMMDD-HHMMSS>/
Run recordings: logs, events and an optional bag ~/racerbot-ws/data/<id>/racerbot_runs/<ts>/
gap_follow speeds and limits src/gap_follow/config/gap_follow.yaml
Racing-line limits for mapping runs src/pure_pursuit/config/auto_map_race.yaml

Files behind these commands

Command File
bringup_launch.py, teleop_launch.py f1tenth_stack/launch/GitHub
gap_follow_launch.py gap_follow_launch.pyGitHub
map_course racerbot_launch/scripts/map_courseGitHub
race_launch.py, auto_map_race_launch.py, dashboard_launch.py, camera_launch.py racerbot_launch/launch/GitHub
record_run.py record_run.pyGitHub
sim_auto_map_race_launch.py sim_auto_map_race_launch.pyGitHub
The daily command sheet docs/launchstart.mdGitHub

Checked against car 1 commit 8a88dcc and car 2 commit 80da6f5, before the two workspaces merged.