Skip to content

UAV Setup Guides (2020)

pryre edited this page Apr 11, 2021 · 3 revisions

Build a drone in a day! (Disclaimer: may take more than a day)

Basic configuration

As of time of writing, a basic configuration for an UAV with an on-board computer would use the following pieces of software to operate:

  • Operating System (Linux) - ideally some version of Ubuntu for best support with ROS
  • Robotic Operating System (ROS) - to allow modular and expandable software development
  • An flight controller / autopilot bridge - to allow communication with some form of a low-level controller
  • Navigation software - to command the flight controller on what to do
  • Data collection / processing - whatever else is required for the UAV to complete it's task

With this "typical" type of setup, there can be many choices for hardware, but it usually looks something like the following:

  • Airframe - any type of autopilot-supported configuration
  • Autopilot - PixHawk (or MavLink compatible, such as the Naze32 running the robin firmware)
  • Computer - a SoC-type device (capable of running Ubuntu)

First Steps

Understand ROS:

  1. What is ROS?
  2. Why use ROS?
  3. Useful Resources.
  4. ROS Wiki Tutorials.

Prepare Your Environment


Click here to show reference material regarding ROS and initial UAV setup
  1. Refer to Software Guides SoC/on-board computer materials.
  2. Installing ROS (Kinetic). Installing ROS (Melodic).
  3. Set up your workspace.
  4. Creating a ROS package.
  5. Understand how to compile your workspace.
  6. Learn how to use git in 5 steps:
    1. Sign up for a Github account.
    2. Create a new remote repository.
    3. Add your new package to the repository.
    4. Cloning a repository elsewhere and updating local copites.
    5. More on git commands.
  7. Install any additional software:
    1. Autopilot, GCS, & Navigation:
    2. Imagery Processing: See the Image Processing - ROS Interfaces page.
    3. Microcontrollers: See the ROS Wiki - rosserial page.

Working with ROS & Automating Your Environment


Click here to show reference material further information that may assist system development
  1. Understand ROS Messages, Topics, and Services:
    • A Message is like a mailed letter, with a specific set of internal variables. For example, the message std_msgs/Header contains the following data variables:
      • uint32 seq
      • time stamp
      • string frame_id
    • A Topic is like a shared mailbox for ROS Node to send messages to (Publish messages) and receive messages from (Subscribe to messages).
    • A Service is like a private mailbox where you can send out a letter (a Service Request), have something happen, then get a return letter with a result (a Service Response).
  2. Read up about ROS Names:
    • If a node's name is test_node, and it's namespace is /robot:
      • Publishing the global topic /foo/bar will create the topic: /foo/bar
      • Publishing the relative topic foo/bar will create the topic: /robot/foo/bar
      • Publishing the private topic ~foo/bar will create the topic: /robot/test_node/foo/bar
    • By private topics, it ensures that unrelated topics won't conflict. For example, two different nodes can have the topic name ~feedback, which will be resolved as the separate topics: /node_1/feedback and /node_2/feedback.
  3. Understand how to use roslaunch, mainly how to:
    • Create a launch file and run it.
    • Use use the remap tag to change topic names and link nodes together without editing node source code.
    • Use a single Launch file to start multiple ROS Nodes.
  4. Understand how ROS Parameters work:
  5. Read up on rosbag and recording data in ROS:
    • Record data with rosbag record /topic/example, with useful flags:
      • -a: record all topics (use care if there is raw images being captured)
      • -d: record for a specific duration
      • --node: Record all topics from a specific node
    • Replay data with rosbag play filename.bag:
      • -s: start the replay at a specific time in the recording
      • -d: only replay data for a specific duration of time
      • -l: loop the replay after it's finished (can be used with -s and -d to replay a recording segment on loop)
      • --clock: output clock data (use with the use_sim_time parameter to use the timestamps captured in the bag file)
  6. Visualization and Feedback of data in ROS:
    • rostopic - Allows you to list available topics, display message data, and perform various diagnostic tasks through the command line.
    • rqt - A plugin-based panel display for interfacing and with nodes and message data.
    • rviz - A utility to perform 3D visualization of geometric data.

The Software Guides

In-depth information on setting up the initial software for GCS and on-board computers can be found here.

The QUTAS Flight Stack

In-depth information on setting up the QUTAS Flight Stack can be found here.

Autopilot & Flight Control

In-depth information on setting up the Autopilot & Flight Contol can be found here.

Path-Planning & Navigation

In-depth information on setting up the Path-Planning & Navigation can be found here.

On-board Image Processing

In-depth information on setting up the On-Board Image Processing can be found here.

Extended ROS Tips & Tricks

Audible Alerts from the GCS

Many options exist for making audible noises, with a common option to use speech synthesizers like espeak to read out text to the operator.


Click here to show code examples

To install espeak (and the Python bindings):

sudo apt install espeak python-espeak

To test the synthesizer in the command line:

espeak "Hello World!"

Note: If you experience errors with the above command, try running it with:

PA_ALSA_PLUGHW=1 espeak "Hello World!"

To use the python bindings:

from espeak import espeak

espeak.synth("Hello World!")

