Welcome!

Hi! Welcome to A Friendly Solution Guide to F1Tenth Labs! We hope this guide will make it easy to get started with ROS (the Robot Operating System) and the F1Tenth Labs.

Who We Are

This page is courtesy of North Carolina State University's Embedded Machine Learning Club! Find out more about us at our homepage and our GitHub.

A Quick Guide to Navigating this Book

To the left, on the summary list, you can find all the chapters, subchapters, and whatnot that this book is comprised of! At the top of the page, next to the sidebar, you will find a hamburger menu, a paintbrush, and a magnifying glass. The hamburger menu allows you to collapse the sidebar, the paint brush lets you change the theme, and the magnifying glass allows you to search all the textin this book!

Things to Know!

This guide will be using ROS Noetic, so we uh highly recommend using it. Though a fair amount of ROS stuff should stay consistent between versions, you may have to do some extra research to get stuff up and running.

With that out of the way, let's move on to setting up our development environment!

Setting Up Your Development Environment

Table of Contents

  1. Things You'll Need
  2. Quick Docker Command Summary
  3. Getting the ROS Docker VNC Image (technically optional)
    1. Pulling the Image
  4. Running the Container
  5. Entering the Container
    1. Desktop Interface
    2. Terminal Interface
    3. Editing with VSCode

P.S.: Please use NeoVim, it's awesome and I (Arvin) built on an excellent configuration here. Also, if you use emacs, you're dead to me /j.

A lot of this guide is completed in a Docker container. I know some nerd's gonna say something along the lines of "why would you ever use a Docker container as a development environment," and to them I say, "leave me alone, I'm new to Docker."

Now, if you want to guide to how to use Docker, check out the next subchapter, (after you get Docker set up, of course). It should be noted, however, that the rest of the guide will directly use Docker commands without too much explanation about why exactly we use those commands. Also, if this guide is not sufficient, feel free to google, I'm by no means an expert in Dockering.

Feel free to just skip this entire page if you already has ROS Noetic.

Things You'll Need

Sorry, I didn't warn you this page was structured like a cookbook /j.

  • Pineapples
  • A Docker installation (see here)
    • Note: If you're using Windows, I highly recommend the WSL version of installation.
  • A decent internet connection (the ability to download about 4GB of data.)

Before proceeding, it may be useful to check out the next subchapter.

Quick Docker Command Summary

Note, command options marked in square brackets ([]) are optional. Additionally, command options marked with angle brackets (<>) customizable. For example, command [-a] <name> means that both command -a hypotenuse and command pineapple are valid commands.

CommandSubset of UsageMeaningMore Info
psdocker ps [-a]List all the containersInfo
imagesdocker imagesSee all docker imagesInfo
cpdocker cp <host-directory> <container-name>:<container-directory>Copy a directory from the host to the container with the matching nameInfo
cpdocker cp <container-name>:<container-directory> <host-directory>Copy a directory from the container with the matching name to the hostInfo
rundocker run <image-name> [--name <container-name>]Create and start a docker container. This command has a lot of options, see the infoInfo
startdocker start <container-name>Start a stopped docker container.Info
stopdocker stop <container-name>Stop a docker container.Info

Getting the ROS Docker VNC Image (technically optional)

If you head to our GitHub project embedml/docker-ros-desktop-vnc, you'll find more thorough instructions.

Pulling the Image

Let's start by grabbing our Docker image from hub.docker.com. This can be done with the following command:

$ docker pull arvinskushwaha/ros-noetic-desktop-vnc

If you get some sort of error that looks like this:

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1": dial unix /var/run/docker.sock: connect: permission denied

Don't worry, it's just a permissions error. You'll have to rerun the command with sudo as follows:

$ sudo docker pull arvinskushwaha/ros-noetic-desktop-vnc

Same usually goes for any future errors of that form. More information on this can be found here. If you must use VSCode to edit within the container, you may have to do some wizardy from the aforementioned link to get the sudo prefix to not be required.

Now, when you do this command, you'll see something that looks like the following:

The output from the docker pull command consisting of a series of docker image hashes followed by a progress bar representing the download and extraction progress.

Running the Container

From our Docker image, we can build a container. This is done with the following command (do not run the following command yet):

$ docker run --name <container-name> -d arvinskushwaha/ros-noetic-desktop-vnc

The above command is essentially the base command which we add modifiers to. Also, where it says <container-name>, replace it with a name of your choice.

For these options, it is important to remember that they cannot be changed without just deleting and recreating the container. For tons of information on the options, check here.

Here are what the current command-line options mean:

  • --name <container-name>: Each container is given a unique ID, and this option lets you customize it. It is important to note that this value must not conflict with other containers. This will be the identifier by which you reference a container.
  • -d: This tells Docker to launch the container "detached". Without this option your terminal would simply freeze, as the container's main process just sleeps indefinitely.

