This is the third tutorial in the Up and flying with the AR.Drone and ROS series.
In this tutorial we will:
In the previous tutorials we:
In the next tutorials, we will:
I also assume that you’ve completed either the keyboard or joystick tutorial, which detail how to setup the software, connect to the drone, etc, and provide a method of controlling the AR.Drone from your computer.
Warning!
In this tutorial, we will be flying the AR.Drone. Flying inside can be dangerous. When flying inside, you should always use the propeller-guard and fly in the largest possible area, making sure to stay away from walls and delicate objects. Even when hovering, the drone tends to drift, so always land the drone if not being flown.
I accept no responsibility for damages or injuries that result from flying the drone.
Please go back and update your installation according to the instructions listed there.
To update your installation for this tutorial, click the “Update Tutorials” button in the ARDroneUbuntu Virtual Machine
At the terminal, enter the following:
$ roscd ardrone_autonomy $ git pull $ roscd ardrone_tutorials $ git pull $ roscd $ rosmake ardrone_tutorials
$ roscd ardrone_tutorials/launch
$ geany keyboard_controller_with_tags.launch
Otherwise, if you intend to fly with the joystick:
$ geany joystick_controller.launch
<param name="enemy_colors" value="3" />
$ roslaunch ardrone_tutorials keyboard_controller_with_tags.launch
or the joystick_controller:
$ roslaunch ardrone_tutorials joystick_controller.launch
$ rostopic list
$ rostopic echo /ardrone/navdata
$ rxplot /ardrone/navdata/rotX,/ardrone/navdata/rotY
$ rxplot /cmd_vel/linear/y,/cmd_vel/linear/x
$ rosbag record -O ARDroneFlight.bag /ardrone/navdata /cmd_vel
$ roscore
Now open a new terminal window and type:
$ rxplot /cmd_vel/linear/y,/cmd_vel/linear/x
Now finally (ignoring the warning from the previous step), switch back to the terminal window that you used to run the rosbag command and type:
$ rosbag play ARDroneFlight.bag
Does the flight look familiar? It should! This is the flight you recorded in the last step! Use Ctrl-C to terminate the above processes.
In the last section, we investigated the AR.Drone’s feedback data by using ROS’s inbuilt plotting (rxplot) and logging (rosbag) commands. In this section, you will learn how we get this feedback data into your own application by writing a custom ROS Node. This node will be used to compare the commanded roll and pitch angles with the AR.Drone’s roll and pitch estimates, thus laying the foundations for the next tutorial, where we will use this feedback data to programmatically control the AR.Drone.
In ROS terminology, a program is known as a Node. To interact with other nodes (for example the AR.Drone driver), each node will have some subscribers, which listen for information messages on a specific topic (communications channel); and some publishers, which send messages to a specific topic.
These messages can be anything. An example that we saw in the previous section was the Navdata message, which is sent over the /ardrone/navdata topic and contains feedback data from the AR.Drone.
We will now investigate this message a little further. Open up a new terminal window and type
rosmsg show ardrone_autonomy/Navdata
You should be presented with the following output:
$ rosmsg show ardrone_autonomy/Navdata std_msgs/Header header uint32 seq time stamp string frame_id float32 batteryPercent uint32 state int32 magX int32 magY int32 magZ int32 pressure int32 temp float32 wind_speed float32 wind_angle float32 wind_comp_angle float32 rotX float32 rotY float32 rotZ int32 altd float32 vx float32 vy float32 vz float32 ax float32 ay float32 az uint32 tags_count uint32[] tags_type uint32[] tags_xc uint32[] tags_yc uint32[] tags_width uint32[] tags_height float32[] tags_orientation float32[] tags_distance float32 tm
This listing shows us the various members of the Navdata message, including their datatypes. Note that the Navdata message has a header, which includes the time the message is sent.
Open up a new terminal window and type:
$ roscd ardrone_tutorials $ geany src/minimum_viable_subscriber.py
Up will pop a geany window, ready for your to edit the new file. Enter the following into the new geany window, then save and close the file:
import roslib
import rospy
roslib.load_manifest('ardrone_tutorials')
from ardrone_autonomy.msg import Navdata
def ReceiveData(data):
print '[{0:.3f}] Pitch: {1:.3f}'.format(data.header.stamp.to_sec(), data.rotY)
rospy.init_node('minimum_viable_subscriber')
sub_Navdata = rospy.Subscriber('/ardrone/navdata', Navdata, ReceiveData)
while not rospy.is_shutdown():
pass
We will now use the previously recorded bag-file to test the node that we’ve just written.
$ roscore
Which begins the roscore process, which coordinates the interactions between nodes.
$ roscd ardrone_tutorials $ python src/minimum_viable_subscriber.py
This will launch the node that we’ve just programmed.
$ rosbag play ARDroneFlight.bag
[1353596639.491] Pitch: 4.375 [1353596639.511] Pitch: 4.128 [1353596639.531] Pitch: 3.750 [1353596639.552] Pitch: 3.304 [1353596639.571] Pitch: 3.063
If you made it through the previous section, congratulations, you have successfully written and tested a ROS node, which listens for Navdata measurements from the AR.Drone (or equivalently: a recorded flight) and outputs a time-stamped pitch measurement. But how does it all work?
Lets take a look at the source-code from the previous section, line by line:
import roslib
import rospy
With these two lines, we import the standard ROS packages, required for every ROS node.
roslib.load_manifest('ardrone_tutorials')
Once the required packages are imported, load the manifest file. This file details the dependencies of the current project (for example, that ardrone_tutorials is dependent on the AR.Drone driver, ardrone_autonomy), which through the above command are loaded into the path.
from ardrone_autonomy.msg import Navdata
Once the project dependencies have been loaded, we can import those which are needed by the current node. In the above case, we load the Navdata message definition from the ardrone_autonomy package.
def ReceiveData(data):
print '[{0:.3f}] Pitch: {1:.3f}'.format(data.header.stamp.to_sec(), data.rotY)
Here we define a callback function to handle the Navdata messages. This function prints a formatted string containing the message’s timestamp and the AR.Drone’s pitch angle. This callback function will be used later in the program.
rospy.init_node('minimum_viable_subscriber')
Before issuing any commands to the rospy package, we need to initialize a ROS node.
sub_Navdata = rospy.Subscriber('/ardrone/navdata', Navdata, ReceiveData)
We can now subscribe the node to various topics. Above we subscribe the node to the /ardrone/navdata topic, which carries Navdata messages. Received messages should then be handled by the previously-defined ReceiveData function.
while not rospy.is_shutdown():
pass
Because our program is relying on callbacks for data processing, we need to stop the program from terminating. The above defines a loop that runs until the node is shut down (for example with Ctrl-C). In most situations the above loop would perform some sort of processing, however in our example, we simply pass (do nothing).
In this section we will introduce the concept of publishing data to a topic (the opposite of subscribing). We will be using this more extensively in the next tutorial, where we will be programmatically controlling the AR.Drone by publishing messages to the /cmd_vel topic.
The goal of this section is to create a minimum_viable_publisher.py file such that Navdata messages are averaged over a second and the average pitch published to the (new) /ardrone/average_pitch topic. Modify the file as follows:
import roslib
import rospy
roslib.load_manifest('ardrone_tutorials')
from ardrone_autonomy.msg import Navdata
from std_msgs.msg import String
messages = []
def ReceiveData(data):
messages.append(data)
rospy.init_node('minimum_viable_publisher')
sub_Navdata = rospy.Subscriber('/ardrone/navdata', Navdata, ReceiveData)
pub_Average = rospy.Publisher('/ardrone/average_pitch', String)
r = rospy.Rate(1)
while not rospy.is_shutdown():
if len(messages)>0:
avg = sum([m.rotY for m in messages])/len(messages)
messages = []
avgmsg = String()
avgmsg.data = 'Average Pitch: {0:.3f}'.format(avg)
pub_Average.publish(avgmsg)
r.sleep()
You can run the program using the following series of commands:
$ roscore
$ roscd ardrone_tutorials $ python src/minimum_viable_publisher.py
$ rostopic echo /ardrone/average_pitch
$ rosbag play ARDroneFlight.bag
data: Average Pitch: 0.649 --- data: Average Pitch: 1.078 --- data: Average Pitch: 3.781 --- data: Average Pitch: 3.285 ---
Comparing minimum_viable_publisher.py to minimum_viable_subscriber.py from the last section, we can see many similarities:
from ardrone_autonomy.msg import Navdata
from std_msgs.msg import String
We additionally import the String message, required for our new topic.
messages = []
def ReceiveData(data):
messages.append(data)
We have updated the navdata callback function to save the messages in a queue, rather than process them straight away.
Note: because we’re accessing the messages list in both a callback function and the main loop, we should be using Python’s threading.Lock to control access to the list and prevent race conditions. See ardrone_tutorials/src/drone_video_display.py for an example.
pub_Average = rospy.Publisher('/ardrone/average_pitch', String)
We create a new publisher, responsible for publishing messages of the String type to the /ardrone/average_pitch topic. Note that we do not need to explicitly initialize this topic, it will be initialized automatically on first usage.
The biggest change is in the program’s main loop, which I will now cover in a little more detail:
r = rospy.Rate(1)
Firstly we define a Rate object. We use this to control the frequency of the main processing loop. In our case, we want to print a message every second, thus we want a frequency of $1 \text{Hz}$.
while not rospy.is_shutdown():
if len(messages)>0:
avg = sum([m.rotY for m in messages])/len(messages)
messages = []
avgmsg = String()
avgmsg.data = 'Average Pitch: {0:.3f}'.format(avg)
pub_Average.publish(avgmsg)
If messages have been received, we calculate the average pitch using a python list comprehension to extract the pitch (.rotY) from every Navdata message. We then empty the messages queue.
We then construct a new String message and set its data property, before publishing it to the topic we previously created.
r.sleep()
Finally, we tell the rate object to sleep. This command will calculate how long it needs to sleep, such that the desired loop frequency is achieved.
I hope you’ve enjoyed working through this tutorial and learning about data flow in the ROS environment. In the next tutorial, which I will release in a month’s time, we will use what we have learned today to programmatically control the AR.Drone, while at the same time learning about PID control.
If you have any questions about the tutorial, please leave me a comment below and I will get back to you as soon as possible!
You can see all the tutorials in here: Up and flying with the AR.Drone and ROS.
If you liked this article, you may also be interested in:
See all the latest robotics news on Robohub, or sign up for our weekly newsletter.