Put Your Package on a Robot
Come here after your package is in your GitHub repository and your Waypoint kit has arrived!
By the end you will have a safe path from package installation to supervised driving. The robot gets to move only after the boring checks pass, which is exactly the point.
1. Prepare the Raspberry Pi
Section titled “1. Prepare the Raspberry Pi”Flash Ubuntu Server 24.04 LTS, 64-bit to the Raspberry Pi Zero 2 W with Raspberry Pi Imager. Ubuntu lists the Zero 2 W as supported for Ubuntu Server; use Server, not Desktop, because the small Pi does not need to render RViz. Follow Ubuntu’s Raspberry Pi installation guide for Imager and first-boot details.
During Imager setup, choose a username, password, Wi-Fi network, and hostname you will remember. Boot the Pi, then connect over SSH from an Ubuntu terminal. This example uses robot and cool-rover.local; replace those two values with the username and hostname you chose.
PI_USER="robot"PI_HOST="cool-rover.local"ssh "$PI_USER@$PI_HOST"Install the ROS 2 repository and base tools by following Set Up Ubuntu for ROS 2 on the Pi, choosing the robot package list instead of the desktop list. Then install the packages your physical interfaces need:
sudo apt updatesudo apt install -y python3-gpiozero python3-lgpio python3-smbus python3-smbus2 i2c-tools2. Enable and Check I2C
Section titled “2. Enable and Check I2C”Your IMU needs the Pi’s I2C bus enabled. Add dtparam=i2c_arm=on to /boot/firmware/config.txt, reboot, then reconnect over SSH.
With the IMU wired and powered, scan the bus:
i2cdetect -y 1You should see the address your IMU uses, often 68 or 69 for an MPU6050. If no address appears, stop and check power, ground, SDA, SCL, and the board’s address pin before blaming ROS.
3. Bring Your Package Over
Section titled “3. Bring Your Package Over”Clone your repository into a workspace on the Pi. Keep the same workspace and package names you used while developing, but do not assume that a path from your laptop exists on the robot. This example uses cool_rover_ws, cool_rover, and https://github.com/cool-hacker/cool-rover.git. Replace those values with yours before running the commands.
WORKSPACE_NAME="cool_rover_ws"PACKAGE_NAME="cool_rover"REPOSITORY_URL="https://github.com/cool-hacker/cool-rover.git"
mkdir -p ~/"$WORKSPACE_NAME"/srccd ~/"$WORKSPACE_NAME"/srcgit clone "$REPOSITORY_URL"cd ~/"$WORKSPACE_NAME"source /opt/ros/jazzy/setup.bashrosdep install -i --from-paths src --rosdistro jazzy -ycolcon build --symlink-installsource install/setup.bashIf rosdep reports a missing system dependency, add the correct dependency to package.xml only after you know which node needs it. Do not fix a build by randomly adding every ROS package you can find.
4. Prove the Package Installed
Section titled “4. Prove the Package Installed”Before launching hardware, check that ROS can find the package, executables, launch files, and installed assets from the Pi’s install/ space.
ros2 pkg prefix "$PACKAGE_NAME"ros2 pkg executables "$PACKAGE_NAME"ros2 launch "$PACKAGE_NAME" bringup.launch.py --show-argsExpected result:
ros2 pkg prefixprints a path inside~/cool_rover_ws/install/ros2 pkg executableslists your motor, IMU, odometry, and autonomy commandsros2 launch ... --show-argsfinds the launch file without using a path intosrc/
If launch or config files are missing, return to Create Your Workspace and confirm your setup.py installs launch/, config/, urdf/, meshes/, and RViz files as package data where appropriate.
5. Start Bringup With Motors Secured
Section titled “5. Start Bringup With Motors Secured”Your bringup launch file should start the nodes you made.
- your motor node
- your IMU node
- your
open_loop_odomnode robot_state_publisheronly if you completed the optional robot-model polish
Keep the initial motor-output limit low in config/robot.yaml. Edit max_output, rebuild if your installed config needs it, launch the package, then verify the loaded value from the second terminal with ros2 param get /motor_driver max_output.
ros2 launch "$PACKAGE_NAME" bringup.launch.pyWith bringup running, the robot should sit still. If a wheel moves before any command arrives, cut motor power, stop the launch file, and fix startup-zero behavior before continuing.
6. Check the ROS Graph
Section titled “6. Check the ROS Graph”Open a second SSH terminal to the Pi and source the same workspace:
WORKSPACE_NAME="cool_rover_ws"PACKAGE_NAME="cool_rover"
source /opt/ros/jazzy/setup.bashsource ~/"$WORKSPACE_NAME"/install/setup.bashros2 node listros2 topic list -tros2 topic info /cmd_vel --verboseros2 param get /motor_driver max_outputros2 param get /motor_driver command_timeout_sros2 param get /imu_node frame_idYou should find /motor_driver, /imu_node, and /open_loop_odom, plus /cmd_vel, /imu/data_raw, /odom, /path, /tf, and /tf_static where appropriate. ros2 topic info /cmd_vel --verbose should show the motor and odometry nodes as subscribers. Before teleop or autonomy starts, it should not show an unexpected command publisher.
If a node is absent, read the launch output before changing code. A missing node usually points to an import error, bad executable entry, missing dependency, or a YAML key that does not match the node name.
7. Prove the Watchdog
Section titled “7. Prove the Watchdog”Keep the wheels secured. Publish one small command, then stop publishing. The motor node should stop by itself after its configured timeout.
ros2 topic pub -1 /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.06}, angular: {z: 0.0}}"Expected behavior:
- both sides briefly request forward motion
- the command ends after one message
- the watchdog returns both motor outputs to zero shortly after the timeout
- no terminal remains publishing
/cmd_vel
If the motors keep running, cut motor power and fix the watchdog before trying any normal driving.
8. Check the IMU
Section titled “8. Check the IMU”With the robot still on the stand, inspect the raw IMU message:
ros2 topic echo /imu/data_raw --onceros2 topic hz /imu/data_rawExpected result:
- the header timestamp is current
header.frame_idmatches the IMU frame you documented, such asimu_link- acceleration is in meters per second squared
- angular velocity is in radians per second
orientation_covariance[0]is-1.0if your node does not publish a fused orientation- the publish rate is close to your configured rate
Leave the robot still, then gently rotate it by hand. With bias values left at zero, a still gyro may have a small offset; turning the robot should change the matching gyro axis. Optional gyro bias calibration can reduce that stationary offset after core bringup works. If messages never arrive, use i2cdetect -y 1 again and inspect the I2C wiring and configured address.
9. Check Each Motor Direction
Section titled “9. Check Each Motor Direction”Use brief low-speed commands. Keep the wheels secured.
ros2 topic pub -1 /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.08}, angular: {z: 0.0}}"ros2 topic pub -1 /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.0}, angular: {z: 0.25}}"For positive linear.x, both sides should request forward movement. For positive angular.z, the robot should request a counter-clockwise turn using the convention you documented for left and right. If a side turns the wrong way, correct that side’s inversion value in configuration. If a motor only turns one direction, inspect that driver’s two direction inputs, the physical wiring, and the motor-driver channel before changing the ROS mixing math.
10. Check Odometry, TF, and Path
Section titled “10. Check Odometry, TF, and Path”With movement commands arriving, inspect the open-loop odometry messages:
ros2 topic echo /odom --onceros2 topic echo /path --onceros2 run tf2_ros tf2_echo odom base_linkExpected result:
/odomusesheader.frame_id: odom/odomuseschild_frame_id: base_link/pathuses theodomframetf2_echo odom base_linkshows the same pose trend as/odom- pose changes come from requested or applied commands, not measured wheel encoder travel
The path will drift because the kit has no wheel encoders. Tune linear and angular scale parameters from observed motion, but keep the code honest about being command-based.
11. Connect Your Computer
Section titled “11. Connect Your Computer”Put the computer and robot on the same non-guest Wi-Fi network. ROS 2 nodes discover one another through DDS, so both sides need matching network settings.
Choose one ROS_DOMAIN_ID between 0 and 101 for your robot. Use the same number in the robot’s SSH terminal and the computer’s Ubuntu terminal. Do not use a value someone else is using nearby.
ROS_DOMAIN_ID_VALUE=37export ROS_DOMAIN_ID="$ROS_DOMAIN_ID_VALUE"export ROS_LOCALHOST_ONLY=0Source ROS and your workspace in each terminal before using ROS commands. A topic published under one domain is invisible to a terminal in another domain.
If your computer runs Windows 11 with WSL, enable mirrored networking before testing ROS discovery. In the Windows file %USERPROFILE%\.wslconfig, add:
[wsl2]networkingMode=mirroredThen close all WSL terminals and run this in PowerShell:
wsl --shutdownOpen Ubuntu WSL again. Microsoft documents mirrored mode because it adds multicast support and lets WSL participate directly on the local network: WSL networking.
Before starting RViz or teleop, test both directions with tiny string topics.
source /opt/ros/jazzy/setup.bashexport ROS_DOMAIN_ID="$ROS_DOMAIN_ID_VALUE"export ROS_LOCALHOST_ONLY=0ros2 topic pub /network_test_from_pi std_msgs/msg/String "{data: hello_from_pi}" -r 1source /opt/ros/jazzy/setup.bashexport ROS_DOMAIN_ID="$ROS_DOMAIN_ID_VALUE"export ROS_LOCALHOST_ONLY=0ros2 topic echo /network_test_from_piThen reverse the test:
ros2 topic pub /network_test_from_computer std_msgs/msg/String "{data: hello_from_computer}" -r 1ros2 topic echo /network_test_from_computerOnly move on once both messages arrive. If discovery fails, first check the Wi-Fi network, domain number, ROS_LOCALHOST_ONLY, firewall settings, and whether both machines have working clocks:
timedatectl statusIf those checks are correct and discovery still fails, use the same middleware implementation on both computers:
sudo apt updatesudo apt install -y ros-jazzy-rmw-cyclonedds-cppexport RMW_IMPLEMENTATION=rmw_cyclonedds_cppRestart the network test after setting it on both sides. ROS 2 supports multiple middleware implementations, and the RMW_IMPLEMENTATION setting selects the one a terminal uses.
12. Use RViz From the Computer
Section titled “12. Use RViz From the Computer”Run RViz on the development computer, not the Pi.
WORKSPACE_NAME="cool_rover_ws"
source /opt/ros/jazzy/setup.bashsource ~/"$WORKSPACE_NAME"/install/setup.bashrviz2In RViz:
- set Fixed Frame to
odom - add Grid
- add TF
- add Path and set the topic to
/path - add Axes and set the reference frame to
base_link - add RobotModel only if you completed the optional robot-model polish step
Keep the view minimal. It should help you see the robot’s origin, body direction, and path. In the TF display, confirm odom -> base_link exists and that your package provides or documents base_link -> imu_link.
13. Drive Manually
Section titled “13. Drive Manually”Install standard keyboard teleop on the computer that will drive the robot:
ROS_DOMAIN_ID_VALUE=37
sudo apt updatesudo apt install -y ros-jazzy-teleop-twist-keyboardsource /opt/ros/jazzy/setup.bashexport ROS_DOMAIN_ID="$ROS_DOMAIN_ID_VALUE"export ROS_LOCALHOST_ONLY=0ros2 run teleop_twist_keyboard teleop_twist_keyboard \ --ros-args \ -p key_timeout:=0.6Watch /cmd_vel in another terminal. Terminal keyboard input depends on the terminal’s key-repeat behavior; key_timeout stops a stale held command after keyboard input stops, and the robot-side watchdog still stops the motors if teleop exits unexpectedly.
ROS_DOMAIN_ID_VALUE=37
source /opt/ros/jazzy/setup.bashexport ROS_DOMAIN_ID="$ROS_DOMAIN_ID_VALUE"ros2 topic info /cmd_vel --verboseros2 topic echo /cmd_velMove to floor driving only after stand checks pass. Start with low speed limits and enough open space to cut power without chasing the robot. If standard terminal teleop works but feels awkward on your system, the optional event-based keyboard teleop extra provides actual key-press and key-release events and can be added later.
14. Run Autonomy Last
Section titled “14. Run Autonomy Last”Only after manual movement, IMU, odometry, TF, and RViz all behave individually should you run the autonomy launch file. Begin with low speeds and a short routine. Watch the robot and /cmd_vel; stop immediately if its real behavior differs from your planned table. Do not start bringup.launch.py separately first; auton.launch.py already includes it.
WORKSPACE_NAME="cool_rover_ws"PACKAGE_NAME="cool_rover"
cd ~/"$WORKSPACE_NAME"source /opt/ros/jazzy/setup.bashsource install/setup.bash
ros2 launch "$PACKAGE_NAME" auton.launch.pyWith autonomy running, check publisher count from a second sourced terminal:
ros2 topic info /cmd_vel --verboseTeleop and autonomy should not publish at the same time unless you deliberately added code to swap between these commands.
- it sends an initial zero command before movement
- each segment matches the route table in your README
Ctrl+Cor node shutdown sends zero- the robot-side watchdog stops the motors if autonomy stops publishing
- the routine ends with zero output
After Simple autonomous routine complete appears, press Ctrl+C in the launch terminal. That removes the finished simple_auton node from the ROS graph and prevents it from remaining as a second /cmd_vel publisher when teleop is started later.
When this is working, document the command sequence, hardware pins, IMU orientation, network settings, and a short video in your README. That helps someone understand what you built and how you brought it to life!
Common Bringup Symptoms
Section titled “Common Bringup Symptoms”| Symptom | Check first | Likely fix |
|---|---|---|
ros2 launch cannot find your launch file |
ros2 launch "$PACKAGE_NAME" bringup.launch.py --show-args |
Install launch/ through setup.py data_files, rebuild, and source install/setup.bash. |
| Robot moves on startup | Motor power cutoff and launch output | Command zero before enabling outputs and initialize the driver to zero. |
Single /cmd_vel keeps motors running |
Watchdog timeout | Store the last command time and command zero when it goes stale. |
| IMU topic never appears | i2cdetect -y 1 |
Fix power, ground, SDA, SCL, address, or the Python I2C dependency. |
| IMU values move on the wrong axis | Your mounting sketch | Correct the axis mapping once; do not also rotate it again in TF. |
| RViz has no fixed frame | RViz Fixed Frame | Use odom, not map, unless you later add real localization. |
| Computer cannot see Pi topics | Two-way network test | Match ROS_DOMAIN_ID, disable localhost-only mode, check WSL mirrored networking, and test both directions. |
| Robot ignores teleop | /cmd_vel publisher and subscriber counts |
Confirm teleop publishes in the same domain and your motor node subscribes to the same resolved topic. |