4. Creating your own Starling Project

This tutorial takes you through using the Starling templates repository to generate your own custom Starling application.

4.1 Prerequisites

In order to complete this tutorial, you will need to install the following. You may have already installed these during Getting Started.

The template generation uses the cookiecutter tool for generating custom projects from a template.

To install it, run the following:

python3 -m pip install --user cookiecutter
# or
easy_install --user cookiecutter

See Cookiecutter Installation for details for different platforms.

This will give you access to the cookiecutter command line interface, which is used in this 4.3 section.

We also recommend you sign up for Docker Hub and Github as both are necessary if you wish to fly your controller in the real world.

4.2 Project Structure Planning

Before diving in to creating your project, you first need to decide on the structure of the project. The structure is determined by the application, and the functionality is split between the central server and each vehicle. Let's review our task for this tutorial:

In this scene, a number of drones take off and automatically fly to starting points equidistant around a circle of a given radius. They then start circling around the edge of the circle attempting to stay equidistant to their neighbours. It is determined that the vehicles have not been well tuned and can end up lagging, so a centralised server monitors all the vehicles and notifies them if they are lagging behind.

A Starling Project is comprised of one or more ROS Nodes which each encompass one piece of functionality. In this project we can identify the following requirements:

  1. A node onboard the vehicle which can arm, takeoff, land and fly it in a circle of radius r from a given start location in a safe manner.
  2. A node offboard on the central server which receives vehicle locations, finds the ideal locations and then sends that information back to the vehicles.

We can see that we may also need to provide a set of custom messages for the communication of specific information between server and vehicle.

Therefore, we will need this Starling Project to contain the source code for an onboard node, an offboard node and a set of custom messages.

An important task is then to name these beforehand as we need a way to refer to them in the next step. We've used some example names below, but please try to give yours better names :)

  1. Offboard node: template_python_node
  2. Onboard node: template_cpp_node
  3. Custom msgs: template_msgs

4.3 Generating the base Starling Project

The first step is to build your own Starling project. The following will start the process of generating the base Starling project. In your workspace, run the following command.

cookiecutter https://github.com/StarlingUAS/starling_controller_templates.git --directory starling_template

You will then be prompted to fill in some details. The value in the angle brackets indicate the default value if you choose not to enter anything. Press Enter to go on to the next one. The inputs include the following.

Name Description
Full Name Your name used for documentaiton
Email Your email address used for documentation
Short Description A short description of your project added to the documentation and project files
Project Name The name you have given to this Starling Project, make sure that you are happy with this as changing the name after fact is a bit of a pain.
Github Username (Optional) Your Github username for filling in the README and metadata
Docker Username (Optional) Your Docker Hub username which is used for naming the Starling image. Will be used to upload to if you wish to push it online.
Docker Image Name The name of the Starling image that this repository produces
Docker Image Name Full An autogenerated name based on your username and image name, you can leave as the default unless you really want to change it. By default it is <Docker Username>/<Docker Image Name>
Onboard ROS2 Package Name The name of the onboard controller which this containers Dockerfile will run in onboard mode.
Offboard ROS2 Package Name The name of the offboard controller which this containers Dockerfile will run in offboard mode.

Note: The last two entries should correspond to the node names you came up with in the planning phase.

Note: The last two entries are for automatically populating the run.sh script. The run.sh script is the default script your project's Docker container runs on startup. You can leave these two as defaults and edit the run.sh script later.

Once complete, the project will be generated into a directory named as project_name. For example, the default project with name starling_controller produces a project with the following structure:

starling_controller
|-- buildtools
    |-- docker-bake.hcl
|-- deployment
    |-- docker-compose.yml
    |-- kubernetes.yaml
|-- starling_controller
    |-- run.sh
|-- Dockerfile
|-- LICENSE
|-- Makefile
|-- README.md

We have the following folders and files.

  • starling_controller will be populated by user-created ros packages. Anything in this folder is directly copied to the Dockerfile and built.
  • deployment contains a sample docker-compose.yml file which runs a default simulation stack, and a sample Kubernetes file for deployment, both will need to be edited to run properly.
  • buildtools contains the specification that Docker uses to build the container. It contains the naming for the Docker image.
  • Dockerfile specifies the build steps for this project. It already specifies the installation of a number of dependencies, including the libInterpolate interpolation library.

Once generated, the Makefile can be used to build and run commands:

cd <Your Application Name> # Go into the your new Starling application directory
make # Will build the project
make run # Will build and run the container
make run_bash # Will build and run the container, putting you in a bash shell inside it.
make help # Shows the help screen

