How to Use Functions and Pointers in C++

In this tutorial, we will explore functions and pointers in C++.

Prerequisites

Using Mathematical Functions

Let’s explore how to use mathematical functions in C++ for robotics. Mathematical functions are essential for performing various calculations in robotic applications.

Open a terminal window, and type this:

cd ~/Documents/cpp_tutorial

code .

Let’s create a new C++ file and name it robot_math_functions.cpp.

Type the following code into the editor:

#include <iostream>
#include <cmath>

using namespace std;

int main() {
    double angle = 45.0;
    double radians = angle * M_PI / 180.0;

    double sine = sin(radians);
    double cosine = cos(radians);

    cout << "Sine: " << sine << endl;
    cout << "Cosine: " << cosine << endl;

    return 0;
}

In this example, we demonstrate how to use mathematical functions to calculate the sine and cosine of an angle.

First, we include the <cmath> header to use the mathematical functions. Then, we declare a double variable angle and assign it the value 45.0, representing an angle in degrees.

To convert the angle from degrees to radians, we multiply it by M_PI (which represents the mathematical constant pi) and divide by 180.0. We store the result in the radians variable.

To calculate the sine and cosine of the angle, we use the sin() and cos() functions, respectively. These functions expect the angle to be in radians. 

We pass the radians variable as an argument to these functions and store the results in the sine and cosine variables.

Finally, we print the values of sine and cosine using cout.

Run the code.

1-robot-math-functions

You should see the values of sine and cosine printed in the terminal.

In robotic projects, mathematical functions are commonly used for tasks such as calculating robot positions, orientations, sensor data processing, control algorithms, and motion planning.

Implementing Functions

Let’s explore how to implement functions in C++ for robotics. Functions are essential for organizing and reusing code in robotic applications.

Let’s create a new C++ file and name it robot_functions.cpp.

Type the following code into the editor:

#include <iostream>
#include <cmath>  // Added header for sqrt()
using namespace std;

// Function declaration 
double calculate_distance(double x1, double y1, double x2, double y2);

int main() {
    double distance = calculate_distance(0, 0, 3, 4);
    cout << "Distance: " << distance << endl;
    return 0;
}

// Function definition
double calculate_distance(double x1, double y1, double x2, double y2) {
    double dx = x2 - x1;
    double dy = y2 - y1;
    double distance = sqrt(dx * dx + dy * dy);
    return distance;
}

In this example, we demonstrate how to implement a function to calculate the distance between two points.

First, we declare the calculate_distance function before the main function. The function takes four parameters: x1, y1, x2, and y2, representing the coordinates of two points. It returns a double value, which is the calculated distance.

In the main function, we call the calculate_distance function with the coordinates (0, 0) and (3, 4). The returned distance is stored in the distance variable and then printed using cout.

After the main function, we provide the function definition for calculate_distance. Inside the function, we calculate the differences in x and y coordinates (dx and dy). 

Then, we use the distance formula (Pythagorean theorem) to calculate the distance between the points. 

Finally, we return the calculated distance.

Run the code.

2-robot-functions

You should see the calculated distance printed in the terminal.

In robotic projects, you can use functions for various purposes, such as calculating sensor data, controlling robot movements, implementing algorithms, and more.

Handling Pointers

Let’s explore how to handle pointers in C++ for robotics. Pointers are variables that store memory addresses and allow you to manipulate data directly in memory.

Let’s create a new C++ file and name it robot_pointers.cpp.

Type the following code into the editor:

#include <iostream>

using namespace std;

int main() {
    int robot_id = 42;
    int* ptr = &robot_id;

    cout << "Robot ID: " << robot_id << endl;
    cout << "Pointer Value: " << ptr << endl;
    cout << "Dereferenced Pointer: " << *ptr << endl;

    *ptr = 99;
    cout << "Updated Robot ID: " << robot_id << endl;

    return 0;
}

In this example, we demonstrate how to handle pointers to manipulate data in memory.

First, we declare an integer variable robot_id and assign it the value 42. Then, we declare a pointer variable ptr and initialize it with the address of robot_id using the address-of operator &. The & operator retrieves the memory address of a variable.

We print the value of robot_id, the value of ptr (which is the memory address), and the dereferenced value of ptr using the dereference operator *. The * operator, when used in front of a pointer variable, retrieves the value stored at the memory address pointed to by the pointer. This is called dereferencing.

