官方文档的示例可以在这里找到 Programming with rcl and rclc

这篇博客主要讲述 micro-ROS C API,也就是 rclc 的一些基础用法。它的主要理念与 ROS2 相同(publishers, subscriptions, services, timers, …),它们甚至依赖于相同的实现,因为 micro-ROS C API 基于 ROS 2客户端支持库(rcl),并在软件包 rclc 中添加了一些便利的功能。也就是说,rclc 没有在 rcl 之上添加新的类型层(rclcpp 和 rclpy),而只提供简化 rcl 类型编程的功能。仅针对 rcl 中缺失的概念引入新类型,例如 executor 的概念。

Node

ROS 2节点是 ROS 2生态系统的主要参与者。节点将使用 publishers, subscriptions, services 等相互沟通。它们之间的通信可以用下面的动图表示,该动图也是 ROS2 官方文档中对 node 的描述。

node gif

对于节点的操作主要有节点的初始化、节点的清除,以及节点的生命周期。

Initialization

创建默认配置的节点

// Initialize micro-ROS allocator
rcl_allocator_t allocator = rcl_get_default_allocator();

// Initialize support object
rclc_support_t support;
rcl_ret_t rc = rclc_support_init(&support, argc, argv, &allocator);

// Create node object
rcl_node_t node;
const char * node_name = "test_node";

// Node namespace (Can remain empty "")
const char * namespace = "test_namespace";

// Init default node
rc = rclc_node_init_default(&node, node_name, namespace, &support);
if (rc != RCL_RET_OK) {
  ... // Handle error
  return -1;
}



allocator 负责分配内存,也叫分配器,support 则是作为配置管理器来使用。


allocator 对象中包装了 micro-ROS 中使用的动态内存分配和重新分配方法

// Get micro-ROS default allocator
rcl_allocator_t allocator = rcl_get_default_allocator();

default allocator 内部结构

- allocate = wraps malloc()
- deallocate = wraps free()
- reallocate = wraps realloc()
- zero_allocate = wraps calloc()
- state = `NULL`



在嵌入式系统中工作,用户可能需要使用自己的内存分配方法修改此默认函数。用户可以使用自己的方法修改默认分配器,如果有需要,可以参考官方的说明 Custom allocator


support 的结构体定义如下

typedef struct
{
    rcl_context_t context;
    rcl_allocator_t * allocator;
    rcl_clock_t clock;
} rclc_support_t;

可以看出来,support 包含了 allocator 的指针、用于创建一些顶层实体(如 node)的 context ,以及对时间相关资源的封装 clock

创建自定义配置的节点

如果是 Foxy 版本镜像,可以使用 rcl_node_options_t 函数配置节点

// Initialize allocator and support objects
...

// Create node object
rcl_node_t node;
const char * node_name = "test_node";

// Node namespace (Can remain empty "")
const char * namespace = "test_namespace";

// Get default node options and modify them
rcl_node_options_t node_ops = rcl_node_get_default_options();

// Set node ROS domain ID to 10
node_ops.domain_id = (size_t)(10);

// Init node with custom options
rc = rclc_node_init_with_options(&node, node_name, namespace, &support, &node_ops);

if (rc != RCL_RET_OK) {
  ... // Handle error
  return -1;
}



如果你使用的是 Galactic 或者更高的版本,在这种情况下,节点选项使用自定义 API 在 rclc_support_t 对象上配置

// Initialize micro-ROS allocator
rcl_allocator_t allocator = rcl_get_default_allocator();

// Initialize and modify options (Set DOMAIN ID to 10)
rcl_init_options_t init_options = rcl_get_zero_initialized_init_options();
rcl_init_options_init(&init_options, allocator);
rcl_init_options_set_domain_id(&init_options, 10);

// Initialize rclc support object with custom options
rclc_support_t support;
rclc_support_init_with_options(&support, 0, NULL, &init_options, &allocator);

