Trying ROS2 on Windows

Hitoruna
6 min readMay 2, 2023

--

I usually do my programming on Linux -Ubuntu to be more specific-, but for a while I was thinking to give ROS2 a try and the day I decided to do it I only had my Windows 10 computer at hand, and I am writing about my experience here.

Photo by Adam Lukomski on Unsplash

I have used ROS before, and we know that ROS is not supported on windows. But ROS2 is!. Now, call me particular, but I didn’t feel like installing ROS2 on the machine. Fortunately we have docker images! (contributed by the community). So I decided to give them a try!.

So following the guide, we open a command window and type:

docker pull osrf/ros:humble-desktop 

with that we have the image and are ready to use it.

A hello world to start

First from the command window that we have open, let’s type:

docker run -it osrf/ros:humble-desktop
root@fa11851cddc2:/#

With that we are inside the container. Let’s check

ros2 --help

Now, let’s open another command terminal and type

>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fa11851cddc2 osrf/ros:humble-desktop "/ros_entrypoint.sh …" 3 minutes ago Up 3 minutes condescending_booth

C:\Users\me>docker exec -ti condescending_booth /bin/bash
root@fa11851cddc2:/# ros2 --help
bash: ros2: command not found
root@fa11851cddc2:/#

So, let’s see what happened here. First we do docker ps to see what containers we have running. There we can see our container and its name (in this case condescending_booth) . Then we call docker exec with the appropriate parameters and running a bash shell. With that we are inside the container. However, ROS2 is not recognized there!

To solve this we type

source /opt/ros/humble/setup.bash

or

source /opt/ros/$ROS_DISTRO/setup.bash

With that ROS2 is recognized on that terminal too.

Let’s finish this part running the demos. On one terminal the listener:

ros2 run demo_nodes_cpp listener

and in the other terminal the talker:

ros2 run demo_nodes_cpp talker
two command windows one with a talker and one with a listener

With that we finished the demo.

Surely we want to write our own code?!

Of course! (and don’t call me Shirley :) ). Running the demo is not enough, we want to type and run our own publisher and subscriber, so let’s do that.

First, let’s exit our container. So from the first command window (the one in which we run the container let’s type exit . You can see that with that the other window also exited the container.

Let’s create by any means you want (the command line or windows explorer) a folder named ros2_ws and inside that folder another one called src . Change directory to the ros2_ws folder in the command terminal and we are ready. Let’s do:

>docker run -it --rm -v %cd%:/usr/me/ros2_ws osrf/ros:humble-desktop

With that we are inside the container and we have mapped the current directory in the window host (the %cdpart) to an appropriate path inside the container. Now inside of it, we need to again change directory to this path: /usr/me/ros2_ws . From the other command window let’s do docker exec` again but be aware that the name of the container have changed, so check first with docker ps. (and of course source ros2 again)

With that we have again our two terminals this time mapped to a workspace on our disk.

Next, let’s create an empty workspace. From inside the container, let’s type

root@561b3a2e827c:/usr/me/ros2_ws# colcon build

Summary: 0 packages finished [0.29s]
root@561b3a2e827c:/usr/me/ros2_ws#

With that we have now three more folders: build, install and log.

Our first package

Let’s create our first package. From the src folder we do

root@561b3a2e827c:/usr/me/ros2_ws/src# ros2 pkg create ros2_hello_cpp --build-type ament_cmake --dependencies std_msgs rclcpp
going to create a new package
package name: ros2_hello_cpp
destination directory: /usr/me/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['root <root@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['std_msgs', 'rclcpp']
creating folder ./ros2_hello_cpp
creating ./ros2_hello_cpp/package.xml
creating source and include folder
creating folder ./ros2_hello_cpp/src
creating folder ./ros2_hello_cpp/include/ros2_hello_cpp
creating ./ros2_hello_cpp/CMakeLists.txt

[WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identitifers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0

Then we go up in the folder and do colcon build again.

root@561b3a2e827c:/usr/me/ros2_ws/src# cd ..
root@561b3a2e827c:/usr/me/ros2_ws# colcon build
Starting >>> ros2_hello_cpp
Finished <<< ros2_hello_cpp [6.44s]

Summary: 1 package finished [6.76s]
root@561b3a2e827c:/usr/me/ros2_ws#

with that we have created our package. Let’s do a lC from the following folder:

root@561b3a2e827c:/usr/me/ros2_ws/src/ros2_hello_cpp# ls
CMakeLists.txt include package.xml src

and we recognize the CMakeLists file and the package xml file.

Coding and running it

We open vscode or any other tool to write our nodes following the example of the ROS2 tutorial.

For the talker

#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}

private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};

int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}

For the listener

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}

private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}

and me modify the CMakeLists.txt as

cmake_minimum_required(VERSION 3.8)
project(ros2_hello_cpp)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rclcpp REQUIRED)

add_executable(talker src/talker.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

add_executable(listener src/listener.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})

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()



ament_package()

With that, we do from the folder ros2_ws

root@561b3a2e827c:/usr/me/ros2_ws# colcon build --packages-select ros2_hello_cpp
Starting >>> ros2_hello_cpp
[0.5s] [0/1 complete] [ros2_hello_cpp:build - 0.2s]
[0.8s] [0/1 complete] [ros2_hello_cpp:build - 0.5s]
Finished <<< ros2_hello_cpp [26.8s]

Summary: 1 package finished [27.2s]

Now let’s try them

First we have to source them so we do in every terminal

source install/local_setup.bash

and then

ros2 run ros2_hello_cpp listener

and

ros2 run ros2_hello_cpp talker

and we have

Our talker and listener

and there we have it! Our own talker and listener using ROS2 in Windows.

As I wrote in the beginning, I mainly do coding on a linux machine, but I am curious to continue writing ROS2 in my windows machine, so I guess next I will try other more interesting nodes and see how they go. Till next time and happy ROSing!

--

--