Next, we use the dereference operator * to modify the value at the memory address pointed to by ptr. 

We assign the value 99 to *ptr, which effectively updates the value of robot_id. By dereferencing ptr and assigning a new value, we are changing the value stored at the memory address pointed to by ptr, which is the memory address of robot_id.

Finally, we print the updated value of robot_id to confirm that it has been modified through the pointer.

Run the code.

3-robot-pointers

You should see the original robot ID, the pointer value (memory address), the dereferenced pointer value (which is the same as the original robot ID), and the updated robot ID printed in the terminal.

In robotics projects, pointers allow you to directly access and modify data without needing to move or copy it, which makes programs run faster and use less memory.

Managing Exceptions

Let’s learn how to manage exceptions in C++ for robotics applications. Proper exception handling is important for robust and reliable software systems, especially in the field of robotics where errors can have significant consequences.

Let’s start by creating a new C++ file called exception_handling.cpp.

Type the following code into the editor:

#include <iostream>
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("Division by zero");
    }
    return a / b;
}

int main() {
    try {
        double result = divide(10, 0);
        std::cout << "Result: " << result << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}

In this example, we define a function divide that throws a std::runtime_error exception if the denominator is zero. In the main function, we wrap the call to divide in a try block and handle any potential exceptions in the catch block.

Run the code.

4-exception-handling

You should see the error message “Error: Division by zero” printed in the terminal, as we intentionally passed 0 as the second argument to the divide function.

Proper exception handling is important in robotics applications, where unexpected situations or sensor failures can occur. By using exceptions and handling them appropriately, you can ensure that your code gracefully handles errors and maintains a consistent state, preventing potential damage or safety issues.

Thanks, and I’ll see you in the next tutorial.

Keep building!

How to Create Classes and Objects in Python

In this tutorial, we are going to learn about one of the core concepts of object-oriented programming: classes and objects. 

Understanding how to use classes and objects is essential for structuring robust and maintainable code in robotics, where you often model real-world entities like robots, sensors, and actuators.

Prerequisites

Getting Comfortable with Classes and Objects

Let’s start by defining what a class is. Think of a class like a recipe for making cookies. Just as a recipe tells us what ingredients we need (these are called variables or attributes in programming) and what steps to follow (these are called methods or functions in programming), a class defines both the data and the actions that our program can use. The recipe itself isn’t a cookie – it’s just instructions, just like a class is just code until we use it.

An object is when you actually make the cookies using your recipe – in programming terms, we call this “instantiating a class.” When you just write down a recipe (define a class), you’re not using any real ingredients or kitchen space yet (no computer memory is used). But when you start baking (create an object), that’s when you use real ingredients and take up real counter space (the computer allocates actual memory for your object). Each batch of cookies you make from the same recipe is like creating a new object from your class – they use the same instructions but are separate, physical things in your kitchen (separate spots in computer memory).

Let’s create a simple class called Robot. This class will have some basic attributes like name and color, and a method that allows the robot to introduce itself.

Create a program called robot_class.py inside the following folder: ~/Documents/python_tutorial.

Write this code:

class Robot:
    def __init__(self, name, color):
        self.name = name
        self.color = color

    def introduce_self(self):
        print(f"Hello, my name is {self.name} and my color is {self.color}.")

The __init__ method is a special method called a constructor. It is called when a new object is instantiated, and it’s used to initialize the attributes of the class.

Now, let’s create an object of the Robot class:

r1 = Robot("Addison", "red")
r1.introduce_self()

This creates an instance of Robot named r1 with the name “Addison” and the color “red”. We then call the introduce_self method to make the robot introduce itself.

You should see the output:

1-robot-class

This demonstrates how we’ve created a Robot object and used its method to perform an action.

Using classes and objects in robotics programming allows you to encapsulate behaviors and states associated with specific robotic components or subsystems, making your code more modular, reusable, and easier to manage. For example, each sensor or actuator can be modeled as an object with its methods for starting, stopping, and processing data.

Implementing a Basic Constructor

Let’s learn how to implement a basic constructor in Python, specifically within the context of robotics. 

Constructors are important for initializing robot objects with specific settings or parameters right when they’re created.

In Python, a constructor is defined using the __init__ method of a class. It is automatically invoked when a new object of that class is created.

The __init__ method can take parameters that define the initial state of the object. This is essential for robotics, where each robot might need specific configurations right from the start.

Let’s create a simple class called Robot. This class will have attributes such as name, type, and sensor_count, which we will set using the constructor.

Create a program called robot_constructor.py inside the following folder: ~/Documents/python_tutorial.

Write this code:

class Robot:
    def __init__(self, name, type, sensor_count):
        self.name = name
        self.type = type
        self.sensor_count = sensor_count
        print(f"Robot created: {self.name}, a {self.type} robot with {self.sensor_count} sensors.")

Here, the __init__ method initializes each new Robot instance with a name, type, and sensor count. These attributes allow us to differentiate between various robots, catering to different roles and functionalities in a robotics lab or factory.

Now, let’s create instances of our Robot class to see the constructor in action:

r1 = Robot("Atlas", "Humanoid", 5)
r2 = Robot("Spot", "Quadruped", 4)

This code creates two robots: “Atlas,” a humanoid robot with 5 sensors, and “Spot,” a quadruped robot with 4 sensors.

Now run this script.

2-robot-constructor

You should see output indicating that the robots have been successfully created with their respective attributes. This output verifies that our constructor is working as expected, initializing each robot with its specific characteristics.

Overriding a Function

Let’s explore the concept of overriding functions in Python, particularly in the context of object-oriented programming. This technique is essential in robotics programming when you need different implementations of the same method in parent and child classes.

Let me explain how overriding works with an example from robotics. Imagine you have a basic robot (parent class) that can move and dock to a charging station. When you want to create a special type of robot (child class) that does these actions differently, that’s where overriding comes in.

In programming, overriding lets you take a behavior that already exists in a parent class (like how the robot moves) and give it a new set of instructions in the child class (like making a cleaning robot move in a specific pattern). It’s like saying “ignore the original instructions and use these new ones instead.”

Before we dive into overriding, you need to understand inheritance, which is how we create new types of robots based on our basic robot design. Just like a cleaning robot inherits all the basic features of the basic robot, a child class inherits all the code from its parent class.

Let’s create a base class called Robot. This class will have a simple method that describes the robot’s operation.

Create a file named function_override.py inside the following folder: ~/Documents/python_tutorial, and write this code:

class Robot:
    def action(self):
        print("Performing a generic action")

Here, our Robot class has one method, action, which prints a generic message about the robot’s activity.

Now, let’s create a subclass (also known as “child class”) called FlyingRobot that inherits from Robot class and overrides the action method to provide more specific behavior:

class FlyingRobot(Robot):
    def action(self):
        print("Flying")

In the FlyingRobot subclass, we redefine the action method. When we call the action method on an instance of FlyingRobot, it will print “Flying”, which is specific to flying robots, instead of the generic action message.

Let’s see this in action. We’ll create instances of both Robot and FlyingRobot and call their action method:

generic_robot = Robot()
generic_robot.action()  # This calls the base class method

flying_robot = FlyingRobot()
flying_robot.action()  # This calls the overridden method in the subclass

Save this script, and run it.

You should see this output:

3-function-overrride

This output shows how the action method behaves differently depending on whether it’s called on an instance of the Robot or the FlyingRobot.

Overriding methods is particularly beneficial in robotics when you deal with different types of robots that might share some functionalities but also have unique behaviors. By using overriding, you can ensure that each robot type performs its tasks appropriately while still inheriting from a common base class.

That’s it for this tutorial on function overriding. Thanks, and I’ll see you in the next tutorial.

Keep building!

How to Install ROS 2 Jazzy

In this tutorial, we will install ROS 2 Jazzy.

By the end of this tutorial, you will be running your first ROS 2 programs.

If you prefer learning through video rather than text, check out the YouTube video below where I walk you through the entire process step by step. Otherwise, keep reading.

You Will Need

In order to complete this tutorial, you will need:

  • A computer or virtual machine running Ubuntu 24.04. If you followed my previous tutorial, you already have that setup.

Set the Locale

The official steps for installing ROS are at this link at ROS.org, but let’s go through this entire process together.

Follow along with me click by click, keystroke by keystroke.

We will begin by installing ROS 2 Jazzy via Debian Packages. Debian packages are software files used to install programs and applications on Ubuntu.

Open a new terminal window.

Type this command inside a terminal window.

locale

A locale is a set of variables that define the language, country, and character encoding settings. These settings are used by applications to determine how to display text, dates, times, and other information.

Now type the following command:

sudo apt update && sudo apt install locales

“sudo apt update” updates the package index list. This list is a database of all the software packages available for your version of Ubuntu.

“sudo apt install locales” installs the locales package, which provides support for different languages and regions.

Now type the following commands into the terminal window to generate locale definition files. After each command, press Enter on your keyboard:

sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8

In this case, these commands are generating locale definition files for the English (United States) locale and the English (United States) UTF-8 locale. The UTF-8 locale is a special locale that supports the UTF-8 character encoding, which is the standard encoding for most languages.

Now we need to verify the settings by typing:

locale

Here is what you should see:

1-locale

Enable the Required Repositories

Let’s add the ROS 2 apt repository to our system. APT stands for “Advanced Package Repository”. This repository provides a convenient way to install and manage ROS 2 packages without having to clone packages to your computer from GitHub and build them from that source code.  

Open a terminal window, and type the following two commands:

sudo apt install software-properties-common
sudo add-apt-repository universe

Press Enter.

The software-properties-common package provides a number of tools for managing software sources on Ubuntu and Debian systems. 

The universe repository is a software repository that contains a wide variety of software packages, including many that are not included in the default Ubuntu and Debian repositories. 

Now we need to add the ROS 2 GPG key with apt. The ROS 2 GPG key makes sure the software packages you are installing are from a trusted source.

Type these two commands:

sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg

Add the repository to your sources list (copy and paste all of this below):

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

Type the following command to install ROS 2 development tools.

sudo apt update && sudo apt install ros-dev-tools

Upgrade the packages on your system to make sure you have the newest versions.

sudo apt upgrade -y

Install ROS 2

Now for the fun part. Here is where we get to install ROS 2 Jazzy. 

Open a terminal window, and type this command:

sudo apt install ros-jazzy-desktop

Set Up the Environment Variables

Once jazzy has finished installing, you need to set up the important environment variables. Environment variables are settings that tell your computer how to find and use ROS 2 commands and packages.

Open a terminal window, and type this command:

echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc

When you run this command, it appends the line source /opt/ros/jazzy/setup.bash to your ~/.bashrc file

What does this do? Each time you open a new terminal window, you are starting what is called a bash session. The bash session needs to know what version of ROS 2 you are using. 

By adding this line (echo “source /opt/ros/jazzy/setup.bash”) to your ~/.bashrc file, you ensure the necessary environment variables and paths for ROS 2 Jazzy are properly set up each time you open a new terminal window, allowing you to use ROS 2 commands and tools without having to manually run the setup.bash script every time.

For the changes to take effect, you now need to open a new terminal window, or you can type this command in the current terminal:

source ~/.bashrc

You can verify that line was added by typing:

sudo apt-get install gedit -y
gedit ~/.bashrc

Close the gedit window.

2-verify-bash-alias

Check Your ROS 2 Version

Now let’s see what ROS 2 version we are using using:

printenv ROS_DISTRO

You should see “jazzy”.

You can also type:

env | grep ROS
3-env-grep-ros

Finally, you can also type:

echo $ROS_DISTRO

Set up the system to manage ROS package dependencies.

sudo rosdep init
rosdep update

Install Gazebo and Other Useful Packages

Let’s install some other useful packages like pip (the Python package manager), Gazebo, a simulation software for robotics, and NumPy, a scientific computing library for Python.

sudo apt-get install python3 python3-pip -y
sudo apt-get install ros-${ROS_DISTRO}-ros-gz -y
sudo apt-get install python3-numpy

Test Your Gazebo Installation

If you want to run an example Gazebo simulation world now, open a new terminal window, and type:

LIBGL_ALWAYS_SOFTWARE=1 QT_QPA_PLATFORM=xcb gz sim -v 4 shapes.sdf

These environment variables here “LIBGL_ALWAYS_SOFTWARE=1 QT_QPA_PLATFORM=xcb” help you avoid the following nasty error which happens by default when you try to launch Gazebo from a fresh ROS 2 Jazzy installation:

[GUI] [Err] [Ogre2RenderEngine.cc:1301]  Unable to create the rendering window: OGRE EXCEPTION(3:RenderingAPIException): currentGLContext was specified with no current GL context in GLXWindow::create at ./.obj-x86_64-linux-gnu/gz_ogre_next_vendor-prefix/src/gz_ogre_next_vendor/RenderSystems/GL3Plus/src/windowing/GLX/OgreGLXWindow.cpp (line 165)

Now close Gazebo by going to the terminal window, and typing CTRL + C on your keyboard.

You can see the error, if you open a new terminal window, and type:

gz sim -v 4 shapes.sdf

Let’s make these environment variables permanent. Open a terminal window, and type these commands, one after the other:

echo 'export LIBGL_ALWAYS_SOFTWARE=1' >> ~/.bashrc
echo 'export QT_QPA_PLATFORM=xcb' >> ~/.bashrc
source ~/.bashrc

Now test your Gazebo installation again by typing the following command:

gz sim

You should see this screen.

4-see-this-screen

Click on the NAO Joint Control simulation, and click Run.

5-nao-simulation

Close the simulation by typing CTRL + C in the terminal window.

Now try the Panda Joint Control World.

gz sim

Click on the Panda Joint Control World simulation, and click Run. It might take up to 60 seconds to load. Just be patient and don’t quit Gazebo if asked.

6-panda-joint-control-world

Close the simulation by typing CTRL + C in the terminal window.

Now run another example:

gz sim shapes.sdf -v 4

Close the simulation by typing CTRL + C in the terminal window.

Now let’s run the same shapes example using a ros2 command (source: official Gazebo ROS 2 GitHub repository):

ros2 launch ros_gz_sim gz_sim.launch.py gz_args:="shapes.sdf"
7-shapes-sdf

Close the simulation by typing CTRL + C in the terminal window.

Test Your ROS 2 Installation

Now that we have tested Gazebo, let’s test our ROS 2 installation by running some sample programs.

Open a terminal window, and type:

ros2 run demo_nodes_cpp talker
9-talker

This command runs a pre-built program called “talker” that comes with ROS 2. The “talker” program publishes messages to the ROS 2 system in string format. 

Open another terminal window, and type:

ros2 run demo_nodes_py listener
10-listener

If your output looks similar to the images above, you have installed ROS 2 successfully. 

To close these programs, press CTRL + C on your keyboard in both terminal windows.

So what did we just do here, and what do these two hello world programs we just ran have to do with real robots?

So imagine you’re building a robot that needs to navigate through a room. The “talker” program is like a sensor on the robot (e.g., a depth camera for example) that constantly sends out information about what it sees. 

The “listener” program, on the other hand, receives the information that was published by the talker program. This listener program, in a real robot, could do something with that data like stop the wheels if an obstacle is detected by the camera.

The talker program is what we call in ROS 2, a publisher. The listener program is called a subscriber. Remember those two terms. Those are the two most important terms in all of ROS 2. 

A ROS 2 publisher sends data, and a subscriber receives data. 

In future tutorials, we will create our own publishers and subscribers for actual robots. 

Publishers and subscribers are the main way programs exchange information with each other inside a robot.

So, again, just remember this…publishers are programs that send data to other programs, and subscribers are programs that receive data from other programs. All these programs are usually written in either Python or C++.

Collectively, publishers and subscribers in ROS 2 are called nodes. You just saw how to run a publisher node named talker and a subscriber node named listener.

The term node is the third most important term in all of ROS 2. So remember it because you will be using that term again and again over the course of your work with ROS 2.

To close out this tutorial, let me show you a cool program that enables you to show multiple terminal windows in a single window.

The program is called terminator.

Open a terminal window, and type:

sudo apt-get install terminator -y

Now type:

terminator

Right-click on the screen, and click “Split Horizontally”.

11-split-horizontally

On the top panel, type:

ros2 run demo_nodes_cpp talker

On the bottom panel, type:

ros2 run demo_nodes_py listener

Now, press Enter in both terminals.

Here is what you should get:

12-terminator

Press CTRL + C in both terminal windows to close everything.

That’s it! Keep building! I’ll see you in the next tutorial.