Setting up ROS

28 Feb 2021 -

My first program for spot was written in pure python, but as the software stack gets more complicated I want to move to ROS for development. ROS 2 Foxy came out recently and is an LTS version so it seems like a good choice. However, with the current version of JetPack on the Jetson Nano being based on Ubuntu 18.04, ROS has to be built from source. Fortuately, Nvidia maintains docker images optimized for the Jetson platform for all of the recent versions of ROS in a github repository here.

Nvidia wrote a blog post about using the ROS 2 docker images on the Jetson platform here. Since docker is preinsalled on JetPack, I grabbed the Dockerfile and built the image. ROS 2 Foxy had to be built from source which meant the build took over an hour on my Jetson Nano. Once I had a base image with ROS, I used this article about how to use docker for robotics development by Jariullah Safi to help me as I created a Dockerfile for my ROS 2 development. My setup is largely based on that article.

Here is the Dockerfile I am currently using: (GitHub)

FROM ros:foxy-ros-base-l4t-r32.4.4
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
RUN apt-get update && apt-get install -y \
    tmux \
    zsh \
    curl \
    wget \
    vim \
    sudo \
    && rm -rf /var/likb/apt/lists/*
RUN pip3 install adafruit-circuitpython-servokit Jetson.GPIO

COPY 99-gpio.rules /etc/udev/rules.d/99-gpio.rules

ARG USER=spot
ARG UID=1000
ARG GID=1000
ARG PW=micro

RUN useradd -m ${USER} --uid=${UID} && echo "${USER}:${PW}" | \
    chpasswd && \
    groupadd -f -r gpio && \
    groupmod --gid=108 i2c && \
    usermod -a -G gpio ${USER} && \
    usermod -a -G i2c ${USER} && \
    usermod -a -G sudo ${USER}

USER ${UID}:${GID}
WORKDIR /home/${USER}

ENTRYPOINT ["./ros2_ws/src/spot_micro/entrypoint.sh"]
CMD ["bash"]

I start with Nvidia’s ROS 2 Foxy image as the base. Then, I set the locale. Next, I install some development tools that are not included in the base image such as tmux and vim. The pip3 command is to install the adafruit library for the servo controller I am using. The part before ENTRYPOINT is to setup the spot user and its permissions so it can access the servo controller board. The ENTRYPOINT command runs a script that creates a new shell with the spot user logged in. The new shell is needed for the groups to take effect.

Once the Dockerfile is all set up, the docker image can be built with docker build -t spot:foxy ..

The command to start the docker container is really long so I created a script to start the container for me. (GitHub)

#!/bin/sh
docker run -it --net=host \
  --privileged \
  -e CONTAINER_NAME=ros-container \
  --runtime=nvidia \
  --rm \
  -v "/home/$USER/:/home/spot/" \
  --name=ros2-foxy \
  spot:foxy

The docker run command starts the container. -it runs the container in interactive mode which provides a terminal for development. --net=host is important as it allows the container to access the same networking as the host operating system. This means that I will be able to communicate with ROS nodes running on other devices on the network such as my laptop. The --privilieged tag gives the container access to all of the devices in /dev/. -e CONTAINER_NAME=ros-container is a clever trick I learned from the article that allows me to tell if I am in the host OS or the docker container by checking the CONTAINER_NAME environment variable.

To make it super clear where I am, I changed my command prompt to include the value of that environment variable. Here is the line I added to my .bashrc to change the command prompt

export PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]: ($CONTAINER_NAME) \[\033[01;34m\]\w\[\033[00m\] \n[\!] \$ "

Here is what it looks like in the host operating system

spot@nano: () ~/ros2_ws/src/spot_micro
[0] $

and in the docker container

spot@nano: (ros-container) ~/ros2_ws/src/spot_micro
[0] $

Now back to the run script, --runtime=nvidia is important to allow the docker container to have direct access to the CUDA cores on the Jetson for machine learning and other high intensity computational tasks. --rm is used to delete the container after its terminal is closed. The -v commands allows the container to access the home directory from my host OS, which gives me direct access to all of my code as well as my dotfiles. This means that programs like vim and tmux will have the same configuration inside the container as they do on the outside. Finally, I give it a name with the --name=ros2-foxy command before specifying the image name and tag as spot:foxy to match the name in the docker build command.

And there we go! A docker image that contains all of the code, tools, and configuration needed to develop software for my spot micro. I think it is awesome that the environment is easily reproducible which would come in handy if the SD card ever gets corrupted or I want to make an army dance team of robot dogs!