Tuesday 13 August 2024

Running ROS2 on a Raspberry Pi 5

ROS2 on the Raspberry Pi requires the 64bit version of Pi OS Bookworm, and docker to run ROS. I found the tutorials and documentation assumed a lot of knowledge of both Docker and ROS so it was a challenge to get started. ROS on Linux is not natively supported on Debian (which the Raspberry Pi OS is based on). ROS runs on Ubuntu, so you could install that OS on the Pi, but many users want to run the Raspberry Pi OS to benefit from the support for hardware and libraries provided for Pi projects. So we need to run ROS inside an Ubuntu docker container. Most of the guides either tell you a series of commands to run which gets you through, but feeling like you don’t understand much of what is going on, while the documentation offers you endless choices or decisions to make, leaving it difficult to know which way to turn at each step. This guide is my attempt to lead you through this forest, and get you to the other side empowered with some understanding of the path we took and how things work. Links to the source documentation are provided at each stage, and these should be followed alongside this guide. We will start with a vanilla installation of Raspberry Pi OS Bookworm. Make sure you are using the 64bit version.

Installing Docker

Install docker on Debian bookworm following the instructions at https://docs.docker.com/engine/install/debian/ 


I used the method in the section: Install using the apt repository


# Add Docker's official GPG key:

sudo apt-get update

sudo apt-get install ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings

sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc


# Add the repository to Apt sources:

echo \

  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \

  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \

  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update


sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin


Next follow the post install steps:

https://docs.docker.com/engine/install/linux-postinstall/ 


Create the docker group.

sudo groupadd docker


Add your user to the docker group.

sudo usermod -aG docker $USER


Log out and log back in so that your group membership is re-evaluated.


Now you should be able to run docker commands without needing to use sudo.


Test docker is working:

docker run hello-world


This should show a success message.


Building a ROS Docker Container

Following the documentation at https://docs.ros.org/en/jazzy/How-To-Guides/Installing-on-Raspberry-Pi.html 


I built a docker image myself, in order to get the desktop variant required to run the tutorials. First clone the ROS  docker images git repo from your home directory:

git clone https://github.com/osrf/docker_images


The docs then say to navigate to the folder for the release you want and build the container. But which folder to choose? I went with the ‘Jazzy Jalisco’ version of ROS2, as the latest stable supported release so it has the longest support lifetime. You also need to choose a version of Ubuntu to run it on. I chose noble as the only option for Jazzy docker files for all the ROS distributions. We are going to build the desktop version, as this has all the tools we need for the tutorials. But the container image will be around 2.4GB in size, so make sure you have enough free disk space. The ROS tutorials should work with other supported distributions, so you could choose the rolling/jammy option if you want to try the latest development build. But while I found this worked for the ROS CLI tools tutorials, it failed to build all the examples in the Client libraries tutorial. Also the Rolling desktop docker image was 3.3GB in size, and used python 3.10 whereas the jazzy image used python 3.12. So I have switched back to Jazzy for now.


Navigate to the folder ~/docker_images/ros/jazzy/ubuntu/noble/desktop

Then run the command:

docker build -t ros_docker .


Note the ‘.’ on the end of the command, it is important, as it tells docker to use the docker file in the folder you are running the command in.


This creates a docker container image, not a container. It will take a while (a go and make a coffee amount of time). When it completes, you should be able to see a docker image named ros_docker if you run the command:

docker image ls


Now we can create a docker container from our image:

docker run -it ros_docker


This will create a contain from the image and open an interactive shell in the container, so now the command prompt is open in a shell inside the container. 


At this point we have a ROS container. Open a new terminal window on the Pi and type:

docker ps


You should see your container listed as a running container.


We need to configure the environment for ROS. https://docs.ros.org/en/jazzy/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html 

We want this configuration to apply for any future shell sessions so we will put the commands into the bash.rc file in our container by entering the command:

echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc


