How to Upload a ROS 2 Project to GitHub

In this tutorial, I will show you how to store your ROS 2 project in a repository on GitHub.

In case you’re not familiar with GitHub, GitHub is an online platform specifically designed for software development. It offers several key functionalities:

  • Version control: This allows developers to track changes made to code over time. Imagine it like a time machine for your code, letting you revert to previous versions if necessary.
  • Code storage: GitHub acts as a secure and centralized location to store and manage code projects. Think of it like a cloud storage specifically for your code.
  • Collaboration: Teams can work together on projects by sharing code, discussing changes, and merging different contributions seamlessly.
  • Open-source contribution: GitHub is a hub for open-source projects, where developers can publicly share their code, contribute to existing projects, and learn from others.

Prerequisites

I have created a ROS 2 workspace that has a folder called mycobot_ros2 (i.e. /home/ubuntu/ros2_ws/src/mycobot_ros2).

Inside the mycobot_ros2 folder, I have two ROS 2 packages, mycobot_description and mycobot_ros2. You can see the complete repository here on GitHub.

Directions

Install Git

The first thing you need to do is install Git. Open a new terminal window, and type:

sudo apt-get update
sudo apt-get install git

Check the git version you have.

git --version

Configure Git

Configure your git username and email.

git config --global user.name "John Doe"
git config --global user.email "johndoe@example.com"
git config --global init.defaultBranch main

Initialize Git

Move to inside your project folder.

cd  ~/ros2_ws/src/mycobot_ros2/

Initialize the folder as a Git repository by running:

git init

Add and Commit the Files to Your Local Repository

Add the files in your folder to the repository with the following command:

git add .

Commit your staged files to your local repository with:

git commit -m "Initial commit"

Create the Remote Repository on GitHub

Go to GitHub and log in.

Click on the “+” icon in the upper right corner and select “New repository.”

Name your repository, add a description (optional), and choose whether the repository will be public or private.

Click “Create repository.”

Link the Local Repository to the Remote Repository

After creating your repository on GitHub, you’ll get a URL for that repository. Link your local repository to GitHub with:

git remote add origin <repository-URL>

Log in to your GitHub account.

Generate your personal access token. Read about how to do this here.

Finally, push your code from your local repository to GitHub with:

git branch --set-upstream-to=origin/main main

The command git branch –set-upstream-to=origin/main main links your local branch named “main” with its corresponding remote tracking branch “origin/main” on GitHub. This establishes a connection between the two branches, making it easier to keep them in sync in the future.

git push origin main --force

Type your GitHub username and personal access token.

Now if you go back to GitHub, you can see your repository.

Let’s add our LICENSE file again.

Go to your repository’s main page on GitHub.

Click on the “Add file” button on the top right corner.

Choose “Create new file”.

In the file name field, type LICENSE or LICENSE.md (all uppercase).

Click on “Choose a license template”.

Click OK when it asks about unsaved changes.

On the left side of the page, review the available licenses and select the one you want to use.

You can optionally add your name, year, and any additional comments in the file content below the chosen license text.

Click Review and Submit.

Click “Commit changes” (twice) to create the license file and add it to your repository.

Now make sure we get these changes locally.

Open a terminal window, and type:

git branch --set-upstream-to=origin/main main
git fetch
git status
git pull

I want GitHub to ignore the .vscode/ folder in the future.

touch .gitignore

Open the .gitignore file in a text editor.

gedit .gitignore

Add the following line to the file:

.vscode/

Save and close the file. This line tells Git to ignore the .vscode directory, meaning any files or subdirectories within .vscode/ will not be tracked or committed.

git add .gitignore
git commit -m "Add .gitignore to exclude .vscode directory"
git push 
git rm -r --cached .vscode
git commit -m "Stop tracking .vscode directory"

Remove the Need to Use a Username and Password

If you want to not have to use a username and password every time you run “git push”, you can use SSH keys. This page has the instructions on how to do that.

Here is the process…

Generate an SSH key pair (if you don’t already have one) by running:

ssh-keygen -t ed25519 -C "your_email@example.com"

When you get prompted for a password or saving location, just keep pressing Enter, which will accept the default.

Start the ssh-agent in the background.

eval "$(ssh-agent -s)"

Add your SSH private key to the ssh-agent:

