Modified 2020-09-16 by Garrett Warren
Modified 2020-09-16 by Garrett Warren
When writing software for a robot, it is easier to break down the software into smaller, more manageable chunks than to write one big program that does everything. While we could have a really long Python program that interfaced with each sensor, used the sensor data to estimate the state of the drone such as it’s height an how fast it’s moving, and then use this information to change the speeds of the motors, this has several disadvantages. First, if there is a bug in the code (a mistake in the software), it would be very hard to find in a large file where all of the code is together. Second, if someone wanted to improve just one part of the code, such as a better estimate of the IR sensor, they would need to read through a bunch of code that isn’t relevant to make their change; additionally, it would be hard to undo those changes if they didn’t work well. Instead, software engineers prefer to write modular code, where each program only performs one small job that contributes to a larger job. For example, one program can handle talking to the IR sensor, another program can interface with the camera, another can combine the data, and another can use the combined data to control the motors. Using this software design, it is easy to design and test smaller pieces of code before they go into the bigger picture. If the IR program isn’t working right, it won’t stop the camera from working, and it will be easy to find the problem.
When we break down the software into smaller programs, we then face the problem of making the programs talk to each other. The data collected in one program might be needed by another program. Let’s consider an example of this situation on the drone.
Modified 2020-09-16 by Garrett Warren
In the last lesson, you wrote a Python program that could read the ADC, and convert the value to a distance measurement. These steps were essential to obtain the height of the drone in meters. The problem we face now is sharing that value with other programs running on the drone. When the drone is flying, we want the controller to know how high the drone is, so that it can speed up or slow down the motors accordingly. We will go into details on controllers in a later section. For now, we will set up a simpler problem to solve that will demonstrate the process of using a sensor to control an actuator.
Goal: The goal is to adjust the brightness of the LED on your drone based on the distance measured by the IR sensor. If the distance is small, the LED should be bright. As the distance measurement increases, the LED should dim. This will simulate speeding up and slowing down the motors based on how high the drone is. The information in this lesson will provide the background needed to accomplish this goal in the following two lessons.
Modified 2020-09-16 by Garrett Warren
Take a look at this diagram that illustrates the software that runs on the drone. Refer back to this diagram as you read through the following points.
Modified 2020-09-16 by Garrett Warren
Each rectangle with sharp corners in the diagram represents a piece of physical hardware. The IR sensor and the camera are two sensors on the drone.
Modified 2020-09-16 by Garrett Warren
Each rectangle with rounded corners represents an individual Python program. Notice that there are specific programs that read the data from each sensor. Inside of that program, the data is read, converted into a useful measurement, and then shared with other programs. If student A’s IR sensor code worked better than student B’s, then student A could share just their IR code with student B, and student B would be able to use just this code with the rest of the code being their own. This is one advantage of modular software design: small parts of code can be interchanged easily.
Modified 2020-09-16 by Garrett Warren
In order to share data between programs, such as the IR sensor reading, we use middleware. Middleware runs “between” the hardware and software on the drone. In the diagram, the middleware is represented by the arrows that connect the software.
ROS
The middleware used on the drone is called the Robot Operating System, or ROS (pronounced like “moss” but with an “r”). This is industry standard software and is used in many profession robotics companies. You can watch a few cool examples of robots that use ROS here.
ROS on the drone
For the purposes of the drone, ROS is a communication tool that allows one Python program to talk to another. For example, ROS will allow the ir.py
program to share the distance measurement from the IR sensor with another program that can adjust how fast the motors are spinning to hold the drone at a specific height.
Modified 2020-09-16 by Garrett Warren
The communication system in ROS comes with its own vocabulary. The following terms are essential for understanding ROS.
Modified 2020-09-16 by Garrett Warren
A ROS node is simply a program that contains publishers or subscribers. On the drone, a node is just one of the Python programs that is used to make the drone fly.
For more info: ROS wiki link
Modified 2020-09-16 by Garrett Warren
A publisher is a ROS entity that sends messages to a topic. In python, a publisher is just an object with a “publish()” method that sends the message.
For more info: ROS wiki link
Modified 2020-09-16 by Garrett Warren
A message is a structured format for data. Messages standardize how the publisher is supposed to format the data, so that the receiver knows how to read it. For example, there is a Boolean message type where the data can only be True
or False
. There is also a String message type, where the data can be any text such as this is my message!
. We can also make custom message types that combine two or more existing message types. For example, we could create a message type that contains two Booleans and one String.
bool
bool
string
We would want to label the Booleans and the String so that the sender and receiver would know which data was which. If the first Boolean determined whether I wanted ice cream, and the second Boolean determined whether I wanted to pay for it, I would not want those mixed up! Here is what a custom message type could look like:
bool do_i_want_icecream
bool do_i_want_to_pay_for_it
string flavor
The first column shows the type of the data, also known as the fieldtype. The second column shows the label for the data, also known as the fieldname. Together, the fieldtype and fieldname make one field in a ROS message.
The way that we would use this message in Python is to create a blank message, and then to edit the fields. For example:
my_icecream_order = OrderMessage() # create a blank message
OrderMessage.do_i_want_icecream = True
OrderMessage.do_i_want_to_pay_for_it = False
OrderMessage.flavor = "Strawberry"
Now I can publish my_icecream_order and my ice cream robot will go and buy me strawberry ice cream!
For more info: ROS wiki link
Modified 2020-09-16 by Garrett Warren
A ROS topic is a named bus that messages get published to and subscribed to. In Python, a ROS topic is just a String that identifies where a publisher should send messages, and where a subscriber should look to receive those messages. Only one message type can be sent along a topic, and this is set when the topic is created by a publisher. For example, we might want to create a topic called order
that has the message type OrderMessage
. We can create a publisher that publishes our ice cream orders to the order
topic.
For more info: ROS wiki link
Modified 2020-09-16 by Garrett Warren
A ROS subscriber “subscribes” to a topic and listens for messages. When the subscriber is created, it is told what topic to listen to. As soon as a subscriber receives a message, it passes along the message to a callback function.
For more info: ROS wiki link
Modified 2020-09-16 by Garrett Warren
A callback is a function that takes as input a ROS message, and then performs some action based on that message. In some cases, the action might just be storing the message data. In other cases, the action might involve the physical world. In the ice cream example, the callback function would hopefully make our ice cream robot go and buy us ice cream. In the case of the drone, the flight controller node is subscribed to roll, pitch, yaw, and throttle commands on the /pidrone/fly_commands
topic. When new commands are published to this topic, the callback in the flight controller subscriber sends these commands to change the speeds of the drone motors.
Modified 2020-09-16 by Garrett Warren
In order for ROS nodes to communicate with messages over topics, each node must register with the ROS Master. The ROS master facilitates communication between nodes by keeping track of all of the nodes, topics, publishers, and subscribers. This information is used to help nodes find each other so that they can communicate.
For more info: ROS wiki link
Modified 2020-09-16 by Garrett Warren
ROS allows a node to publish a message to a topic and then another node can subscribe to the topic to receive messages. Once a message is received, the subscriber passes the message to a callback function that does something with the data. This communication is facilitated by a ROS Master, which allows nodes to find each other to communicate.
Modified 2020-09-16 by Garrett Warren
There are a number of useful ROS commands that you can use in the terminal.
For details on all of the ROS commands: ROS wiki
Modified 2020-09-16 by Garrett Warren
roscd
is the ROS version of cd
; it lets you navigate directly to ROS packages. For example, if you are in the home directory on the drone, instead of typing: cd ~/ws/src/pidrone_pkg
, you can just type roscd pidrone_pkg
.
Modified 2020-09-16 by Garrett Warren
roscore
is the command used to start a ROS master. No arguments are needed to run this command
Modified 2020-09-16 by Garrett Warren
rostopic list
will list all of the ROS topics that are currently being published and subscribed to
Modified 2020-09-16 by Garrett Warren
rostopic info (topic_name)
will list the publishers, subscribers, and message type of whatever topic you replace (topic_name)
with
Modified 2020-09-16 by Garrett Warren
rosbag allows you to record all of the messages sent to topics. you can specify just one topic to record using rosbag record (topic_name)
, or you can record all of the topics using rosbag record -a
. Recording data is useful for testing and debugging. You can also use rosbag play (file.bag)
to play back the recorded data.
For more info: ROS wiki
Modified 2020-09-16 by Garrett Warren
Environment variables modify key attributes of a program. For our applications in ROS, the most important environment variable is ROS_MASTER_URI
. To check the value of this environment variable, type echo $ROS_MASTER_URI` and press enter in the terminal, and the value will be printed out. the `$
symbol is used to signal environment variables. To change an environment variable, you can either type export (VARIABLE)=(value)
with the correct variable and values into the terminal. These changes will only be temporary and you’ll need to export the values every time. Alternatively, you can create a shell script to export the values for you, as we have done in the pidrone_pkg. Take a look at the setup_for_managed_mode.sh
and setup_for_master_mode.sh
scripts.
Modified 2020-09-16 by Garrett Warren
The ROS_MASTER_URI
environment variable sets the IP address and port of the ROS master. All of the ROS nodes will look to this address to register themselves. If there are two computers both connected on the same internet network and both running ROS, one computer can start up a ROS Master using roscore
, and the other computer can set it’s ROS_MASTER_URI
to be the same as the first computer’s. Then, the ROS nodes on the two separate computers can talk to each other! This is a very useful feature because it allows the computation load to be distributed across devices. For example, some of the programs that are run on the drone can instead be run offboard on another computer running ROS. This is necessary for some of the features on the drone. However, nearly all of the DuckieDrone’s programs can be run onboard.
Modified 2020-09-16 by Garrett Warren
There is one more essential ROS element that is worth mentioning if you are interested in creating your own workspace. In programming, a workspace is used to keep track of all of the programming files, dependencies, and executable code. For ROS, the workspace setup is called catkin. The main thing to note is that ROS packages (a set of programs that run together), go in the src
directory. This is why the path to the pidrone_pkg
is ~/ws/src/pidrone_pkg
. For instructions on creating your own workspace, refer to the ROS wiki.