Docs menu
Web dashboard
Watch the car's map, scan, pose, camera and decisions from a browser at dashboard.sfuracerbot.ca, tune the driving nodes live, and open the car in Lichtblick.
The dashboard shows a running car in a browser: the map as it is built, the LiDAR scan, the car’s pose, the camera, and the plan and binding limit from drive intent. It lives at dashboard.sfuracerbot.ca, behind a sign-in with a team email. The car runs only the server side, web_dashboard, and the site connects to it.
The dashboard publishes to no ROS topic, so it can run beside any mode, including a race. Its code, the site and its full manual are in the sfu-racerbot/web-dashboards repository, included in the workspace as the src/web_dashboardsGitHub submodule.
What it reads
web_dashboard_node subscribes to the topics below and forwards what it needs to the browser over one WebSocket connection. It publishes to no topic.
| Topic and type | Shown as | Notes |
|---|---|---|
/map, nav_msgs/OccupancyGrid |
The map | Latched subscription. Sent compressed, and after the first full copy only the changed patches, with a full copy every 30 s. |
/scan, sensor_msgs/LaserScan |
The scan overlay | Best-effort subscription, which matches any publisher. Sent to the browser at 10 Hz. |
/pf/viz/inferred_pose, geometry_msgs/PoseStamped |
The car’s position | A LiDAR pose, moved back 0.26 m to the rear axle (laser_offset_x) |
/ackermann_cmd, ackermann_msgs/AckermannDriveStamped |
Commanded steering and speed | What won at the mux, not what one node asked for |
/odom, nav_msgs/Odometry |
Measured speed | |
/joy, sensor_msgs/Joy |
Whether LB is held | Read only, with the same 0.5 s timeout as the driving nodes |
/drive_intent, std_msgs/String (JSON) |
The planned path and the binding limit | See Drive intent |
/racing_line, std_msgs/String (JSON) |
The racing line | Latched, so a browser opened mid-race still gets it |
/auto_map_race/controller, std_msgs/String |
Which controller is driving in a map-and-race run | Latched |
Car telemetry goes to the browser at up to 20 Hz (telemetry_rate_hz). The map, the heaviest message, is the one the compression and patching exist for.
Start it
ros2 launch racerbot_launch dashboard_launch.pyThe log reports the server listening on port 8080, and the site lists the car as CAR ONLINE. dashboard_launch.py loads the car’s own settings on top of the dashboard’s generic defaults.
| Car 1 | Car 2 | |
|---|---|---|
| Site ID | rb1 | rb2 |
| Settings file | racerbot_launch/config/web_dashboard_rb1.yaml | racerbot_launch/config/web_dashboard_rb2.yaml |
Camera, started by ros2 launch racerbot_launch camera_launch.py | The UVC webcam, 1280×720 at 30 fps, through webcam_launch.py | The RealSense D435i, through realsense_camera_launch.py |
The camera stream goes straight to the browser and publishes no ROS image topic. To start the LiDAR and the camera for bench testing, without any driving stack, use ros2 launch racerbot_launch dashboard_test_launch.py. It does not start a second dashboard, because the cars already run one at boot and a second copy fails on the busy port 8080. Add dashboard:=true only on a computer where no dashboard is running.
What it can change
The dashboard has four write paths, all bounded:
| Action | How | Limit |
|---|---|---|
| Tune a parameter | set_parameters on gap_follow_node or pure_pursuit_node |
Only parameters each node lists in its live_tunable_spec, within the bounds the node declares. Save writes each value back to the file that already holds it: the shared YAML, or the car’s own calibration/ file for a per-car value such as max_steering_angle. |
| Stop a driving process | An operating-system signal | Only processes listed in the car’s settings file |
| Reset live SLAM | The slam_toolbox/reset service |
Refused while a driving node runs |
| Delete a saved run | Removes a run folder | Only under this car’s ~/racerbot-ws/data/<id>/racerbot_auto/ and ~/racerbot-ws/data/<id>/racerbot_sim/auto/, where <id> is rb1 or rb2. Never the upstream demo maps in particle_filter/maps and range_libc/maps, the simulator’s tracks, or the home folder. Car 1’s runs saved under ~/.ros/ before the two workspaces merged are no longer listed. |
How live tuning works
Each driving node publishes a read-only parameter, live_tunable_spec: a JSON catalogue of the parameters it accepts live, with their group, bounds, unit and step. The dashboard reads the catalogue and builds its sliders from it, so it never needs to know a node’s parameters in advance. An entry in gap_follow’s catalogue, from live_tuning.pyGitHub:
Tunable('max_lateral_accel', 'Speed', 0.3, 4.0, unit='m/s^2', step=0.1,
description='Cornering aggression: speed is held to '
'sqrt(a_lat / curvature) for the steering angle '
'being commanded.'),A change goes to the node through the standard ROS set_parameters service. The node checks it in its own parameter callback, against the same bounds and every rule that ties parameters together, and refuses anything outside them, so the dashboard cannot set a value the node would not accept from its YAML. Save writes the accepted values back into the node’s YAML file, listed per car in tuning_config_files.
Live tuning is the fast way to tell a profile problem from a controller problem between runs: lower pure_pursuit’s max_speed and the whole profile is clipped without recording anything again. Steering-law parameters cannot be tuned live; the node refuses.
The contract between the dashboard and the driving nodes, the live_tunable_spec format and the YAML save, is tested on both sides. This workspace’s half is in test_gap_follow_live_tuning.pyGitHub and test_pure_pursuit_live_tuning.pyGitHub.
Lichtblick
foxglove_bridge starts at boot as the foxglove-bridge system service and lets Lichtblick, an open-source Foxglove-style viewer, display any topic. Browsers can publish to no topic through it. With client publishing on, the bridge’s topic whitelist was not enforced in version 3.5.0, and a client’s /drive reached ackermann_mux past every LB check, so client publishing is off and a test fails if it is turned back on.
Change a car’s settings
Edit the car’s settings file in racerbot_launch/config/, not the package’s YAML in the submodule, which holds the defaults every team shares. Then run the tests that check it:
python3 -m pytest src/racerbot_launch/test/ -vThey catch a misspelt key, which ROS would otherwise ignore without a warning, a LiDAR offset that disagrees with the bringup transform, and a process or folder the dashboard would drop.
If the site cannot reach the car
On the car:
ros2 run web_dashboard remote_check --site https://dashboard.sfuracerbot.ca --car rb1Use rb2 on car 2. It ends with Everything this car can check is fine when the car’s side works, and otherwise names the step that fails. http://<car-ip>:8080/ returns 404 on purpose: the car serves no page itself.
Checked against
car 1 commit 0265263
and
car 2 commit b81b291, before the two workspaces merged.