Other command-line options you should know of:

  • --net=<network>: This tells Docker to use a different network than the default (bridge). Values are typically host, bridge, container, or none. You can also create custom network configurations with Docker, but I am definitely not knowledgeable enough to say anything about that. Essentially, if you set --net=host, you never have to worry about container and host ports.

  • -p <host-port>:<container-port>: Now, to worry about container and host ports. The VNC/NoVNC servers the container runs use port 5900 and 80 respectively. This means we can control how they are exposed on the host-side with the -p option, or just use them as-is with the --net=host option. Additionally, if there are other servers running inside the container, additional -p options may be added for those. As an example, if I wanted to map the VNC server port to port 3000 on localhost, I would add -p 3000:5900 as an option.

  • --device=<host-path>:<container-path>[:modifier]: This option allows you to pick and chose which devices are passed through to the container. Since each device is a file in linux, we must pass in the device paths. For example, /dev/sda. If I wanted to map joystick 0 (/dev/js0) from the host to the container, I would add --device=/dev/js0:/dev/js0 as an option. I don't know must about the modifiers, so I'll just link the reference here.

  • --privileged: This option is kind of dangerous. It, for all intents and purposes, gives full access to your device from the container. This can be useful to passthrough any device needed. See the reference above.

For my default setup, I'm going with the following:

$ docker --name rosenv -p 80:80 -d arvinskushwaha/ros-noetic-desktop-vnc

This allows me to just access the NoVNC server by typing in localhost in my browser.

Entering the Container

Now that we've made our container, it would be useful to be able to actually do things in it. There are two main ways of interacting with the container: terminal and desktop.

Desktop Interface

If you would like to use the desktop interface, you must have mapped either the VNC or NoVNC server ports such that it's accessible to the local host.

Then, for VNC, you can download some form of VNC server and connect to localhost:<mapped-vnc-port>. Or, in the case of NoVNC, just open localhost:<mapped-novnc-port> in the browser.

This is what it looks like:

An empty desktop with a taskbar in an instance of Firefox

Terminal Interface

If you would prefer using the terminal, execute the following command:

$ docker exec -it <container-name> bash

This will open a shell prompt that looks something like the following:

root@67c3c27e7401:/root#

Now, you may enter ./login, a nice simple login script we've written, or just do cd /home/ubuntu; su ubuntu -.

root@67c3c27e7401:/root# ./login

This shows yet another prompt:

root@67c3c27e7401:/root# ./login
ubuntu@67c3c27e7401:~$

To exit, execute exit twice. The first time should exit back to root and the second should exit the container.

root@67c3c27e7401:/root# ./login
ubuntu@67c3c27e7401:~$ exit
exit
root@67c3c27e7401:/root# exit
exit

Great! Now that we've got that down, let's talk about our in-container development environment.

In the home directory (/home/ubuntu), you will find two files and a directory:

  • install_dev_tools: This file is run when the image was originally built. It contains information about the development tools installed on the container, i.e. NeoVim. This makes it easy for those who choose to primarily use the terminal to edit their files.

  • upgrade: This script essentially just updates Ubuntu packages. It's recommended to execute this every once in a while and as soon as you open this container for the first time. It may take some time to execute.

  • ros_ws: This directory is the main ROS workspace. It is where you will do most of your development.

Editing with VSCode

If you want to use VSCode to do most of your development and don't want to bother with having to manually open the Docker container and whatnot all the time, I highly suggest adding this VSCode extension pack. More information about remote development in VSCode may be found here.

The Magic of Docker

If you're looking at this guide, you're probably relatively familiar with Virtual Machines (VMs). If not, feel free to check out the appendix of this page.

Note: This page is a ROUGH DRAFT and has been written by someone who is not an expert on Docker. If you find any inaccuracies, please file an issue along with a source or citation and we'll fix it ASAP.

Docker is a set of services to perform virtualization. The software you'll become most familiar with is called Docker Engine. Unlike Virtual Machines, which virtualize all aspects of a system down to the hardware, Docker containers build upon the Linux kernel itself. The Linux kernel provides utilities that the Docker Engine takes advantage of to containerize and isolate the applications run in Docker containers.

For operating systems like Windows and MacOS, Docker must still run on a Linux kernel. For MacOS, this is done through a small Linux VM, and for Windows, this is done either with Hyper-V or WSL.

Why are we using Docker?

Unfortunately, ROS (Robot Operating System) is not particularly portable, so to make up for that, we use Docker to sort of bridge between the OS we are currently running on and the OS that ROS is supported on (which for ROS Noetic, is Ubuntu 20.04).

Appendix

Welcome to the Appendix! Here we will be looking at virtual machines (and maybe more stuff) in a bit more detail.

Virtual Machines

Virtual Machines are awesome.

Getting into ROS

Before we get into the F1Tenth labs, let's talk ROS. ROS, the Robot Operating System, is somewhat of a misnomer. It's not really an operating system and more of a set of development tools for robots. It provides some low-level device control and a message-passing framework, amongst other things.

Following this page, you will find many introductions to different ROS concepts.

Nodes and Message Passing

Topics, Subscribers, and Publishers

Introduction to F1Tenth

