First of all, install ROS 2 Humble Hawksbill on your Ubuntu 22.04 LTS computer. To do so from binaries, via Debian packages, follow the instructions detailed here.
TIP: Alternatively, you can use a docker container with a fresh ROS 2 Humble installation. The one that serves the purpose is the container run by the command:
docker run -it --net=host -v /dev:/dev --privileged ros:humble
Once you have a ROS 2 installation in the computer, follow these steps to install the micro-ROS build system:
# Source the ROS 2 installation
source /opt/ros/$ROS_DISTRO/setup.bash
# Create a workspace and download the micro-ROS tools
mkdir microros_ws
cd microros_ws
git clone -b $ROS_DISTRO https://github.com/micro-ROS/micro_ros_setup.git src/micro_ros_setup
# Update dependencies using rosdep
sudo apt update && rosdep update
rosdep install --from-paths src --ignore-src -y
# Install pip
sudo apt-get install python3-pip
# Build micro-ROS tools and source them
colcon build
source install/local_setup.bash
Creating a new firmware workspace
Once the build system is installed, let’s create a firmware workspace that targets all the required code and tools:
# Create firmware step
ros2 run micro_ros_setup create_firmware_ws.sh host
Once the command is executed, a folder named firmware must be present in your workspace.
This step is in charge, among other things, of downloading a set of micro-ROS apps for Linux, that are located at src/uros/micro-ROS-demos/rclc. Each app is represented by a folder containing the following files:
main.c: This file contains the logic of the application.
CMakeLists.txt: This is the CMake file containing the script to compile the application.
For the user to create a custom application, a folder <my_app> will need to be registered in this location, containing the two files just described. Also, any such new application folder needs to be registered in src/uros/micro-ROS-demos/rclc/CMakeLists.txt by adding the following line:
export_executable(<my_app>)
In this tutorial, we will focus on the out-of-the-box ping_pong application located at src/uros/micro-ROS-demos/rclc/ping_pong. You can check the complete content of this app here.
This example showcases a micro-ROS node with two publisher-subscriber pairs associated with a ping and a pong topics, respectively. The node sends a ping package with a unique identifier, using a ping publisher. If the ping subscriber receives a ping from an external node, the pong publisher responds to the incoming ping with a pong. To test that this logic is correctly functioning, we implement communication with a ROS 2 node that:
Listens to the topics published by the ping subscriber.
Publishes a fake_ping package, that is received by the micro-ROS ping subscriber. As a consequence, the pong publisher on the micro-ROS application will publish a pong, to signal that it received the fake_ping correctly.
The diagram below clarifies the communication flow between these entities:
The contents of the host app specific files can be found here: main.c and CMakeLists.txt. A thorough review of these files is illustrative of how to create a micro-ROS app in this RTOS.
#include<rcl/rcl.h>#include<rcl/error_handling.h>#include<rclc/rclc.h>#include<rclc/executor.h>#include<std_msgs/msg/header.h>#include<stdio.h>#include<unistd.h>#include<time.h>#defineSTRING_BUFFER_LEN100#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){printf("Failed status on line %d: %d. Aborting.\n",__LINE__,(int)temp_rc); return 1;}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){printf("Failed status on line %d: %d. Continuing.\n",__LINE__,(int)temp_rc);}}
rcl_publisher_t ping_publisher;rcl_publisher_t pong_publisher;rcl_subscription_t ping_subscriber;rcl_subscription_t pong_subscriber;std_msgs__msg__Header incoming_ping;std_msgs__msg__Header outcoming_ping;std_msgs__msg__Header incoming_pong;int device_id;int seq_no;int pong_count;voidping_timer_callback(rcl_timer_t* timer,int64_t last_call_time){ (void) last_call_time;if (timer !=NULL) { seq_no =rand();sprintf(outcoming_ping.frame_id.data,"%d_%d", seq_no, device_id);outcoming_ping.frame_id.size =strlen(outcoming_ping.frame_id.data); // Fill the message timestampstructtimespects;clock_gettime(CLOCK_REALTIME,&ts);outcoming_ping.stamp.sec =ts.tv_sec;outcoming_ping.stamp.nanosec =ts.tv_nsec; // Reset the pong count and publish the ping message pong_count =0;RCSOFTCHECK(rcl_publish(&ping_publisher, (constvoid*)&outcoming_ping,NULL));printf("Ping send seq %s\n",outcoming_ping.frame_id.data); }}voidping_subscription_callback(constvoid* msgin){const std_msgs__msg__Header * msg = (const std_msgs__msg__Header *)msgin; // Dont pong my own pingsif(strcmp(outcoming_ping.frame_id.data,msg->frame_id.data) !=0){printf("Ping received with seq %s. Answering.\n",msg->frame_id.data);RCSOFTCHECK(rcl_publish(&pong_publisher, (constvoid*)msg,NULL)); }}voidpong_subscription_callback(constvoid* msgin){const std_msgs__msg__Header * msg = (const std_msgs__msg__Header *)msgin;if(strcmp(outcoming_ping.frame_id.data,msg->frame_id.data) ==0) { pong_count++;printf("Pong for seq %s (%d)\n",msg->frame_id.data, pong_count); }}intmain(){rcl_allocator_t allocator =rcl_get_default_allocator();rclc_support_t support; // create init_optionsRCCHECK(rclc_support_init(&support,0,NULL,&allocator)); // create nodercl_node_t node;RCCHECK(rclc_node_init_default(&node,"pingpong_node","",&support)); // Create a reliable ping publisher RCCHECK(rclc_publisher_init_default(&ping_publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/ping"));
// Create a best effort pong publisher RCCHECK(rclc_publisher_init_best_effort(&pong_publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/pong"));
// Create a best effort ping subscriber RCCHECK(rclc_subscription_init_best_effort(&ping_subscriber, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/ping"));
// Create a best effort pong subscriber RCCHECK(rclc_subscription_init_best_effort(&pong_subscriber, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/pong"));
// Create a 3 seconds ping timer timer,rcl_timer_t timer;RCCHECK(rclc_timer_init_default(&timer,&support,RCL_MS_TO_NS(2000), ping_timer_callback)); // Create executorrclc_executor_t executor =rclc_executor_get_zero_initialized_executor();RCCHECK(rclc_executor_init(&executor,&support.context,3,&allocator));RCCHECK(rclc_executor_add_timer(&executor,&timer)); RCCHECK(rclc_executor_add_subscription(&executor, &ping_subscriber, &incoming_ping, &ping_subscription_callback, ON_NEW_DATA));
RCCHECK(rclc_executor_add_subscription(&executor, &pong_subscriber, &incoming_pong, &pong_subscription_callback, ON_NEW_DATA));
// Create and allocate the pingpong messagescharoutcoming_ping_buffer[STRING_BUFFER_LEN];outcoming_ping.frame_id.data = outcoming_ping_buffer;outcoming_ping.frame_id.capacity = STRING_BUFFER_LEN;charincoming_ping_buffer[STRING_BUFFER_LEN];incoming_ping.frame_id.data = incoming_ping_buffer;incoming_ping.frame_id.capacity = STRING_BUFFER_LEN;charincoming_pong_buffer[STRING_BUFFER_LEN];incoming_pong.frame_id.data = incoming_pong_buffer;incoming_pong.frame_id.capacity = STRING_BUFFER_LEN; device_id =rand();rclc_executor_spin(&executor);RCCHECK(rcl_publisher_fini(&ping_publisher,&node));RCCHECK(rcl_publisher_fini(&pong_publisher,&node));RCCHECK(rcl_subscription_fini(&ping_subscriber,&node));RCCHECK(rcl_subscription_fini(&pong_subscriber,&node));RCCHECK(rcl_node_fini(&node));}
Building the firmware
Once the app has been created, the build step is in order. Notice that, with respect to the four-steps workflow delined above, we would expect a configuration step to happen before building the app. However, given that we are compiling micro-ROS in the host machine rather than in a board, the cross-compilation implemented by the configuration step is not required in this case. We can therefore proceed to build the firmware and source the local installation:
# Build step
ros2 run micro_ros_setup build_firmware.sh
source install/local_setup.bash
Creating the micro-ROS agent
The micro-ROS app is now ready to be connected to a micro-ROS agent to start talking with the rest of the ROS 2 world. To do that, let’s first of all create a micro-ROS agent:
# Download micro-ROS-Agent packages
ros2 run micro_ros_setup create_agent_ws.sh
Now, let’s build the agent packages and, when this is done, source the installation:
# Build step
ros2 run micro_ros_setup build_agent.sh
source install/local_setup.bash
Running the micro-ROS app
At this point, you have both the client and the agent correctly installed in your host machine.
To give micro-ROS access to the ROS 2 dataspace, run the agent:
# Run a micro-ROS agent
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888
And then, in another command line, run the micro-ROS node (remember sourcing the ROS 2 and micro-ROS installations, and setting the RMW Micro XRCE-DDS implementation):
source /opt/ros/$ROS_DISTRO/setup.bash
source install/local_setup.bash
# Use RMW Micro XRCE-DDS implementation
export RMW_IMPLEMENTATION=rmw_microxrcedds
# Run a micro-ROS node
ros2 run micro_ros_demos_rclc ping_pong
Output
Testing the micro-ROS app
Now, we want to check that everything is working.
Open a new command line. We are going to listen to the ping topic with ROS 2 to check whether the micro-ROS Ping Pong node is correctly publishing the expected pings: