A ROS 1 (Noetic) catkin workspace for simulating Adaptive Cruise Control (ACC) in a Dockerized environment. Developed for Vanderbilt University's CS 3892 Autonomous Vehicles & Traffic course.
Recorded driving data from a real vehicle is replayed as a lead car, and one or more simulated ego vehicles follow it using an ACC controller -- all running as ROS nodes inside a Docker container.
To cite the usage of this repository, use the following:
Kate Sanborn, Daniel B. Work, Jonathan Sprinkle. "Single Vehicle to Traffic Scale: a Model-Based Workflow." in 2026 IEEE International Conference on Intelligent Transportation Systems (ITSC), (in press) 2026.
or using bibtex:
@inproceedings{sanborn2026single,
author = {Sanborn, Kate and Work, Daniel B. and Sprinkle, Jonathan},
title = {Single Vehicle to Traffic Scale: A Model-Based Workflow},
booktitle = {2026 IEEE International Conference on Intelligent Transportation Systems (ITSC)},
year = {2026},
note = {in press}
}
git clone https://github.com/jmscslgroup/rossim rossim
cd rossimThis gives you the workspace skeleton: launch files, setup scripts, and this README. The ROS packages themselves are cloned in Step 4.
- macOS / Windows: Install Docker Desktop
- Linux: Install Docker Engine
docker pull sprinkjm/rosemptyRun the image and confirm you get a shell prompt:
docker run --rm -it sprinkjm/rosempty /bin/bashYou should see a root prompt like root@<container_id>:/#. Inside it, verify ROS is available:
roscore &
sleep 2 && rostopic listYou should see /rosout and /rosout_agg listed. If so, ROS is working. Type exit to leave the container.
This step confirms that your host files are visible inside the container. We use
Docker Compose (included with Docker Desktop
and modern Docker Engine) so you don't have to type long docker run --mount ...
commands -- the image and mount are defined once in compose.yaml.
From the rossim/ directory on your host:
docker compose run --rm ros lsYou should see README.md, scripts/, src/, etc. -- the contents of your
rossim/ directory, which is mounted at /ros/catkin_ws inside the container.
If you see an empty listing or an error, double-check that you are in the
rossim/ directory and that Docker has permission to access it.
A setup script clones all the packages needed for the profacc ACC simulation:
./scripts/setup_profacc.shThis clones the following into src/:
| Package | Description |
|---|---|
| profacc | Time-headway ACC controller (Simulink-generated) |
| subtractor | Computes difference of two Float64 topics |
| odometer | Integrates velocity to produce position |
| carsimplesimulink | Simple point-mass vehicle model |
| carcomplexsimulink | Higher-fidelity vehicle model |
Download the example bag file from Brightspace (hwilexample.bag) and place it in the rossim/ root directory as mytest.bag:
cp /path/to/hwilexample.bag mytest.bagThis file contains a recorded velocity trace from a real vehicle and is replayed as the lead car in simulation.
Open an interactive shell in the container (the workspace is already mounted):
docker compose run --rm rosInside the container, build:
catkin_makeIf the build succeeds you will see a summary like:
[100%] Built target profacc
[100%] Built target carsimplesimulink
...
Then source the workspace:
source devel/setup.bashIf you are already in the container shell from Step 6 (with the workspace sourced), launch directly:
roslaunch profacc profaccDocker.launchOr, from your host, use the one-command shortcut that builds, sources, and launches in a fresh container:
./scripts/run.sh # default: profaccDocker.launch
./scripts/run.sh profaccDocker_complex.launch # any launch file in src/profacc/launch/You will see ROS start up several nodes. The bag file replays the lead car velocity trace, the ACC controller computes acceleration commands, and the ego car model responds. All topics are recorded to a new bag file (profacc_*.bag) in the workspace root.
When the bag file finishes playing (or you want to stop early), press Ctrl+C.
The terminal will show log output from the various nodes. To see the simulation in action, open a second terminal on your host and join the running container (the workspace is sourced for you automatically):
./scripts/join.shjoin.sh connects to the container named rossim (the default that
run.sh creates), so there is no need to look up the container name or type a
long docker exec command. If you started the simulation with a custom name
(./scripts/run.sh --name egocarB ...), pass the same name: ./scripts/join.sh egocarB.
Then try:
# List all active topics
rostopic list
# Watch the ego car velocity in real time
rostopic echo /egocar/car/state/vel_x
# Watch the ACC acceleration commands
rostopic echo /egocar/cmd_accelEach run.sh invocation starts its own container with its own roscore, so you
can run two independent simulations side by side -- just give them different
names. In two host terminals:
./scripts/run.sh --name carA --port 8888 profaccDocker.launch
./scripts/run.sh --name carB --port 8889 profaccDocker_complex.launchGive each one a different --port so their dashboards don't collide on the host
(carA on localhost:8888, carB on localhost:8889). Join either one from
another terminal:
./scripts/join.sh carA
./scripts/join.sh carBBecause each container has its own ROS master, the two simulations do not see each other's topics -- they are fully isolated.
A small web dashboard shows the cars in the simulation updating in real time in
your browser. It runs as a ROS node and needs no extra software -- just
rospy and the Python standard library (details in
dashboard/README.md). It has two views:
- Overhead -- an ego-centric top-down view of the selected car, the car ahead, and any cars behind, placed by their odometry.
- Data -- value tiles for the selected car (speed, commanded acceleration, lead distance, relative velocity, odometer).
The dashboard discovers the cars automatically (any namespace publishing
car/state/vel_x), so leadcar, egocar, egocar1, ... all appear as
buttons at the top -- click to swap which car you are watching. This works
for a single car or a whole platoon.
With a simulation running (./scripts/run.sh), start the dashboard from a second
host terminal:
./scripts/dashboard.shThen open http://localhost:8888 in your browser. Press Ctrl+C to stop the dashboard; the simulation keeps running.
When you are logged into the car over SSH with no web access, use the text version instead. It does the same car discovery and prints a refreshing table (plus a front-to-back ordering) right in the terminal -- no browser, no ports:
./scripts/dashboard.sh --text # the sim, in the container
# or, directly on the real vehicle where ROS is sourced:
python3 dashboard/dashboard_tui.py --mode liveThere are two modes:
| Mode | What it shows |
|---|---|
sim (default) |
the fields available in simulation |
live |
the same fields plus extra real-vehicle fields, for running on the actual car |
./scripts/dashboard.sh --mode liveThe live set is a superset that grows as real-car topics come online -- see
dashboard/dashboard.py (the LIVE_EXTRA_FIELDS list) and dashboard/README.md.
Each Docker launch file sets up this pipeline:
Recorded bag file Lead car Ego car
(real driving data) (odometer) (ACC + vehicle model)
| | |
/leadcar/car/state/vel_x odom_x profacc <-- subtractor (rel_vel)
\ | \--- subtractor (lead_dist)
\ v
carsimplesimulink --> vel_x, odom_x
rosbag playreplays the recorded velocity trace as the lead carodometerintegrates lead car velocity into positionsubtractornodes compute relative velocity and distance between lead and egoprofacccomputes an acceleration command using the ACC control lawcarsimplesimulinksimulates the ego car's response to that commandrosbag recordcaptures everything to a new bag file
The controller implements a time-headway ACC law:
cmd_accel = alpha * (lead_dist - tau * vel_x) + lambda * rel_vel
| Parameter | Default | Description |
|---|---|---|
alpha |
1.1 | Proportional gain |
tau |
2.0 | Time headway (seconds) |
lambda |
0.1 | Relative velocity gain |
Output is saturated to [-3.0, 1.5] m/s^2.
| Direction | Topic | Type | Description |
|---|---|---|---|
| Subscribes | car/state/vel_x |
std_msgs/Float64 |
Ego car velocity |
| Subscribes | lead_dist |
std_msgs/Float64 |
Distance to lead car |
| Subscribes | rel_vel |
std_msgs/Float64 |
Relative velocity (lead - ego) |
| Publishes | cmd_accel |
std_msgs/Float64 |
Acceleration command |
Parameters can be changed at runtime:
rosparam set /egocar/profacc_node/tau 3.0All Docker launch files are in src/profacc/launch/:
| Launch file | Ego cars | Vehicle model | Description |
|---|---|---|---|
profaccDocker.launch |
1 | simple | Basic scenario. Replays mytest.bag from t=100s. Lead starts at x=20m. |
profaccDocker_test1.launch |
1 | simple | Ego starts closer (x0=15m) and faster (v0=2.5 m/s). |
profaccDocker_test2.launch |
1 | complex | Adds 10m extra buffer to lead_dist. |
profaccDocker_complex.launch |
1 | complex | Same as basic but with the complex vehicle model. |
After a simulation, the recorded .bag file is written to the rossim/ directory. Open MATLAB, run the provided testResult script, and select the bag file when prompted.