# This loop is only needed to stop the script exiting
# This can be omitted if your program will not exit
# immediately after the synth
while espeak.is_playing():
	pass

Note: If you experience errors with the above command, try running it with (for example, if you saved the script as "speech.py"):

PA_ALSA_PLUGHW=1 python speech.py

ROS Integration example:

import rospy
from std_msgs.msg import String

from espeak import espeak

def callback(msg_in):
	text = "I heard %s" % msg_in.data
	espeak.synth(text)
	rospy.loginfo(text)

if __name__ == "__main__":
	rospy.init_node('synth')
	pub = rospy.Subscriber("chatter", String, callback)

	try:
		rospy.spin()
	except rospy.ROSInterrupyException:
		pass

Displaying Custom Markers in RVIZ

In rviz, the plugin Marker allows you to set a specific marker in the world display, and is useful for all sorts of feedback to the operator. The Marker plugin listens for a visualization_msgs/Marker message, and will display it accordingly.


Click here to show a code example of placing a marker in the "map" frame of reference

To publish a simple marker to rviz using Python:

#!/usr/bin/env python

import rospy
from visualization_msgs.msg import Marker

if __name__ == "__main__":
	rospy.init_node('node_name')
	pub = rospy.Publisher("tile_marker", Marker, queue_size=10, latch=True)

	# Set up the message header
	msg_out = Marker()
	msg_out.header.frame_id = "map"
	msg_out.header.stamp = rospy.Time.now()

	# Namespace allows you to manage
	# adding and modifying multiple markers
	msg_out.ns = "my_marker"
	# ID is the ID of this specific marker
	msg_out.id = 0
	# Type can be most primitive shapes
	# and some custom ones (see rviz guide
	# for more information)
	msg_out.type = Marker.CUBE
	# Action is to add / create a new marker
	msg_out.action = Marker.ADD
	# Lifetime set to Time(0) will make it
	# last forever
	msg_out.lifetime = rospy.Time(0)
	# Frame Locked will ensure our marker
	# moves around relative to the frame_id
	# if this is applicable
	msg_out.frame_locked = True

	# Place the marker at [1.0,1.0,0.0]
	# with no rotation
	msg_out.pose.position.x = 1.0
	msg_out.pose.position.y = 1.0
	msg_out.pose.position.z = 0.0
	msg_out.pose.orientation.w = 1.0
	msg_out.pose.orientation.x = 0.0
	msg_out.pose.orientation.y = 0.0
	msg_out.pose.orientation.z = 0.0

	# Make a square tile marker with
	# size 0.1x0.1m square and 0.02m high
	msg_out.scale.x = 0.1
	msg_out.scale.y = 0.1
	msg_out.scale.z = 0.02

	# Make the tile a nice opaque blue
	msg_out.color.r = 0.0
	msg_out.color.g = 0.2
	msg_out.color.b = 0.8
	msg_out.color.a = 1.0

	# Publish the marker
	pub.publish(msg_out)

	try:
		rospy.spin()
	except rospy.ROSInterrupyException:
		pass

To view the marker, simply run the python node, then in rviz, set the marker topic to /tile_marker.


Click here to show a code example of placing a marker using TF2

Instead of placing a marker at a point on the map, you could pick a frame of reference in TF2 and have rviz render the marker as though it was "attached" to that frame.

First, we will inject a "fake" target location into TF2 by adding a static transformation:

rosrun tf2_ros static_transform_publisher 1 1 0 0 0 0 map fake_target

The format for this command is "rosrun tf2_ros static_transform_publisher" to run the node, then "X Y Z Rx Ry Rz" where these are the location of the target and the roll, pitch, and yaw rotations, and finally "map" and "fake_target" are the frame IDs to put into TF2. So, this command would place a frame of reference called "fake_target" at the location (1,1,0) with 0 rotation relative to the "map" frame.

At this point you could check the location of the fake target in rviz using the "TF Plugin" to see how this marker is placed, and to try moving it around by changing the input variables. For a real mission, a real estimate of the target would be used instead of creating a fake target frame of reference (i.e. it would be added to TF by the image processing system).

The second step is to place a marker that is attached to this frame of reference. Run the following Python code as a ROS node:

import rospy
from visualization_msgs.msg import Marker

if __name__ == "__main__":
	rospy.init_node('node_name')
	pub = rospy.Publisher("tile_marker", Marker, queue_size=10, latch=True)

	# Set up the message header
	msg_out = Marker()
	msg_out.header.frame_id = "fake_target"
	msg_out.header.stamp = rospy.Time.now()
	msg_out.ns = "my_marker"
	msg_out.id = 0
	msg_out.type = Marker.CUBE
	msg_out.action = Marker.ADD
	msg_out.lifetime = rospy.Time(0)
	msg_out.frame_locked = True

	# Place the at the center of the frame of reference with no rotation
	msg_out.pose.position.x = 0.0
	msg_out.pose.position.y = 0.0
	msg_out.pose.position.z = 0.0
	msg_out.pose.orientation.w = 1.0
	msg_out.pose.orientation.x = 0.0
	msg_out.pose.orientation.y = 0.0
	msg_out.pose.orientation.z = 0.0
	
	msg_out.scale.x = 0.1
	msg_out.scale.y = 0.1
	msg_out.scale.z = 0.02
	msg_out.color.r = 0.0
	msg_out.color.g = 0.2
	msg_out.color.b = 0.8
	msg_out.color.a = 1.0

	# Publish the marker
	pub.publish(msg_out)

	try:
		rospy.spin()
	except rospy.ROSInterrupyException:
		pass