ssh-add ~/.ssh/id_ed25519

Add the SSH public key to your Git server.

cat ~/.ssh/id_ed25519.pub

Copy the entire result to your clipboard by highlighting everything and copying it.

Go to your GitHub account “Settings” by clicking your profile icon in the upper right of the website.

Look for “SSH and GPG keys”.

Add a new SSH key, pasting the copied key there.

Go back to the main page of your repository on GitHub and find the SSH URL by clicking the green button labeled “Code”.

Copy the SSH URL that is in there.

Switch your repository’s remote URL to SSH by going to your Ubuntu Linux terminal window, and moving to the directory of your repository.

cd <path to your your local repository>
git remote set-url origin git@github.com:username/repository.git

That’s it!

Using SSH keys is a more secure and convenient method for machines where you regularly push changes, as it doesn’t require entering your credentials after the initial setup.

How to Create a ROS 2 C++ Subscriber – Iron

In this tutorial, we will go over how to create a C++ subscriber for ROS 2.

In ROS 2 (Robot Operating System 2), a C++ subscriber is a program (written in C++) that listens for messages being published on a specific topic.

Topics in ROS 2 are channels of communication named according to the type of information they carry, such as “/robot/speed” for speed information or “/camera/image” for vision information. Each subscriber in ROS 2 declares its interest in a particular topic and is programmed to react or process the messages received on that topic.

The official instructions for creating a subscriber are here, but I will walk you through the entire process, step by step.

We will be following the ROS 2 C++ Style Guide.

Let’s get started!

Prerequisites

Directions

Open a terminal, and type these commands to open VS Code.

cd ~/ros2_ws
code .

Write the Code

Go back to the Explorer (Ctrl + Shift + E).

Right-click on the src folder to create a new file called “minimal_cpp_subscriber.cpp”.

Type the following code inside minimal_cpp_subscriber.cpp:

/**
 * @file minimal_cpp_subscriber.cpp
 * @brief Demonstrates subscribing to string messages on a ROS 2 topic.
 *
 * Description: Demonstrates the basics of subscribing to messages within the ROS 2 framework. 
 * The core functionality of this subscriber is to display output to the terminal window
 * when a message is received over a topic.
 * 
 * -------
 * Subscription Topics:
 *   String message
 *   /topic_cpp - std_msgs/String
 * -------
 * Publishing Topics:
 *   None
 * -------
 * @author Addison Sears-Collins
 * @date 2024-02-15
 */

#include "rclcpp/rclcpp.hpp" // ROS 2 C++ client library for node creation and management
#include "std_msgs/msg/string.hpp" // Standard message type for string messages
using std::placeholders::_1; // Create a placeholder for the first argument of the function

/**
 * @class MinimalSubscriber
 * @brief Defines a minimal ROS 2 subscriber node.
 *
 * This class inherits from rclcpp::Node and demonstrates creating a subscriber and
 * subscribing to messages.
 */
class MinimalSubscriber : public rclcpp::Node
{
public:
    /**
     * @brief Constructs a MinimalSubscriber node.
     *
     * Sets up a subscriber for 'std_msgs::msg::String' messages on the "topic_cpp" topic.     * 
     */
    MinimalSubscriber() : Node("minimal_subscriber")
    {
        // Create a subscriber object for listening to string messages on 
        // the "topic_cpp" topic with a queue size of 10.
        subscriber_ = create_subscription<std_msgs::msg::String>
        (
            "/topic_cpp", 
            10, 
            std::bind(
                &MinimalSubscriber::topicCallback, 
                this, 
                _1
            )
        );	
    }

    /**
     * @brief This function runs every time a message is received on the topic.
     *
     * This is the callback function of the subscriber. It publishes a string message
     * every time a message is received on the topic.
     * 
     * @param msg The string message received on the topic
     * @return Void.
     */
    void topicCallback(const std_msgs::msg::String &msg) const
    {
        // Write a message every time a new message is received on the topic.
        RCLCPP_INFO_STREAM(get_logger(), "I heard: " << msg.data.c_str());

    }
	
private:
    // Member variables.
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber_; // The subscriber object.
};

/**
 * @brief Main function.
 *
 * Initializes the ROS 2 system and runs the minimal_subscriber node. It keeps the node
 * alive until it is manually terminated.
 */