Now if we exit and re-enter our container the environment configuration should stick. Type ‘exit’ to leave the container shell. The container remains running (check with docker ps). We can enter a new interactive shell in it by using the container name. Names are generated randomly when containers are created. So you need to note the name your container has when you list it with the docker ps command. Then run:

docker exec -it <container_name> bash


But substitute <container_name> with the actual name of your container. Once in the new shell, we can test that our environment is configured as expected:

printenv | grep -i ROS


This should list all the ROS environment variables. So you should see something like this:

ROS_VERSION=2 ROS_PYTHON_VERSION=3 PWD=/ros2_ws AMENT_PREFIX_PATH=/opt/ros/jazzy CMAKE_PREFIX_PATH=/opt/ros/jazzy/opt/gz_math_vendor:/opt/ros/jazzy/opt/gz_utils_vendor:/opt/ros/jazzy/opt/gz_cmake_vendor ROS_AUTOMATIC_DISCOVERY_RANGE=SUBNET PYTHONPATH=/opt/ros/jazzy/lib/python3.12/site-packages LD_LIBRARY_PATH=/opt/ros/jazzy/opt/rviz_ogre_vendor/lib:/opt/ros/jazzy/lib/aarch64-linux-gnu:/opt/ros/jazzy/opt/gz_math_vendor/lib:/opt/ros/jazzy/opt/gz_utils_vendor/lib:/opt/ros/jazzy/opt/gz_cmake_vendor/lib:/opt/ros/jazzy/lib PATH=/opt/ros/jazzy/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ROS_DISTRO=jazzy


If all that is as expected, then we should be able to run a ros2 command:

ros2 pkg executables 


This should list all the ros2 packages which are installed (the desktop version has quite a lot of them).


We now have a working ROS2 install on our Raspberry Pi. But before we get into the tutorials, it will be helpful to know a few more docker commands.


Some Useful Docker Commands

Now we have played around with docker a bit, it is useful to know how to manage the docker artifacts we may have created. For example, we will have created a local copy of the hello-world image, and a hello-world container for each time we ran the docker run hello-world command.


List your docker images:

docker image ls


List your docker containers:

docker ps -a


(The -a flag includes stopped containers. Without it, only running containers are shown).


To stop and start containers, we can use their container names with these commands:

docker start <container_name>

docker stop <container_name>


To clean up docker containers, note their container ID shown with the docker ps -a command and delete them using their ID:

docker rm <container ID>


Each time we use the docker run command with an image name, we create a new container. We don’t want this, as our environment will not be configured in the new container. So use start and stop instead once we have a container. (This goes against the philosophy of Docker, where containers are created and destroyed so you always run from a clean state. But we are just running some tutorials here, so our container will be treated as persistent with the configuration here. We’ll go deeper into docker and the docker compose tools in another tutorial).


We have run some shell commands, but our container will not have a graphical context so the GUI tools will not run. Try running the rqt command in your container and you will get an error about not being able to access the display. We need to give our container the hooks to access our display.

Following: https://wiki.ros.org/docker/Tutorials/GUI 

I used the simple (insecure) option. Unfortunately you have to provide the parameters when you create the container, so we will have to start again with a fresh container.


docker run -it \

    --env="DISPLAY" \

    --env="QT_X11_NO_MITSHM=1" \

    --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \

    ros_docker


Now we have a container which can access the graphics display of the host Pi. We need to configure the environment again in our new container:

echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc


This only needs to be done once in the new container. We also need to grant permissions to allow the display to be accessed. In a terminal in the host Pi, run the command:

xhost +local:root


Note this is not secure, but it only lasts until the next reboot, so I was happy with this on a private network. The linked article goes into more detail, and gives some other options. But this was the simplest one to get working on a Raspberry Pi.


Now, in the shell in the container, you should be able to run the command:

rqt