Viewing the marker in rviz is then the same as the previous example. Changing the variables in the first command (i.e. moving the frame of reference) will cause the marker to also move without needing to restart the node.


Test Node for Payload Actuation

Payload actuation can be achieved in a number of ways, although it is typically convenient to utilise existing on-board computational devices if possible (e.g. the on-board computer or the flight controller) as this reduces the amount of additional components and unnecessary weight.


Click here to show code examples for integration with the flight controller

Interfacing with the flight controller (further described here) can be achieved by sending a SET_ACTUATOR_CONTROL_TARGET message from the GCS. This can be achieved using ROS and MAVROS using the actuator_control plugin.

In this case, the flight controller will look to the ACTUATOR_OB_* parameters to determine which actuators should be changed. With this parameters configured, and MAVROS running, the following example node can be run to toggle all inputs on and off using Python:

#!/usr/bin/env python
import rospy
from mavros_msgs.msg import ActuatorControl

# Group mix values taken from the Robin interfacing document
GROUP_MIX_OB_PWM = 4
GROUP_MIX_OB_DIGITAL = 5

if __name__ == '__main__':
	# Setup the ROS backend for this node
	rospy.init_node('actuator_controller', anonymous=True)
	# Setup the publisher that will send data to mavros
	pub = rospy.Publisher('/mavros/actuator_control', ActuatorControl, queue_size=10)

	# Configure our program (will turn actuators on/off at 1Hz)
	rate = rospy.Rate(1)
	is_low = True

	# Prepare some "set all" actuator lists.
	#In practice, only specific ports should be set on or off
	all_actuators_off = [-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]
	all_actuators_on = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]

	try:
		while not rospy.is_shutdown():
			# Prepare message to send to MAVROS
			# http://docs.ros.org/api/mavros_msgs/html/msg/ActuatorControl.html
			msg_out = ActuatorControl()
			msg_out.header.stamp = rospy.Time.now()
			msg_out.group_mix = GROUP_MIX_OB_DIGITAL

			# Figure out if we are turning actuators on or off
			is_low = not is_low
			if is_low:
				msg_out.controls = all_actuators_off
				rospy.loginfo("Set servos low")
			else:
				msg_out.controls = all_actuators_on
				rospy.loginfo("Set servos high")

			# Publish the complete message to MAVROS
			pub.publish(msg_out)

			# Sleep for a bit and repeat while loop
			rate.sleep()
	except rospy.ROSInterruptException:
		pass


Click here to show code examples for integration with a Raspberry Pi

Interfacing with a Raspberry Pi can be achieved by utilising the GPIO pins directly using a library like RPi.GPIO. Once you have a working actuator, extending the functions of the GPIO so that it can be interfaced with other subsystems is then the more difficult part. However, ROS can be used to simplify this process.


Note: ROS does not work when run as a super user (e.g. using "sudo"). Running a ROS script with "sudo" may cause damage to your software setup! If your system is configured correctly, you should not need to use "sudo" to access the GPIO and run the following script.


Interfacing with other subsystems can be achieved using a simple ROS node:

#!/usr/bin/env python
import rospy
import RPi.GPIO as GPIO
from std_msgs.msg import Bool

sub_a = None
chan_list = [11,12] # Set the channels you want to use (see RPi.GPIO docs!)

def callback_a(msg_in):
	# A bool message contains one field called "data" which can be true or false
	# http://docs.ros.org/melodic/api/std_msgs/html/msg/Bool.html
	# XXX: The following "GPIO.output" should be replaced to meet the needs of your actuators! 
	if msg_in.data:
		rospy.loginfo("Setting output high!")
		GPIO.output(chan_list, GPIO.HIGH)
	else:
		rospy.loginfo("Setting output low!")
		GPIO.output(chan_list, GPIO.LOW)

def shutdown():
	# Clean up our ROS subscriber if they were set, avoids error messages in logs
	if sub is not None:
		sub_a.unregister()

	# XXX: Could perform some failsafe actions here!

	# Close down our GPIO
	GPIO.cleanup()

if __name__ == '__main__':
	# Setup the ROS backend for this node
	rospy.init_node('actuator_controller', anonymous=True)

	# Setup the GPIO
        GPIO.setup(chan_list, GPIO.OUT)

	# Setup the publisher for a single actuator (use additional subscribers for extra actuators)
	sub_a = rospy.Subscriber('/actuator_control/actuator_a', Bool, callback_a)

	# Make sure we clean up all our code before exiting
	rospy.on_shutdown(shutdown)

	# Loop forever
	rospy.spin()

Running that script will allow you to then send commands to the actuator:

rostopic pub /actuator_control/actuator_a std_msgs/Bool '{data: True}'

Clone this wiki locally