上一篇已经讲述了如何创建一个micro-ROS节点,那么之后便是需要使用Topic、Service等进行节点间的通讯。下面将会对Publisher- Subscriber,Server-Client的详细用法进行讲解。
Publisher
若想使用 Publisher 进行通信,那么需要进行初始化 Publisher,之后便可以使用 Publisher 进行消息的发送。使用 Publisher 进行话题的发送的步骤如下:
创建并初始化 Node -> 创建并初始化 Publisher(包括消息格式、话题名、绑定节点) -> 发送消息
Initialization
以下示例代码的前提是已经有初始化 rcl 并创建 micro-ROS Node 的代码(Node 篇),根据所需的 QoS(Quantity of Service 服务质量)配置,有三种方法初始化发布服务器:
- Reliable (default):
// Publisher对象
rcl_publisher_t publisher;
const char * topic_name = "test_topic";
// 创建消息格式支持
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32);
// 在node上创建并初始化一个reliable的publisher
rcl_ret_t rc = rclc_publisher_init_default(
&publisher, &node,
type_support, topic_name);
if (RCL_RET_OK != rc) {
... // 错误处理
return -1;
}
- Best effort:
// Publisher对象
rcl_publisher_t publisher;
const char * topic_name = "test_topic";
// 创建消息格式支持
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32);
// 在node上创建并初始化一个best effort的publisher
rcl_ret_t rc = rclc_publisher_init_best_effort(
&publisher, &node,
type_support, topic_name);
if (RCL_RET_OK != rc) {
... // 错误处理
return -1;
}
- Custom QoS:
// Publisher对象
rcl_publisher_t publisher;
const char * topic_name = "test_topic";
// 创建消息格式支持
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32);
// 设置publisher QoS
const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default;
// Creates a rcl publisher with customized quality-of-service options
rcl_ret_t rc = rclc_publisher_init(
&publisher, &node,
type_support, topic_name, qos_profile);
if (RCL_RET_OK != rc) {
... // 错误处理
return -1;
}
发布消息
若想发送消息,需要使用上面创建的 publisher
// Int32 消息对象
std_msgs__msg__Int32 msg;
// 设置消息对象的内容
msg.data = 0;
// 发布消息
rcl_ret_t rc = rcl_publish(&publisher, &msg, NULL);
if (rc != RCL_RET_OK) {
... // 错误处理
return -1;
}
rcl_publish 是线程安全的,可以从多个线程调用
Cleaning Up
// Destroy publisher
rcl_publisher_fini(&publisher, &node);
example
利用 Node 篇的 Lifecycle 回调函数并使用 publisher 发送消息
Subscriber
已经学会了发布消息,那么接下来就学习如何使用 Subscriber 订阅消息。使用 Subscriber 订阅消息在创建并初始化阶段与 Publisher 相同。
Initialization
根据 QoS,同样有三种初始化方法:
- Reliable (default):
// Subscription object
rcl_subscription_t subscriber;
const char * topic_name = "test_topic";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32);
// Initialize a reliable subscriber
rcl_ret_t rc = rclc_subscription_init_default(
&subscriber, &node,
type_support, topic_name);
if (RCL_RET_OK != rc) {
... // Handle error
return -1;
}
- Best effort:
// Subscription object
rcl_subscription_t subscriber;
const char * topic_name = "test_topic";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32);
// Initialize best effort subscriber
rcl_ret_t rc = rclc_subscription_init_best_effort(
&subscriber, &node,
type_support, topic_name);
if (RCL_RET_OK != rc) {
... // Handle error
return -1;
}
- Custom QoS:
// Subscription object
rcl_subscription_t subscriber;
const char * topic_name = "test_topic";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32);
// Set client QoS
const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default;
// Initialize a subscriber with customized quality-of-service options
rcl_ret_t rc = rclc_subscription_init(
&subscriber, &node,
type_support, topic_name, qos_profile);
if (RCL_RET_OK != rc) {
... // Handle error
return -1;
}
订阅消息
Subscriber 订阅消息需要在 executor 上配置一个订阅回调函数,该函数参数只有一个,那就是订阅的话题的消息类型,并且包含 publisher 所发送的信息内容。
定义订阅回调函数
// Function prototype:
void (* rclc_subscription_callback_t)(const void *);
// Implementation example:
void subscription_callback(const void * msgin)
{
// Cast received message to used type
const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin;
// Process message
printf("Received: %d\n", msg->data);
}
在已经初始化 executor 和 subscriber 后,将回调配置到 executor 中。在 executor spin 后就会进行订阅。
// Message object to receive publisher data
std_msgs__msg__Int32 msg;
// Add subscription to the executor
rcl_ret_t rc = rclc_executor_add_subscription(
&executor, &subscriber, &msg,
&subscription_callback, ON_NEW_DATA);
if (RCL_RET_OK != rc) {
... // Handle error
return -1;
}
// Spin executor to receive messages
rclc_executor_spin(&executor);
Cleaning Up
// Destroy subscriber
rcl_subscription_fini(&subscriber, &node);
example
使用 Subscriber 订阅一个控制灯光的 topic,通过 topic 控制灯光的亮灭
订阅 /led_topic
话题,若接受到数字 0 则关闭板载 led 灯,若接受到数字 1 则打开 led 灯。
#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}}
void error_loop() {
while(1) {
delay(100);
}
}
#include <Arduino.h>
#include <micro_ros_platformio.h>
#include <WiFi.h>
#include <rcl/rcl.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <std_msgs/msg/int32.h>
rclc_executor_t executor;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;
std_msgs__msg__Int32 msg;
rcl_subscription_t subscriber;
void subscription_callback(const void * msgin)
{
const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin;
Serial.printf("Received: %d\n", msg->data);
if(msg->data == 1) {
digitalWrite(2, HIGH);
} else digitalWrite(2, LOW);
}
void setup()
{
Serial.begin(115200);
// Set WiFi
IPAddress agent_ip;
agent_ip.fromString("IP");
set_microros_wifi_transports("SSID", "PASSWD", agent_ip, PORT);
delay(2000);
allocator = rcl_get_default_allocator();
RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));
RCCHECK(rclc_node_init_default(&node, "Test_Node", "", &support));
RCCHECK(rclc_executor_init(&executor, &support.context, 1, &allocator));
// Initialize a reliable subscriber
RCCHECK(rclc_subscription_init_default(
&subscriber,
&node,
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
"led_topic"));
// Add subscriber to executor
RCCHECK(rclc_executor_add_subscription(
&executor,
&subscriber,
&msg,
&subscription_callback,
ON_NEW_DATA));
pinMode(2, OUTPUT);
}
void loop()
{
delay(100);
RCCHECK(rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100)));
}
注意:
所有的教程中都是直接在声明并初始化,而上述例子中,node、executor、support 等都是在函数外声明的。
这是因为若在 Arduino 的 setup 函数直接声明则是局部变量,在 loop 函数中执行时就会发生错误
Services
介绍完使用 Topic 进行通信,接下来介绍如何使用 Service 来进行节点间通信。C/S 架构由 Client 和 Server 组成。所以我们也需要对 Server 和 Client 分别进行学习。
Server
一样是由初始化开始,Server 负责对 Client 的请求进行处理,并将结果返回给 Client。
Initialization
根据 QoS 由三种初始化方法
- Reliable (default):
// Service server object
rcl_service_t service;
const char * service_name = "/addtwoints";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts);
// Initialize server with default configuration
rcl_ret_t rc = rclc_service_init_default(
&service, &node,
type_support, service_name);
if (rc != RCL_RET_OK) {
... // Handle error
return -1;
}
- Best effort:
// Service server object
rcl_service_t service;
const char * service_name = "/addtwoints";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts);
// Initialize server with default configuration
rcl_ret_t rc = rclc_service_init_best_effort(
&service, &node,
type_support, service_name);
if (rc != RCL_RET_OK) {
... // Handle error
return -1;
}
- Custom QoS:
// Service server object
rcl_service_t service;
const char * service_name = "/addtwoints";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts);
// Set service QoS
const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_services_default;
// Initialize server with customized quality-of-service options
rcl_ret_t rc = rclc_service_init(
&service, &node, type_support,
service_name, qos_profile);
if (rc != RCL_RET_OK) {
... // Handle error
return -1;
}
之后便是需要注册 Server 的回调函数,用于处理 Client 发送的请求
Callback
当请求发送到 Server 时,executor 将会调用已经注册的回调函数,其参数包含 request 和 response 的消息指针。request_msg 包含 Client 发送的消息,response_msg 则需要在函数里进行修改赋值,在函数结束后就会将消息发送给请求的 Client。
使用 AddTwoInts.srv
消息类型进行讲解
int64 a
int64 b
---
int64 sum
Client 的请求包含 a,b 两个值,sum 是 Server 接受到请求处理后返回的响应结果。
// Function prototype:
void (* rclc_service_callback_t)(const void *, void *);
// Implementation example:
void service_callback(const void * request_msg, void * response_msg){
// Cast messages to expected types
example_interfaces__srv__AddTwoInts_Request * req_in =
(example_interfaces__srv__AddTwoInts_Request *) request_msg;
example_interfaces__srv__AddTwoInts_Response * res_in =
(example_interfaces__srv__AddTwoInts_Response *) response_msg;
// Handle request message and set the response message values
printf("Client requested sum of %d and %d.\n", (int) req_in->a, (int) req_in->b);
res_in->sum = req_in->a + req_in->b;
}
回调函数定义之后,便需要将回调函数进行注册,需要在 executor 中进行添加注册。下面的示例代码中的一些对象的创建在上一步已经完成。
// Service message objects
example_interfaces__srv__AddTwoInts_Response response_msg;
example_interfaces__srv__AddTwoInts_Request request_msg;
// Add server callback to the executor
rc = rclc_executor_add_service(
&executor, &service, &request_msg,
&response_msg, service_callback);
if (rc != RCL_RET_OK) {
... // Handle error
return -1;
}
// Spin executor to receive requests
rclc_executor_spin(&executor);
Client
Client 用于向 Server 发送 request。Server 也会根据 request 返回 response。
Initialization
- Reliable (default):
// Service client object
rcl_client_t client;
const char * service_name = "/addtwoints";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts);
// Initialize client with default configuration
rcl_ret_t rc = rclc_client_init_default(
&client, &node,
type_support, service_name);
if (rc != RCL_RET_OK) {
... // Handle error
return -1;
}
- Best effort:
// Service client object
rcl_client_t client;
const char * service_name = "/addtwoints";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts);
// Initialize client with default configuration
rcl_ret_t rc = rclc_client_init_best_effort(
&client, &node,
type_support, service_name);
if (rc != RCL_RET_OK) {
... // Handle error
return -1;
}
- Custom QoS:
// Service client object
rcl_client_t client;
const char * service_name = "/addtwoints";
// Get message type support
const rosidl_message_type_support_t * type_support =
ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts);
// Set client QoS
const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_services_default;
// Initialize server with customized quality-of-service options
rcl_ret_t rc = rclc_client_init(
&client, &node, type_support,
service_name, qos_profile);
if (rc != RCL_RET_OK) {
... // Handle error
return -1;
}
Client 相比 Server,需要主动发送一个请求到 Server。所以 Client 具有一个发送消息的功能,同时还需要具有接收到 Server 发送的 response 并进行处理的回调功能。
发送 Request
使用 AddTwoInts.srv
消息格式向 Server 发送一个请求。rcl_send_request
函数用于发送请求
// Request message object (Must match initialized client type support)
example_interfaces__srv__AddTwoInts_Request request_msg;
// Initialize request message memory and set its values
example_interfaces__srv__AddTwoInts_Request__init(&request_msg);
request_msg.a = 24;
request_msg.b = 42;
// Sequence number of the request (Populated in rcl_send_request)
int64_t sequence_number;
// Send request
rcl_send_request(&client, &request_msg, &sequence_number);
// Spin the executor to get the response
rclc_executor_spin(&executor);
Callback
定义回调函数,函数参数为 response_msg
的指针
// Function prototype:
void (* rclc_client_callback_t)(const void *);
// Implementation example:
void client_callback(const void * response_msg){
// Cast response message to expected type
example_interfaces__srv__AddTwoInts_Response * msgin =
(example_interfaces__srv__AddTwoInts_Response * ) response_msg;
// Handle response message
printf("Received service response %ld + %ld = %ld\n", req.a, req.b, msgin->sum);
}
将回调函数添加到 executor 中
// Response message object
example_interfaces__srv__AddTwoInts_Response res;
// Add client callback to the executor
rcl_ret_t rc = rclc_executor_add_client(&executor, &client, &res, client_callback);
if (rc != RCL_RET_OK) {
... // Handle error
return -1;
}
// Spin executor to receive requests
rclc_executor_spin(&executor);
Cleaning Up
如果想删除已经注册的 server 和 client,执行下面函数
// Destroy service server and client
rcl_service_fini(&service, &node);
rcl_client_fini(&client, &node);
这将删除 agent 上任何自动创建的运行结构,并在客户端释放已使用的内存。
评论(0)
您还未登录,请登录后发表或查看评论