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.py

The 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.

Per-car dashboard settings
Car 1Car 2
Site IDrb1rb2
Settings fileracerbot_launch/config/web_dashboard_rb1.yamlracerbot_launch/config/web_dashboard_rb2.yaml
Camera, started by ros2 launch racerbot_launch camera_launch.pyThe UVC webcam, 1280×720 at 30 fps, through webcam_launch.pyThe 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/ -v

They 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 rb1

Use 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.