Set up a Rover Robotics platform with ROS 2 by hand, one script at a time. These scripts install ROS 2, clone and build the rover workspace, configure the robot for its wheels and controller, install udev rules, and optionally create a systemd service that starts the driver at boot.
| Operating system | Ubuntu 22.04 (Jammy) or Ubuntu 24.04 (Noble) |
| ROS 2 | Humble (on Jammy) or Jazzy (on Noble) |
| Hardware | Intel/AMD x86, Raspberry Pi, or NVIDIA Jetson (AGX Orin / Orin Nano). The installer detects which and adapts. |
| Network | Internet connection required |
| Privileges | A user with sudo. Do not run the scripts with sudo; they call it themselves where needed. |
git clone https://github.com/RoverRobotics/rover_install_scripts_ros2
cd rover_install_scripts_ros2
chmod +x *.shSkip this if ROS 2 is already installed.
./ros2_installation.shYou will be asked which distribution (Humble or Jazzy) and whether you want the
desktop or base package set. Pick base for a headless robot computer;
desktop adds RViz and the GUI tools.
Non-interactive options
-d, --distro SEL 1 or "humble" | 2 or "jazzy"
-p, --package SEL 1 or "desktop" | 2 or "base"
-y, --yes Non-interactive apt
-f, --force Install even if the Ubuntu release does not match
--upgrade Also run a full 'apt-get upgrade' first (off by default)
--refresh-keys Re-download the ROS keyring (fixes signature errors)
-h, --help Show usage
./ros2_installation.sh -d humble -p base -y--upgrade is off by default on purpose: on a Jetson a full upgrade can pull
in an L4T or kernel update you did not ask for.
Only NVIDIA Jetson boards need this step. Intel/AMD x86 machines and
Raspberry Pi already ship the gs_usb module in the stock Ubuntu kernel and
autoload it when the adapter is plugged in, so there is nothing to do.
The L4T/Tegra kernel does not ship gs_usb. Without it the USB-CAN adapter
enumerates as a USB device but never produces a CAN network interface, so the
rover looks dead on the bus.
./setup_jetson_can.shThis builds gs_usb against the running kernel, installs the udev rule, and
sets up can.service. See
Nvidia_Kernel_Mod_for_jetpack_6plus.pdf
for the background.
If you skip this step on a Jetson, setup_rover.sh will detect the missing
module and tell you.
./setup_rover.shThe installer asks for the following.
Mini 2WD, Mini (4WD), Miti 65, Miti, Zero, Pro, Max, Mega.
MAX wheels come in two sizes the installer supports, each needing a different config and URDF:
| Wheels | Config | Wheel radius |
|---|---|---|
| 13 inch | max_130_config.yaml |
0.1651 m |
| 15 inch | max_150_config.yaml |
0.1905 m |
The 6.5 inch and 10 inch variants are being phased out and are not offered by
the installer. Their configs (max_65_config.yaml, max_100_config.yaml) still
ship in roverrobotics_ros2 if you need them. Point max.launch.py at one by
hand.
The installer sets both the config and the URDF in max.launch.py to match.
Getting this wrong does not throw an error. It silently scales odometry and
commanded velocity by the ratio of the two wheel radii.
Every *_teleop.launch.py in roverrobotics_ros2 ships pointed at
ps4_controller.launch.py. The installer repoints them at whichever pad you
select, so a PS5 (DualSense) works without hand-editing a launch file.
JetPack 6 (L4T R36.x) enumerates joystick axes and buttons differently from
JetPack 5 and from desktop Ubuntu, so the repo carries a _jp6 variant of each
controller config. The installer detects JetPack 6 and defaults accordingly.
If your sticks and triggers come out swapped, re-run with the opposite choice:
./setup_rover.sh -r miti -g ps5 --jp6 # force the JetPack 6 map
./setup_rover.sh -r miti -g ps5 --no-jp6 # force the stock map- Udev rules: stable
/devnames for the ESCs, IMU, LIDAR and GPS. On by default. - BNO055 IMU: clones and builds the IMU driver.
- RPLIDAR S2: clones and builds the LIDAR driver.
- Automatic start service:
roverrobotics.service, starts the driver at boot. Decline this if you prefer to launch by hand.
Installing a sensor driver does not switch it on by itself: set active: true
for that sensor in roverrobotics_driver/config/accessories.yaml, since
accessories.launch.py only starts a node whose active flag is true.
Asked separately, because how it installs depends on the computer:
| Platform | librealsense SDK | ROS wrapper |
|---|---|---|
| Intel/AMD x86, Raspberry Pi | prebuilt packages (ros-$ROS_DISTRO-librealsense2, falling back to Intel's apt repo), quick |
ros-$ROS_DISTRO-realsense2-camera from apt |
| NVIDIA Jetson | built from source with CUDA when available, ~45 minutes | realsense-ros built in your workspace |
Intel publishes no arm64 debs, which is why a Jetson has to build the SDK. The installer detects the platform and picks the right path; you are only asked whether you want RealSense at all.
Unplug the camera before the Jetson SDK build. The installer says so and waits. Plug it back in afterwards.
You are then asked whether to install rover-realsense.service, which
starts the camera at boot. It runs reset_realsense_usb.sh before every start,
power-cycling the camera over USB. The D435i is prone to enumeration failures
that a plain restart does not clear. The reset needs a NOPASSWD sudoers
entry, which the installer writes to /etc/sudoers.d/rover-realsense, scoped to
that one script.
Offered for every CAN-connected model (Mini, Miti, Miti 65, Max, Mega). See CAN interface naming below.
Non-interactive options
-r, --robot TYPE mini_2wd | mini | miti_65 | miti | zero | pro | max | mega
-w, --max-wheel IN MAX wheel size in inches: 13 | 15
-g, --gamepad PAD ps4 | ps5
-d, --distro NAME ROS 2 distro (humble, jazzy)
--with-imu Install the BNO055 IMU repository
--with-lidar Install the RPLIDAR S2 repository
--with-realsense Install Intel RealSense support (SDK + ROS wrapper)
--no-realsense Skip RealSense without being asked
--with-rs-service Install rover-realsense.service (implies --with-realsense)
--with-service Install the roverrobotics.service autostart unit
--no-udev Skip the udev rules
--jp6 Force the JetPack 6 gamepad mapping
--no-jp6 Force the stock gamepad mapping
-y, --yes Non-interactive
-h, --help Show usage
./setup_rover.sh --robot miti --gamepad ps5 --with-service -y
./setup_rover.sh -r max -w 13 -g ps5 --with-imu -y--yes requires --robot, and --robot max also requires --max-wheel.
There is no safe default for either.
Most M-series rovers (Mini, Miti, Max, Mega) connect over a USB-CAN adapter
(1d50:606f: CANable / candleLight / InnoMaker USB2CAN V3.3).
The kernel's canN names are not stable. On the Jetson AGX Orin the two
onboard mttcan controllers and the USB adapter race for can0/can1/can2
at boot: the same adapter can come up as can2 on one boot and can0 on the
next. Anything pinned to a canN name will, on some boots, bind to an onboard
controller with nothing wired to it. That interface comes up UP and
ERROR-ACTIVE and looks perfectly healthy, but carries no data, and the driver
logs Did not receive any data from the robot, indistinguishable from a
genuinely failed bus.
udev/99-can-usb.rules therefore binds the adapter to the fixed name
rovercan by USB vendor and product ID, and setup_rover.sh updates
device_port in the robot's config to match. The same name is used by
enablecan, can-watchdog, setup_jetson_can.sh and the driver config, so
everything agrees.
Note: that config edit is local to your clone. Running
git pullinside~/rover_workspace/src/roverrobotics_ros2revertsdevice_porttocan0. Re-runsetup_rover.shafterwards, or re-apply it by hand.
The adapters used on these rovers support classic CAN only: not CAN-FD and
not berr-reporting. enablecan tries CAN-FD first and falls back, reporting
which mode it settled on.
| Path | Purpose | Installed by |
|---|---|---|
~/rover_workspace/ |
ROS 2 workspace and source repos | setup_rover.sh |
/etc/udev/rules.d/55-roverrobotics.rules |
Stable /dev names for ESCs, IMU, LIDAR, GPS |
setup_rover.sh |
/etc/udev/rules.d/99-can-usb.rules |
Renames the USB-CAN adapter to rovercan |
both |
/etc/modules-load.d/gs_usb.conf |
Loads gs_usb at boot (Jetson only) |
both |
/usr/sbin/enablecan |
Resets the adapter and brings the CAN link up | both |
/etc/systemd/system/can.service |
Runs enablecan at boot |
both |
/usr/sbin/can-watchdog + .service + .timer |
Restarts can.service if the link drops |
setup_rover.sh |
/usr/sbin/can-selftest |
Says whether a silent bus is the adapter or the rover | setup_rover.sh |
/usr/local/sbin/reset_realsense_usb.sh |
Power-cycles the camera over USB before each start | setup_rover.sh (RealSense service) |
/etc/sudoers.d/rover-realsense |
NOPASSWD for that one reset script | setup_rover.sh (RealSense service) |
/etc/systemd/system/rover-realsense.service |
Starts the camera node at boot | setup_rover.sh (RealSense service) |
/usr/sbin/roverrobotics |
Sources ROS and the workspace, launches the driver | setup_rover.sh |
/etc/systemd/system/roverrobotics.service |
Starts the driver at boot | setup_rover.sh |
source ~/rover_workspace/install/setup.bashCAN bus. Expect UP and ERROR-ACTIVE, and traffic from the VESCs:
ip -details link show rovercan
candump rovercanIf the interface is up but candump shows nothing, run the self-test rather
than guessing:
sudo can-selftestIt puts the adapter into loopback, where it transmits to itself and needs no rover, no VESCs and no wiring. If the frame comes back the adapter is fine and the problem is rover power, the cable or termination. If it does not, the adapter itself is wedged. The test restores normal mode when it finishes.
Driver. Launch it by hand:
ros2 launch roverrobotics_driver miti_teleop.launch.pyOdometry. Expect a steady rate:
ros2 topic hz /odometry/wheelsGamepad. Expect axis values to change as you move the sticks:
ros2 topic echo /joyCamera (if installed):
ros2 topic hz /camera/camera/color/image_raw
realsense-viewer # Jetson source build onlyServices:
systemctl status can.service roverrobotics.service rover-realsense.service
journalctl -u roverrobotics.service -f| Symptom | Likely cause | What to do |
|---|---|---|
rovercan does not exist |
udev rename has not run since the adapter was plugged in | Unplug/replug the adapter, or reboot |
rovercan does not exist, Jetson |
gs_usb not built for the L4T kernel |
Run ./setup_jetson_can.sh |
rovercan does not exist, x86 or Pi |
Module missing from the kernel | sudo apt-get install linux-modules-extra-$(uname -r) |
Interface is up but candump is silent |
Adapter wedged, or rover unpowered | sudo can-selftest tells you which |
| Adapter works, then dies after every reboot | Some boards wedge on a warm reboot | See USB-CAN adapters that need a replug below |
| Bus worked, then went silent mid-run | Adapter knocked loose | can-watchdog.timer re-runs can.service every 30 s; see journalctl -u can-watchdog |
Did not receive any data from the robot |
Same as above | Same as above |
RTNETLINK answers: Operation not supported |
CAN-FD options on a classic-CAN adapter | Expected; enablecan falls back automatically |
| Sticks and triggers swapped | Wrong gamepad map for this kernel | Re-run with --jp6 or --no-jp6 |
| Controller does nothing | Teleop launch pointed at the other pad | Re-run with -g ps4 or -g ps5 |
| Odometry distance is consistently off | Wrong MAX wheel size | Re-run with -w 13 or -w 15 |
| Driver dies at boot, works by hand | Driver started before the CAN bus | journalctl -u roverrobotics.service; the unit is ordered After=can.service and retries every 5 s |
| Wheels keep turning for about a second after the service stops | Unit installed by an older setup_rover.sh, without the graceful-stop settings |
Re-run ./setup_rover.sh --with-service; the unit now sets KillMode=mixed, KillSignal=SIGINT, TimeoutStopSec=15, so the driver can brake the motors as it exits |
| Wheels coast for about a second after the driver crashes | A hard crash or kill -9 cannot send the brake; the VESCs hold the last command until their own timeout, then release |
In VESC Tool, set a Timeout Brake Current on each VESC so it brakes instead of coasting when commands stop |
UnicodeDecodeError from colcon or rosdep |
Non-UTF-8 locale | Re-run ./ros2_installation.sh, which configures the locale |
apt update signature error |
Stale ROS keyring | ./ros2_installation.sh --refresh-keys |
udev /dev symlinks missing |
setserial not installed |
Re-run ./setup_rover.sh; it is in the package list |
| Camera node starts then dies repeatedly | D435i USB enumeration failure | rover-realsense.service power-cycles it each start; check journalctl -u rover-realsense |
librealsense2 has no install candidate |
Intel publishes no build for this Ubuntu release | Build from source, or use a release Intel supports |
| Sensor installed but no topics | active: false in accessories.yaml |
Set active: true for that sensor and rebuild |
After changing a config or updating the driver, restart the service instead of rebooting:
sudo systemctl restart roverrobotics.serviceAll scripts are safe to re-run. setup_rover.sh updates an existing clone in
place with git pull --ff-only rather than failing, and re-applies the CAN
name, wheel size, controller and gamepad map every time, so re-running after a
git pull is the supported way to restore them.
Anything patched into the source tree is applied before colcon build. If
you edit a launch file or config by hand afterwards, rebuild:
cd ~/rover_workspace && colcon buildRemoves the services, udev rules and helper scripts. It does not delete
~/rover_workspace.
./uninstall_rover_script.shNot all 1d50:606f adapters behave the same. Some boards come back cleanly
from a warm reboot; others enumerate, bind to gs_usb and bring the interface
UP, but their CAN core stays dead until the module is physically unplugged
and plugged back in.
A canable.io CANable was measured in this state on a Jetson AGX Orin (L4T R39). It failed loopback, meaning it could not receive even its own frames, so the fault was in the adapter rather than the bus. None of the following recovered it:
- toggling
/sys/bus/usb/devices/<dev>/authorized usbreset(USBDEVFS_RESET port-reset signalling)- unbinding and rebinding the
gs_usbdriver - unbinding and rebinding the USB device, forcing full re-enumeration
- reloading the
gs_usbkernel module - rebinding the parent hub
- cutting the port's VBUS with
uhubctl(port status confirmedoff)
Only a physical replug cleared it. If your adapter behaves this way, either reflash its firmware (these boards expose a DFU interface) or use one that survives a reboot. The InnoMaker USB2CAN V3.3 does.
sudo can-selftest identifies the condition in about two seconds instead of
sending you looking at cables.
- The
_neoURDF variants (max_130_neo,max_150_neo,miti_neo) are a different chassis, not a different wheel size. The installer does not select them; set them by hand in the launch file if you have that chassis. - MAX 6.5 inch and 10 inch are being phased out and are not selectable. Their configs still ship upstream.
device_port, the MAX wheel size and the controller choice are edits to your local clone ofroverrobotics_ros2. Agit pullreverts them.
Check the Rover Robotics documentation or open a discussion on GitHub.