-
Notifications
You must be signed in to change notification settings - Fork 7
UAV Setup Guides (2020)
Build a drone in a day! (Disclaimer: may take more than a day)
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)
Click here to show reference material regarding ROS and initial UAV setup
- Refer to Software Guides SoC/on-board computer materials.
- Installing ROS (Kinetic). Installing ROS (Melodic).
- Set up your workspace.
- Creating a ROS package.
- Understand how to compile your workspace.
- Learn how to use
git
in 5 steps: - Install any additional software:
- Autopilot, GCS, & Navigation:
-
mavros
andmavros_extras
: Follow the steps listed in the QUTAS Flight Environment guide. -
contrail
: Follow the Installation Instructions. -
egh450_navigation_interface
: Follow the Installation Instructions. -
rqt_mavros_gui
: Follow the Installation Instructions. -
rqt_robin_gcs
: Follow the Installation Instructions.
-
- Imagery Processing: See the Image Processing - ROS Interfaces page.
- Microcontrollers: See the ROS Wiki - rosserial page.
- Autopilot, GCS, & Navigation:
Click here to show reference material further information that may assist system development
- 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).
- 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:
- 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
- Publishing the global topic
- 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
.
- If a node's name is
- Understand how to use roslaunch, mainly how to:
- Create a launch file and run it.
- Example in the uavusr_emulator package example).
- Run it with:
roslaunch uavusr_emulator emulator.launch
.
- 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.
- Create a launch file and run it.
- Understand how ROS Parameters work:
- Parameter client examples for Python and C++.
- Use Parameters in a ROS Launch file.
- Further reading on Dynamic Reconfigure.
- 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 theuse_sim_time
parameter to use the timestamps captured in the bag file)
-
- Record data with
- Visualization and Feedback of data in ROS:
In-depth information on setting up the initial software for GCS and on-board computers can be found here.
In-depth information on setting up the QUTAS Flight Stack can be found here.
In-depth information on setting up the Autopilot & Flight Contol can be found here.
In-depth information on setting up the Path-Planning & Navigation can be found here.
In-depth information on setting up the On-Board Image Processing can be found here.
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
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.
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}'
- Home
- Common Terminology
- UAV Setup Guides (2024)
- UAV Setup Guides (2022)
- UAV Setup Guides (2021)
- UAV Setup Guides (--2020)
- General Computing
- On-Board Computers
- ROS
- Sensors & Estimation
- Flight Controllers & Autopilots
- Control in ROS
- Navigation & Localization
- Communications
- Airframes
- Power Systems
- Propulsion
- Imagery & Cameras
- Image Processing
- Web Interfaces
- Simulation