6.1. Getting started micro-ROS¶
Table of Contents
6.1.1. Overview¶
This tutorial provides step-by-step instructions to use Vulcanexus micro tools to build and execute a micro-ROS publisher/subscriber demo.
6.1.2. Prerequisites¶
Ensure that the Vulcanexus installation includes Vulcanexus micro (either vulcanexus-humble-desktop
, vulcanexus-humble-micro
, or vulcanexus-humble-base
).
Also, remember to source the environment in every terminal in this tutorial.
source /opt/vulcanexus/humble/setup.bash
6.1.3. Create a micro-ROS workspace¶
A workspace is a directory containing ROS 2 packages. Before using ROS 2, it’s necessary to source your ROS 2 installation workspace in the terminal you plan to work in. This makes ROS 2’s packages available for you to use in that terminal.
source /opt/vulcanexus/humble/setup.bash
# Best practice is to create a new directory for every new workspace.
mkdir -p ~/microros_ws/src
# Another best practice is to put any packages in your workspace into the src directory.
cd ~/microros_ws/src
6.1.3.1. Build micro-ROS library¶
The first step is to download and build micro-ROS sources into the created workspace. Vulcanexus micro includes the micro_ros_setup
tool, which will handle all the needed steps.
Let’s begin using the create_firmware_ws.sh
command with host
as target:
# Create firmware step.
ros2 run micro_ros_setup create_firmware_ws.sh host
This command will download all needed sources and tools to build micro-ROS as a library. A folder named firmware must be present in your workspace, which will old platform specific tools and source code:
~/microros_ws/src/
├── firmware
│ └── dev_ws
└── src
├── eProsima
│ ├── Micro-CDR
│ └── Micro-XRCE-DDS-Client
├── ros2
│ ├── common_interfaces
│ ├── example_interfaces
│ ├── rcl_interfaces
│ └── unique_identifier_msgs
└── uros
├── micro-ROS-demos
├── micro_ros_msgs
├── micro_ros_utilities
├── rclc
├── rmw_microxrcedds
└── rosidl_typesupport_microxrcedds
As there is no need for extra configuration steps usually needed for cross-compilation or RTOS configuration, let’s build:
# Build step.
ros2 run micro_ros_setup build_firmware.sh
This completes the micro-ROS library build. Remember sourcing this workspace before building or running a micro-ROS app.
# Source micro-ROS installation.
source install/local_setup.bash
Note
A set of examples apps for Linux is also present and build under the src/uros/micro-ROS-demos/rclc
directory.
6.1.3.2. Create a package¶
Recall that packages should be created in the src
directory, not the root of the workspace. So, navigate into microros_ws/src, and run the package creation command:
ros2 pkg create --build-type ament_cmake micro_pubsub
micro_pubsub/
├── CMakeLists.txt
├── package.xml
├── include
│ └── micro_pubsub
└── src
6.1.4. Add micro-ROS apps to the workspace¶
After the workspace is ready and sourced, micro-ROS applications can be added, build and run.
Note
Certain topics as message memory handling or micro-ROS API details are not covered on this tutorial. Check the micro-ROS User API tutorial for more details.
6.1.4.1. Write the micro-ROS publisher¶
On this example app, a publisher will send a periodic string message using a configurable timer.
Add the publisher source code on micro_pubsub/src/micro_publisher.c
:
// C standard includes.
#include <stdio.h>
#include <unistd.h>
// micro-ROS general includes with general functionality.
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
// Include subscriber message type.
#include <std_msgs/msg/string.h>
// Macros to handle micro-ROS return codes, error handling should be customized for the target system.
#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); }}
// Set maximum publisher string length.
#define STRING_LEN 200
// The publisher and string message objects are declared as global so they are available on `timer_callback`.
rcl_publisher_t publisher;
std_msgs__msg__String msg;
int counter = 0;
// The ``timer_callback`` function will be in charge of publishing and increment the message data on each timer period.
void timer_callback(rcl_timer_t * timer, int64_t last_call_time)
{
(void) last_call_time;
if (timer != NULL)
{
// Update string message with new value and size before its published.
sprintf(msg.data.data, "Hello from micro-ROS #%d", counter++);
msg.data.size = strlen(msg.data.data);
RCSOFTCHECK(rcl_publish(&publisher, &msg, NULL));
printf("Publishing: \"%s\"\n", msg.data.data);
}
}
int main()
{
// Get configured allocator.
rcl_allocator_t allocator = rcl_get_default_allocator();
// Initialize support object.
rclc_support_t support;
RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));
// Node name and namespace.
const char * node_name = "minimal_micro_publisher";
const char * node_namespace = "";
// Create node with default configuration.
rcl_node_t node;
RCCHECK(rclc_node_init_default(&node, node_name, node_namespace, &support));
// Topic name and type support.
const char * topic_name = "topic";
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String);
// Create publisher.
RCCHECK(rclc_publisher_init_default(
&publisher,
&node,
type_support,
topic_name));
// Initialize publisher message memory.
char string_memory[STRING_LEN];
msg.data.data = &string_memory[0];
msg.data.size = 0;
msg.data.capacity = STRING_LEN;
// Create timer with its callback and its trigger period.
rcl_timer_t timer;
const unsigned int timer_period = 500;
RCCHECK(rclc_timer_init_default(
&timer,
&support,
RCL_MS_TO_NS(timer_period),
timer_callback));
// Initialize executor with one handle.
rclc_executor_t executor;
const size_t number_of_handles = 1;
RCCHECK(rclc_executor_init(&executor, &support.context, number_of_handles, &allocator));
// Add timer callback to the executor.
RCCHECK(rclc_executor_add_timer(&executor, &timer));
// Spin forever
RCSOFTCHECK(rclc_executor_spin(&executor));
// Free used resources on spin error and exit.
// This methods will free used memory even if connection with the Agent is lost.
RCSOFTCHECK(rclc_executor_fini(&executor));
RCSOFTCHECK(rcl_timer_fini(&timer));
RCSOFTCHECK(rcl_publisher_fini(&publisher, &node));
RCSOFTCHECK(rcl_node_fini(&node));
RCSOFTCHECK(rclc_support_fini(&support));
return 1;
}
6.1.4.2. Write the micro-ROS subscriber¶
This subscriber example is very similar to the publisher, but here the message reception will be handle by the subscriber callback if there is enough memory available.
Add the subscriber source code on micro_pubsub/src/micro_subscriber.c
:
// C standard includes.
#include <stdio.h>
#include <unistd.h>
// micro-ROS general includes with general functionality.
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
// Include subscriber message type.
#include <std_msgs/msg/string.h>
// Macros to handle micro-ROS return codes, error handling should be customized for the target system.
#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); }}
// Set maximum received string length.
// Received publications with strings larger than this size will be discarded.
#define STRING_LEN 200
// Callback to handle incoming subscriber messages.
void subscription_callback(const void * msgin)
{
// Message type shall be casted to expected type from void pointer.
const std_msgs__msg__String * msg = (const std_msgs__msg__String *)msgin;
printf("I heard: \"%s\"\n", msg->data.data);
}
int main()
{
// Get configured allocator.
rcl_allocator_t allocator = rcl_get_default_allocator();
// Initialize support object.
rclc_support_t support;
RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));
// Node name and namespace.
const char * node_name = "minimal_micro_subscriber";
const char * node_namespace = "";
// Create node.
rcl_node_t node;
RCCHECK(rclc_node_init_default(&node, node_name, node_namespace, &support));
// Topic name and message type support.
const char * topic_name = "topic";
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String);
// Create subscriber.
rcl_subscription_t subscriber;
RCCHECK(rclc_subscription_init_default(
&subscriber,
&node,
type_support,
topic_name));
// Initialize executor.
rclc_executor_t executor;
const size_t number_of_handles = 1;
RCCHECK(rclc_executor_init(&executor, &support.context, number_of_handles, &allocator));
// Add subscriber callback.
std_msgs__msg__String msg;
RCCHECK(rclc_executor_add_subscription(&executor, &subscriber, &msg, &subscription_callback, ON_NEW_DATA));
// Initialize subscriber message memory.
char string_memory[STRING_LEN];
msg.data.data = &string_memory[0];
msg.data.size = 0;
msg.data.capacity = STRING_LEN;
// Spin forever
RCSOFTCHECK(rclc_executor_spin(&executor));
// Free used resources on spin error and exit.
// This methods will free used memory even if connection with the Agent is lost.
RCSOFTCHECK(rclc_executor_fini(&executor));
RCSOFTCHECK(rcl_subscription_fini(&subscriber, &node));
RCSOFTCHECK(rcl_node_fini(&node));
RCSOFTCHECK(rclc_support_fini(&support));
return 1;
}
6.1.4.3. Add dependencies¶
Before building this examples, micro-ROS used headers and tools need to be included on the created ROS2 package:
Open
package.xml
onmicroros_ws/src/micro_pubsub
directorymake sure to fill in the
<description>
,<maintainer>
and<license>
tags:
<description>Examples of minimal publisher/subscriber using rclcpp</description> <maintainer email="you@email.com">Your Name</maintainer> <license>Apache License 2.0</license>
Add a new line after the
ament_cmake buildtool
dependency and paste the following dependencies corresponding to your node’s include statements:
<depend>rcl</depend> <depend>rclc</depend> <depend>std_msgs</depend> <depend>rmw_microxrcedds</depend>
Add the dependencies to the CMakeLists.txt file, the following example can be used:
cmake_minimum_required(VERSION 3.5) project(micro_pubsub) find_package(ament_cmake REQUIRED) find_package(rcl REQUIRED) find_package(rclc REQUIRED) find_package(std_msgs REQUIRED) find_package(rmw_microxrcedds REQUIRED) add_executable(publisher src/micro_publisher.c) ament_target_dependencies(publisher rcl rclc std_msgs rmw_microxrcedds) add_executable(subscriber src/micro_subscriber.c) ament_target_dependencies(subscriber rcl rclc std_msgs rmw_microxrcedds) install(TARGETS publisher subscriber DESTINATION lib/${PROJECT_NAME}) ament_package()
6.1.5. Build¶
Once the source code is ready, build your new package:
# ~/microros_ws
colcon build --packages-select micro_pubsub
6.1.6. Run¶
6.1.6.1. Start the Agent¶
The agent is included on Vulcanexus micro tool set. The default transport configuration for micro-ROS is UDPv4 with the Agent on 127.0.0.1:8888
:
# Start the agent
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888 -v5
This command will start the Agent, allowing communication between our micro-ROS apps and ROS2 environment.
Note
At the top of the apps main
function, micro-ROS is initialized using the rclc_support_init
method.
It is important that the Agent is reachable on this step, as the method will fail if a connection cannot be established.
6.1.6.2. Run the apps¶
Before running the apps, set Micro XRCE-DDS as RMW implementation for each terminal:
# Set Micro XRCE-DDS as RMW implementation
export RMW_IMPLEMENTATION=rmw_microxrcedds
Run the publisher in one terminal:
# ~/microros_ws
ros2 run micro_pubsub publisher
Then run listener in another terminal:
# ~/microros_ws
ros2 run micro_pubsub subscriber
The listener will start printing messages to the console. Notice how the creation of entities and each publication appears on the Agent log.