This should successfuly build your project container which you can try and run or inspect. Currently it has no functionality so nothing can happen. Have a look inside the container using make run_bash.

Note On Windows you can either use WSL to run the make commands. Alternatively, check out this link for other solutions.

4.4 Adding Nodes to your project

The generated project has no functionality right now. This repository contains other templates which will generate rosnodes for you. In particular

  • cpp_ros2_node_onboard_template: Generates a ROS2 node, designed for running onboard the vehicle written in CPP.
  • python_ros2_node_offboard_template: Generates a ROS2 node, designed for running offboard (central server) written in Python
  • ros2_msgs_template: Generates a ROS2 msgs package which can be used by any ROS2 package within this container.

These nodes can be added to your project using the following cookiecutter commands. Note that the packages should be generated in the starling_project_name directory of the base Starling project. Each of these commands are single line commands.

Navigate into <your project name>/<your project name> e.g. starling_controller/starling_controller (by default this should only contain the file run.sh and run the following)

# CPP Onboard
cookiecutter https://github.com/StarlingUAS/starling_controller_templates.git --directory cpp_ros2_node_onboard_template 
# Python Offboard
cookiecutter https://github.com/StarlingUAS/starling_controller_templates.git --directory python_ros2_node_offboard_template
# Messages
cookiecutter https://github.com/StarlingUAS/starling_controller_templates.git --directory ros2_msgs_template 

Note the --directory argument points cookiecutter to the correct template; the -o argument specifies the output directory, which in our case should be inside the created Starling project.

Similar to the base project generation, these commands will ask you a number of questions during the generation. Most importantly, it will ask what the package_name is which will become the name of that particular node package.

Name Default Description
full_name starling_user Your name used for documentation
email starling.user@starling.co.uk Your email address used for documentation
year 2022 The year of creation for documentation
package_name template_node The name of this ROS2 Node, make sure this is correct and note it down. It should match the ones given in the initial Starling setup
short_description ROS2 node template A short description of the functionality of this node for documentation and project files
custom_ros2_msgs_name template_msgs Important! The name of the custom msgs package name you added as part of this Starling project. The name must match exactly otherwise the default functionality will fail!

Note convention for msg packages is to have a package name of format <mymessages>_msgs, e.g. circle_experiment_msgs.

Note For those familiar with ROS1, it is ROS2 convention to keep messages in a separate package to the ros nodes themselves.

This should give you a file tree that looks something like the following (of course with your package names instead)

starling_controller
|-- buildtools
    |-- docker-bake.hcl
|-- deployment
    |-- docker-compose.yml
    |-- kubernetes.yaml
|-- starling_controller
    |-- template_onboard_controller
        |-- ...
    |-- template_offboard_controller
        |-- ...
    |-- template_msgs
        |-- ...
    |-- run.sh
|-- Dockerfile
|-- LICENSE
|-- Makefile
|-- README.md

The nodes can be run standalone for your own projects, one at a time, or whenever you need a new node within your project.

Once these packages have been placed within the correct directory inside the Starling project, you can simply run make to check that they successfully build.

4.5 What is in the templates

The set of node templates above should create you a project which runs the example scenario specifically developed for the purpose of this tutorial. This example has been designed to show the development of an onboard and an offboard container, as well as demonstrate communication between the two containers. The scenario is as follows:

  1. We have \(n\) drones which we would like to fly equidistant around a circle of fixed radius at a initial velocity.
  2. A central server will send each drone an id \(i<n\) to determine its start location around the circle.
  3. Once received the drones will start flying around the circle and send its current position to the server.
  4. The server collates the drones information to determine if any of the drones are lagging or ahead of where they should be. This ideal position is sent back to each drone.
  5. The drone adjusts its velocity to try and match with the ideal.

We can now quickly run through what is in each of the ros node templates.

Note: More detail about the actual functionality within these nodes will be in this tutorial section

4.5.1 cpp_ros2_node_onboard_template

|-- CMakeLists.txt
|-- include
|   |-- controller.hpp
|   |-- main.hpp
|   `-- state.hpp
|-- launch
|   `-- template_cpp_node.launch.xml
|-- package.xml
`-- src
    |-- controller.cpp
    `-- main.cpp