The GUI tool window should open successfully on your host Pi desktop. Now you have a container which can run graphical tools. If you get an authorisation error, check you have run the xhost +local:root command again since you last booted up the Pi.


Remember we just created a new container, so run docker ps -a to find out what the new container name is, and use this one for the graphical tools in the tutorials. You can rm the older container as we no longer need it.


Recap

First we built a docker container image for a specific version of Ubuntu and a specific ROS2 distribution. We used the desktop variant as it contains all the packages needed for graphical demos and the tutorials.

Next we created a docker container using this image to run ROS2. We created it with a configuration so it can run graphical tools on the host desktop. We set up the bash.rc file to configure the shell each time we start a new shell, so the ROS2 tools can be run. Once we had created this container, we start it with docker start <container_name> and enter a new shell with the command docker exec -it <container_name> bash


We don’t need to use docker run again, unless we want to create a new container with a different configuration.


Running the TurtleSim Tutorial in Docker

The turtlesim tutorial takes you through some basics of ROS using a graphical simulation tool and the ROS GUI tool RQT. The documentation is at:

https://docs.ros.org/en/jazzy/Tutorials/Beginner-CLI-Tools/Introducing-Turtlesim/Introducing-Turtlesim.html

But we can skip the install steps with our preconfigured docker container and jump straight to the run step. First open an interactive shell in your docker container (you need it to be started first). Then in the shell, run the command:

ros2 run turtlesim turtlesim_node


The turtlesim window should appear on your host desktop. 

The shell window will be tied up now, running the simulation node and displaying output from it. So to run another node to control the turtle, we need to open a new terminal window on the host, and start a second interactive shell in the same docker container:

docker exec -it <container_name> bash


Now in this second shell, we can run the turtle_teleop_key node:

ros2 run turtlesim turtle_teleop_key


This should launch the turtle_teleop_key node and you should be able to control the turtle with the arrow keys. The tutorial then suggests some ros2 commands you can run. So you will need a new terminal window to execute another interactive shell in the docker container. You can use this to run the ros2 commands to list nodes, and later run the rqt tool to follow the tutorial steps to execute various api calls to the turtlesim node.


Hopefully this has got you up and running with ROS in Docker on the raspberry Pi. If there are any steps that didn’t make sense, please let me know so I can fill in any gaps in this guide.


See the next post in this blog for how to extend this using Docker Compose.

3 comments:

  1. Yes, this is a great start. Another tip: since it takes so long to build the ROS image, I broke my Docker image build into two images - the first is the base OS+ROS2+MyRobot'sHardwareAPI that takes forever to build, and then the second quickly extends the base image with my attempts to add packages and configurations that will fail a few times before I get it right.

    My Docker build scripts and Dockerfiles are at: https://github.com/slowrunner/GoPi5Go/tree/main/config/docker

    ReplyDelete
  2. Hi, Thanks a lot for your post. I am getting error when trying to run display turtlesim window inside the docker container. I followed the steps given above for exporting display from docker. The error is as follows:
    -------------------------------
    swg@8ea56f12a962:~$ ros2 run turtlesim turtlesim_node
    qt.qpa.xcb: could not connect to display :0
    qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in " " even though it was found.
    This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
    Available platform plugins are: eglfs, linuxfb, minimal, minmalegl, offscreen, vnc, xcb.

    [ros2run]: Aborted
    -------------------------------------
    I will appreciate if you can help me fix this problem.

    Regards,
    Swagat

    ReplyDelete
    Replies
    1. This looks like the errors I saw before I configured the container to have access to the display. Make sure you built the docker container with the display parameters as covered in my blog post. I got this working by following the insecure 'simple way' detailed in this article: https://wiki.ros.org/docker/Tutorials/GUI#The_simple_way
      and setting the permissions with xhost +local:root
      This worked on Raspberry Pi OS Bookworm. Sorry I can't help beyond that as I was learning myself from the article linked above to get this working in the first place.

      Delete