F1Tenth is a community of individuals enthusiastic about Autonomous Systems. This amazing community has put together many labs and competitions to bring attention to and showcase the science behind autonomous vehicles. The following chapters aim to behave as a sort of walkthrough for each of the F1Tenth labs.

Lab 1

Welcome to Lab 1! Below, you will find an embedded PDF with the Lab 1 guide. Feel free to open it up in another tab.

A small preface: commands to be executed in the terminal will be given as follows:

(<path-to-directory>) $ <command> [<arguments> ...]

If the (<path-to-directory>) segment is omitted, the command may be executed from anywhere.

Setting up the ROS Workspace

If you are using the docker container, you will find that the ros_ws directory has already been created for you. Otherwise, you will have to create it yourself. This can be done with the mkdir command. Navigate to your home directory (any directory should work, but we will use the home directory throughout this guide) and execute:

(~/) $ mkdir ros_ws

Now navigate inside the ros_ws folder and make a folder named src. By now, the folder structure should look like:

ros_ws
`-- src

From here, we can use the catkin_make command-line utility. Run the following command from inside the ros_ws directory:

(~/ros_ws/) $ catkin_make

From here, catkin (our build tool) will set up the rest of the folder structure:

ros_ws
|-- build
|   |-- CATKIN_IGNORE
|   |-- CMakeCache.txt
|   |-- CMakeFiles
|   |-- CTestConfiguration.ini
|   |-- CTestCustom.cmake
|   |-- CTestTestfile.cmake
|   |-- Makefile
|   |-- atomic_configure
|   |-- bin
|   |-- catkin
|   |-- catkin_generated
|   |-- catkin_make.cache
|   |-- cmake_install.cmake
|   |-- gtest
|   `-- test_results
|-- devel
|   |-- _setup_util.py
|   |-- cmake.lock
|   |-- env.sh
|   |-- lib
|   |-- local_setup.bash
|   |-- local_setup.sh
|   |-- local_setup.zsh
|   |-- setup.bash
|   |-- setup.sh
|   `-- setup.zsh
`-- src
    `-- CMakeLists.txt

Don't let that scare you off though, most of it is completely irrelevant to us! As you can see, under the devel folder, a setup.bash file was created. This file essentially gives us access to a bunch of ROS utilities and sets some environment variables that describe the location of our workspace. We will now "source" that file as follows:

(~/ros_ws/) $ source devel/setup.bash

Assuming you are using the Docker container and the workspace named ros_ws, the following, less verbose command:

$ rossource

may be used instead of the source command preceding it.

Creating our Package

For lab 1, we will put our code in a package titled lab1 (so original, I know). To create such a package, we use the catkin_create_package command (a bit verbose, yes?), which has the following syntax:

catkin_create_package <package-name> [<package-dependencies> ...]

Here are some basic dependencies you should be aware of:

  • roscpp: If you prefer to use C++ for your ROS shenanigans, roscpp is a necessary dependency.
  • rospy: If you prefer Python to be your weapon of choice, rospy is essential.
  • std_msgs: Sometimes you need to communicate with other ROS nodes (like always). If your messages are a primitive type, like Float32 or Bool, std_msgs provides those.
  • sensor_msgs: If your communication involves sensor data, you will likely use sensor_msgs. For example, the LaserScan message.

Of course, you can always add or remove dependencies later, so don't stress too much about it. To start with, we'll use rospy, roscpp, std_msgs, and sensor_msgs. Hence, run the following command:

(~/ros_ws/src/) $ catkin_create_package lab1 roscpp rospy std_msgs sensor_msgs

Note that this command is executed in the ~/ros_ws/src/ directory and not the ~/ros_ws/ directory.

This command generates a folder called lab1 in the src directory. Let's look at what's inside:

lab1
|-- CMakeLists.txt
|-- include
|   `-- lab1
|-- package.xml
`-- src

A bit oddly, catkin_create_package does not create the scripts directory, which is typically where the python code goes, but regardless, the majority of the structure is setup for us!

Investigating our package.xml

Every package has two things in common: package.xml, and CMakeLists.txt. Let's look at package.xml first. package.xml behaves as a sort of metadata file containing information about who is managing a package, what dependencies a package has, and the license for the package, amongst other things. It is important to keep this file up-to-date, as it kind of serves as a living project tracker.

Let's start by setting the description, maintainer, author, and license. You'll find that the dependencies have been set up for us.

Starting description:

<description>The lab1 package</description>

Let's update this description to something a bit more useful, like this:

<description>
    A lidar processing node that outputs the closest and furthest data points, along with the range of data points.
</description>

Similarly, we can update the maintainer, author, and license to the following:

<maintainer email="your.email@example.com">Your Name</maintainer>
<license>MIT</license> <!-- MIT is quite nice, but be sure to research licenses! -->
<author email="your.email@example.com">Your Name</author>

Remember to delete or comment out the template XML if you are replacing it!

Lab 2

Lab 3

Lab 4

Lab 5

Lab 6

Lab 7

Lab 8

Lab 9