int main(int argc, char * argv[])
{

  // Initialize ROS 2.
  rclcpp::init(argc, argv); 
  
  // Create an instance of the MinimalPublisher node and keep it running.
  auto minimal_subscriber_node = std::make_shared<MinimalSubscriber>();
  rclcpp::spin(minimal_subscriber_node);

  // Shutdown ROS 2 upon node termination.
  rclcpp::shutdown(); 

  // End of program.
  return 0; 
}

Configure CMakeLists.txt

Now we need to modify the CMakeLists.txt file inside the package so that the ROS 2 system will be able to find the cost we just wrote.

Open up the CMakeLists.txt file that is inside the package.

Make it look like this:

cmake_minimum_required(VERSION 3.8)
project(cobot_arm_examples)

# Check if the compiler being used is GNU's C++ compiler (g++) or Clang.
# Add compiler flags for all targets that will be defined later in the 
# CMakeLists file. These flags enable extra warnings to help catch
# potential issues in the code.
# Add options to the compilation process
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# Locate and configure packages required by the project.
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclpy REQUIRED)
find_package(std_msgs REQUIRED)

# Define a CMake variable named dependencies that lists all
# ROS 2 packages and other dependencies the project requires.
set(dependencies
  rclcpp
  std_msgs
)

# Add the specified directories to the list of paths that the compiler
# uses to search for header files. This is important for C++
# projects where you have custom header files that are not located
# in the standard system include paths.
include_directories(
  include
)

# Tells CMake to create an executable target named minimal_cpp_publisher
# from the source file src/minimal_cpp_publisher.cpp. Also make sure CMake
# knows about the program's dependencies.
add_executable(minimal_cpp_publisher src/minimal_cpp_publisher.cpp)
ament_target_dependencies(minimal_cpp_publisher ${dependencies})

add_executable(minimal_cpp_subscriber src/minimal_cpp_subscriber.cpp)
ament_target_dependencies(minimal_cpp_subscriber ${dependencies})

# Copy necessary files to designated locations in the project
install (
  DIRECTORY cobot_arm_examples scripts
  DESTINATION share/${PROJECT_NAME}
)

install(
  DIRECTORY include/
  DESTINATION include
)

# Install cpp executables
install(
  TARGETS
  minimal_cpp_publisher
  minimal_cpp_subscriber
  DESTINATION lib/${PROJECT_NAME}
)

# Install Python modules for import
ament_python_install_package(${PROJECT_NAME})

# Install Python executables
install(
  PROGRAMS
  scripts/minimal_py_publisher.py
  scripts/minimal_py_subscriber.py
  #scripts/example3.py
  #scripts/example4.py
  #scripts/example5.py
  #scripts/example6.py
  #scripts/example7.py
  DESTINATION lib/${PROJECT_NAME}
)

# Automates the process of setting up linting for the package, which
# is the process of running tools that analyze the code for potential
# errors, style issues, and other discrepancies that do not adhere to
# specified coding standards or best practices.
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

# Used to export include directories of a package so that they can be easily
# included by other packages that depend on this package.
ament_export_include_directories(include)

# Generate and install all the necessary CMake and environment hooks that 
# allow other packages to find and use this package.
ament_package()

Configure package.xml

Now we need to configure the package.xml file.

Open the package.xml file, and make sure it looks like this:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>cobot_arm_examples</name>
  <version>0.0.0</version>
  <description>Basic examples demonstrating ROS 2</description>
  <maintainer email="automaticaddison@example.com">Addison Sears-Collins</maintainer>
  <license>Apache-2.0</license>

  <!--Specify build tools that are needed to compile the package-->
  <buildtool_depend>ament_cmake</buildtool_depend>
  <buildtool_depend>ament_cmake_python</buildtool_depend>

  <!--Declares package dependencies that are required for building the package-->
  <depend>rclcpp</depend>
  <depend>rclpy</depend>
  <depend>std_msgs</depend>

  <!--Specifies dependencies that are only needed for testing the package-->
  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

Build the Workspace

cd ~/ros2_ws
colcon build
source ~/.bashrc

Run the Nodes

First run your publisher node.

ros2 run cobot_arm_examples minimal_cpp_publisher 
23-run-cpp-publisher-1
24-topic-echo-cpp-publisher-1

