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.
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
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 %cd
part) 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
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!