This node runs onboard the vehicle and interfaces with MAVROS. It contains a state machine with functionality to arm, takeoff, land, loiter and takes care of safety functionality.

  • CMakeLists.txt: contains the instructions to build this rosnode. This includes specifying dependencies and extra libraries (e.g. messages such as geometry_msgs or external dependencies). It also specifies the name of the binary containing all of your functionality. By default this is controller.
  • include and src: In CPP, your code files are split into header files (specifying object definitions) and source files (specifying object functionality), these are stored here
  • launch: contains a ROS launch file. We use XML notation to describe how your rosnode gets launched, including extra parameters or other changes you want to make at runtime instead of buildtime. This is what gets run to run your rosnode
  • package.xml: ROS2 Metadata file specifying ros2 dependencies of your project.

As a brief overview of the code files:

  • main.hpp and main.cpp: Contains the program entrypoint function and the core of the ros node. It contains all of the core functionality to fly a vehicle as well as the state machine. It includes the user controller specified in controller.hpp to be expected to run during the execution phase of the state machine.
  • controller.hpp and controller.cpp: For the majority of simple applications, a user should only need to provide their own version of these files. The controller contains an initialisation and loop function which a user can fill in.
  • state.hpp: A header only file containing the states of the state machine.

4.5.2 python_ros2_node_offboard_template

|-- package.xml
|-- resource
|   `-- template_python_node
|-- setup.cfg
|-- setup.py
|-- template_python_node
|   |-- __init__.py
|   `-- main.py
`-- test
    |-- test_copyright.py
    |-- test_flake8.py
    `-- test_pep257.py

This node runs offboard on the central server. It is designed to run on its own with no external dependency on anything outside of the application.

  • package.xml: ROS2 Metadata file specifying ros2 dependencies of your project.
  • resource: A Python ros package special folder, do not touch
  • setup.cfg: Configuration file specifying where key resources are
  • setup.py: The Python equivalent of CMakeLists.txt and contains the instructions to build this rosnode. It specifies which resources are copied over and available to the rosnode at runtime. Also specifies the name of the binary, and which function it is intended to run. By default this is controller
  • <your rosnode name> e.g. template_python_node: The source directory for your python files.
  • test: A number of testing utilities which can be run with pytest. Currently not used.

A brief overview of code files:

  • main.py: Contains a ros node which uses a timer to repeat poll the current state of vehicles on the network at given intervals. It then performs the calculation of ideal vehicle location and sends that to the vehicles.

4.5.3 ros2_msgs_template

|-- CMakeLists.txt
|-- msg
|   |-- NotifyVehicles.msg
|   `-- TargetAngle.msg
`-- package.xml

This node is purely for specifying and building the custom ros messages in our application. Any application which uses these messages need a compiled version of this node.

  • CMakeLists.txt: contains the instructions to build the messages. Any extra messages or services need to be added to the CMakeLists.
  • msg: A list of specified custom messages.
  • package.xml: ROS2 Metadata file specifying ros2 dependencies of your project.

4.5.4 Dockerfile

As mentioned in the previous tutorial, a Dockerfile is used as a recipe to build your controller Docker container.

The Dockerfile in this template contains the command line instructions to build your controller.

By default, it will install a number of useful libraries for compatibility.

If you need any new libraries, you will have to add their installation here!

It then essentially copies in all of the rosnodes specified within the project name directory and runs them through the standard ROS2 build tool named colcon.

It also copies over the run.sh file which the Dockerfile will run on container startup. Therefore the run.sh should have the instructions for running your applications.

Thankfully, you do not need to run docker build automatically as we have set up a special build system which is wrapped up inside the Makefile.

4.6 Running your new project

With your project now constructed, you can now re-run your container with the same make commands as earlier.

cd <Your Application Name> # Go into the your new Starling application directory
make run # Will build and run the container

This will build a container called <Docker Username>/<Docker Image Name> with tag latest e.g. myname/starling_template:latest

This will start the onboard controller by default, but it will start complaining that it hasn't received any state or position messages for initialisation. This is normal for now!

If you want to start the offboard controller, you can add the extra option ENV="-e OFFBOARD=true" to the make command like so make run ENV="-e OFFBOARD=true". It will then start trying to identify the number of vehicles on the network, but of course it cannot find any!

You can have a look inside both containers using make run_bash.

4.7 Initialising Git

Optionally, at this point you can set up version control on your project in order to save your progress. To initialise git and create your first commit, go to the root of your project and run:

git init
git add -A
git commit -m "Initial Commit"

If you want to push this code onto github, you can follow this tutorial. In short, create an empty Github repository of the same name in your Github account, and change the remote locally:

git remote add origin <remote repository URL>
git remote -v
git push origin master

4.8 Next Steps

Congratulations, you now have your own Starling application! It doesn't have any functionality yet but before we get to adding some, it's important to understand how you run the simulation for you to test your controller against!