// Create node object
rcl_node_t node;
const char * node_name = "test_node";

// Node namespace (Can remain empty "")
const char * namespace = "test_namespace";

// Init node with configured support object
rclc_node_init_default(&node, node_name, namespace, &support);

if (rc != RCL_RET_OK) {
  ... // Handle error
  return -1;
}

上面是不同版本中设置 ROS 的 Domain ID 的示例,使用不同的 Domain ID ,可以使 micro-ROS 加入不同的 DDS 域,之后会在 ROS2 机器人通讯原理详细讲解。

Cleaning Up

要销毁初始化节点,必须在节点本身之前销毁节点拥有的所有实体(Publishers, subscribers, services, ……)

// Destroy created entities (Example)
rcl_publisher_fini(&publisher, &node);
...

// Destroy the node
rcl_node_fini(&node);

这将从 ROS2 图中删除节点,包括 agent 上生成的任何实体和客户端上使用的内存。

Lifecycle

rclc 生命周期包在 C 中提供了方便的函数,将 rcl 节点与 ROS 2 节点生命周期状态机捆绑在一起,类似于 C++的 rclcpp 生命周期节点。Node 的生命周期的状态机图如下,更多可以参考 Node Lifecycle
Node Lifecycle

绑定 lifecycle 节点

创建 lifecycle 节点作为 rcl 节点和 rcl 生命周期状态机的一个绑定。假设有一个已经初始化的 node 和 executor

// Create rcl state machine
rcl_lifecycle_state_machine_t state_machine =
rcl_lifecycle_get_zero_initialized_state_machine();

// Create the lifecycle node
rclc_lifecycle_node_t lifecycle_node;
rcl_ret_t rc = rclc_make_node_a_lifecycle_node(
  &lifecycle_node,
  &node,
  &state_machine,
  &allocator);

注册 lifecycle 服务

// Register lifecycle services on the allocator
rclc_lifecycle_add_get_state_server(&lifecycle_node, &executor);
rclc_lifecycle_add_get_available_states_server(&lifecycle_node, &executor);
rclc_lifecycle_add_change_state_server(&lifecycle_node, &executor);

注册服务时,同时需要为服务添加处理函数

注册 lifecycle 回调函数

可以添加回调函数在状态发生改变时进行调用,示例如下:
首先定义一个回调函数,以 configure 状态为例

rcl_ret_t my_on_configure() {
  printf("  >>> my_lifecycle_node: on_configure() callback called.\n");
  return RCL_RET_OK;
}

之后将它添加到 lifecycle 节点

// Register lifecycle service callbacks
rclc_lifecycle_register_on_configure(&lifecycle_node, &my_on_configure);
rclc_lifecycle_register_on_activate(&lifecycle_node, &my_on_activate);
rclc_lifecycle_register_on_deactivate(&lifecycle_node, &my_on_deactivate);
rclc_lifecycle_register_on_cleanup(&lifecycle_node, &my_on_cleanup);

改变 lifecycle 状态

如果要改变 lifecycle 节点的状态,则使用下面的操作

bool publish_transition = true;
rc += rclc_lifecycle_change_state(
  &lifecycle_node,
  lifecycle_msgs__msg__Transition__TRANSITION_CONFIGURE,
  publish_transition);

rc += rclc_lifecycle_change_state(
  &lifecycle_node,
  lifecycle_msgs__msg__Transition__TRANSITION_ACTIVATE,
  publish_transition);

清除 lifecycle

下面函数可以直接清除 lifecycle 节点上注册的所有服务和回调函数。

rc += rcl_lifecycle_node_fini(&lifecycle_node, &allocator);

生命周期服务还不能通过 ros2 lifecycle调用



比如 ros2 lifecycle set /node ...,只能够使用 ros2 service CLI 进行调用
eg:ros2 service call /node/change_state lifecycle_msgs/ChangeState "{transition: {id: 1, label: configure}}"