Now run your subscriber node.

ros2 run cobot_arm_examples minimal_cpp_subscriber
27_minimal_cpp_subscriber

An Important Notes on Subscribers and Publishers

In the example above, we published a string message to a topic named /topic_cpp using a C++ node, and we subscribed to that topic using a C++node. 

ROS 2 is language agnostic, so we could have also used a Python node to the publishing and a C++ node to do the subscribing, and vice versa.

That’s it for now. Keep building!

How to Create a ROS2 C++ Publisher – Iron

In this tutorial, we will go over how to create a C++ publisher for ROS 2.

In ROS 2 (Robot Operating System 2), a C++ publisher is a program (written in C++) that sends messages across the ROS network to other parts of the system.

The official instructions for creating a publisher are here, but I will walk you through the entire process, step by step.

We will be following the ROS 2 C++ Style Guide.

Let’s get started!

Prerequisites

Directions

Open a terminal, and type these commands to open VS Code.

cd ~/ros2_ws
code .

Go to View -> Extensions

Search for the “Doxygen Documentation Generator” extension.

22-doxygen-documentation-generator

Install it.

Write the Code

Go back to the Explorer (Ctrl + Shift + E).

Right-click on the src folder to create a new file called “minimal_cpp_publisher.cpp”.

Type the following code inside minimal_cpp_publisher.cpp:

/**
 * @file minimal_cpp_publisher.cpp
 * @brief Demonstrates publishing string messages to a ROS 2 topic.
 *
 * Description: Demonstrates the basics of publishing messages within the ROS 2 framework. 
 * The core functionality of this publisher is to repeatedly send out string messages
 * at a fixed frequency.
 * 
 * -------
 * Subscription Topics:
 *   None
 * -------
 * Publishing Topics:
 *   String message
 *   /topic_cpp - std_msgs/String
 * -------
 * @author Addison Sears-Collins
 * @date 2024-02-12
 */

#include "rclcpp/rclcpp.hpp" // ROS 2 C++ client library for node creation and management
#include "std_msgs/msg/string.hpp" // Standard message type for string messages

using namespace std::chrono_literals; // Enables the specification of a time duration

/**
 * @class MinimalPublisher
 * @brief Defines a minimal ROS 2 publisher node.
 *
 * This class inherits from rclcpp::Node and demonstrates creating a publisher,
 * publishing messages, and using a timer callback in ROS 2.
 */
class MinimalPublisher : public rclcpp::Node
{
public:
    /**
     * @brief Constructs a MinimalPublisher node.
     *
     * Sets up a publisher for 'std_msgs::msg::String' messages on the "topic_cpp" topic
     * and initializes a timer to call the timerCallback method.
     */
    MinimalPublisher() : Node("minimal_publisher"), count_(0) 
    {
        // Create a publisher object for sending string messages on the "topic_cpp" topic 
        // with a queue size of 10.
        publisher_ = create_publisher<std_msgs::msg::String>("topic_cpp", 10);
	
	    // Set up a timer to call the timerCallback function 
	    timer_ = create_wall_timer(500ms, std::bind(&MinimalPublisher::timerCallback, this));
        RCLCPP_INFO(get_logger(), "Publishing at 2 Hz");
    }

    /**
     * @brief Timer callback function.
     *
     * This method is called at a fixed interval. It publishes a string message
     * containing "Hello World" followed by a sequence number.
     * 
     *  @return Void.
     */
    void timerCallback() 
    {
        // Create a new String message object.
        auto message = std_msgs::msg::String();
        message.data = "Hello World! " +  std::to_string(count_++);
        
		// Publish the message
        publisher_->publish(message);
    }
	
private:
    // Member variables.
    size_t count_; // A counter to keep track of the number of messages published.
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; // The publisher object.
    rclcpp::TimerBase::SharedPtr timer_; // Timer for scheduling the publishing task.
};

/**
 * @brief Main function.
 *
 * Initializes the ROS 2 system and runs the minimal_publisher node. It keeps the node
 * alive until it is manually terminated.
 */
int main(int argc, char * argv[])
{

  // Initialize ROS 2.
  rclcpp::init(argc, argv); 
  
  // Create an instance of the MinimalPublisher node and keep it running.
  auto minimal_publisher_node = std::make_shared<MinimalPublisher>();
  rclcpp::spin(minimal_publisher_node);

  // Shutdown ROS 2 upon node termination.
  rclcpp::shutdown(); 

  // End of program.
  return 0; 
}

Configure CMakeLists.txt

Now we need to modify the CMakeLists.txt file inside the package so that the ROS 2 system will be able to find the cost we just wrote.

Open up the CMakeLists.txt file that is inside the package.

Make it look like this:

cmake_minimum_required(VERSION 3.8)
project(cobot_arm_examples)

# Check if the compiler being used is GNU's C++ compiler (g++) or Clang.
# Add compiler flags for all targets that will be defined later in the 
# CMakeLists file. These flags enable extra warnings to help catch
# potential issues in the code.
# Add options to the compilation process
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# Locate and configure packages required by the project.
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclpy REQUIRED)
find_package(std_msgs REQUIRED)

# Define a CMake variable named dependencies that lists all
# ROS 2 packages and other dependencies the project requires.
set(dependencies
  rclcpp
  std_msgs
)

# Add the specified directories to the list of paths that the compiler
# uses to search for header files. This is important for C++
# projects where you have custom header files that are not located
# in the standard system include paths.
include_directories(
  include
)

# Tells CMake to create an executable target named minimal_cpp_publisher
# from the source file src/minimal_cpp_publisher.cpp. Also make sure CMake
# knows about the program's dependencies.
add_executable(minimal_cpp_publisher src/minimal_cpp_publisher.cpp)
ament_target_dependencies(minimal_cpp_publisher ${dependencies})

add_executable(minimal_cpp_subscriber src/minimal_cpp_subscriber.cpp)
ament_target_dependencies(minimal_cpp_subscriber ${dependencies})

# Copy necessary files to designated locations in the project
install (
  DIRECTORY cobot_arm_examples scripts
  DESTINATION share/${PROJECT_NAME}
)

install(
  DIRECTORY include/
  DESTINATION include
)

# Install cpp executables
install(
  TARGETS
  minimal_cpp_publisher
  minimal_cpp_subscriber
  DESTINATION lib/${PROJECT_NAME}
)

# Install Python modules for import
ament_python_install_package(${PROJECT_NAME})

# Install Python executables
install(
  PROGRAMS
  scripts/minimal_py_publisher.py
  scripts/minimal_py_subscriber.py
  #scripts/example3.py
  #scripts/example4.py
  #scripts/example5.py
  #scripts/example6.py
  #scripts/example7.py
  DESTINATION lib/${PROJECT_NAME}
)

# Automates the process of setting up linting for the package, which
# is the process of running tools that analyze the code for potential
# errors, style issues, and other discrepancies that do not adhere to
# specified coding standards or best practices.
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

# Used to export include directories of a package so that they can be easily
# included by other packages that depend on this package.
ament_export_include_directories(include)

# Generate and install all the necessary CMake and environment hooks that 
# allow other packages to find and use this package.
ament_package()

Configure package.xml

Now we need to configure the package.xml file.

Open the package.xml file, and make sure it looks like this:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>cobot_arm_examples</name>
  <version>0.0.0</version>
  <description>Basic examples demonstrating ROS 2</description>
  <maintainer email="automaticaddison@example.com">Addison Sears-Collins</maintainer>
  <license>Apache-2.0</license>

  <!--Specify build tools that are needed to compile the package-->
  <buildtool_depend>ament_cmake</buildtool_depend>
  <buildtool_depend>ament_cmake_python</buildtool_depend>

  <!--Declares package dependencies that are required for building the package-->
  <depend>rclcpp</depend>
  <depend>rclpy</depend>
  <depend>std_msgs</depend>

  <!--Specifies dependencies that are only needed for testing the package-->
  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

Build the Workspace

cd ~/ros2_ws
colcon build
source ~/.bashrc

Run the Node

ros2 run cobot_arm_examples minimal_cpp_publisher 
23-run-cpp-publisher

Let’s check out the list of topics.

ros2 topic list

Let’s check out the data coming over the topic_cpp topic.

ros2 topic echo /topic_cpp 
24-topic-echo-cpp-publisher
ros2 topic hz /topic_cpp
ros2 topic info /topic_cpp --verbose