ROS2——什么是接口

接口简介

在机器人系统中, 往往有许许多多的硬件, 比如摄像头, 激光雷达, 每一个硬件发送的数据/数据类型是不同的, 那么ROS2是如何使用这些数据的呢?

前文KFCHamburger中我们使用过这些定义:

1
2
3
4
5
#include "std_msgs/msg/string.hpp"
#include "std_msgs/msg/u_int32.hpp"
...
<std_msgs::msg::String>
<std_msgs::msg::UInt32>

这里的std_msgs是ROS2内置的一个接口功能包.

查看某一个接口包下所有的接口:

1
ros2 interface package std_msgs

ros2-interface-package-std_msgs

当然, 这些内置接口也许不能满足你的需求, 此时就可以自定义接口.

自定义接口

针对ROS2的通信方式, 接口可以分为:

  • 话题接口

    文件名: *.msg

    1
    int64 num

    This is your custom message that transfers a single 64-bit integer called num.

  • 服务接口

    文件名:*.srv

    1
    2
    3
    4
    5
    int64 a
    int64 b
    int64 c
    ---
    int64 sum

    This is your custom service that requests three integers named a, b, and c, and responds with an integer called sum.

  • 动作接口

    文件名:*.action

    1
    2
    3
    4
    5
    int32 order
    ---
    int32[] sequence
    ---
    int32[] partial_sequence

自定义话题接口

前情提要: 前面的KFC实例中, KFC会定时发送一条广告话题, 如果要发送图片信息怎么办? 这就需要自定义KFC专属消息接口.

编辑.msg文件

cd到工作空间的/src文件夹下, 新建接口包

1
2
cd ros2_ws/src
ros2 pkg create topic_interfaces --build-type ament_cmake

cd进入topic_interfaces文件夹, 新建KFC.msg文件(首字母要求大写)

1
2
mkdir msg
touch msg/KFC.msg

编辑KFC.msg

1
2
3
4
5
6
7
8
# 原始数据类型string
string txt

# 原始数据类型 uint32
uint32 money

# 图像消息,调用sensor_msgs下的Image类型
sensor_msgs/Image image

修改Cmakelist.txt

注意:rosidl_generate_interfaces()必须在 ament_package()

1
2
3
4
5
6
7
8
9
# 这两句添加依赖
find_package(sensor_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

# 声明msg文件所属的工程名字, 文件位置, 依赖DEPENDENCIES
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Ad.msg"
DEPENDENCIES sensor_msgs
)

修改package.xml

1
2
3
4
5
<!-- 添加如下内容 -->
<depend>sensor_msgs</depend>
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

编译并刷新环境变量

1
2
colcon build --packages-select topic_interfaces
source ~/.bashrc

使用自定义话题接口修改KFC与Customer

上代码! 请读者自行对比区别, // CHANGE为修改的地方

新建文件Customer_with_interfaces.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "rclcpp/rclcpp.hpp"
// 这个头文件是topic_interfaces接口包编译后自动生成的
#include "topic_interfaces/msg/kfc.hpp" // CHANGE

using namespace std::chrono_literals;

using std::placeholders::_1;

class CustomerNode : public rclcpp::Node
{
public:
CustomerNode(std::string name) : Node(name)
{
RCLCPP_INFO(this->get_logger(), "大家好,我是一个%s.",name.c_str());

sub_hamburger = this->create_subscription<topic_interfaces::msg::KFC>("hamburger", 10, std::bind(&CustomerNode::hamburger_callback, this, _1)); // CHANGE

sub_advertisement = this->create_subscription<topic_interfaces::msg::KFC>("advertisement", 10, std::bind(&CustomerNode::advertisement_callback, this, _1)); // CHANGE

hungry_timer = this->create_wall_timer(1000ms, std::bind(&CustomerNode::hungry_timer_callback, this));

pub_money = this->create_publisher<topic_interfaces::msg::KFC>("money_of_hamburger", 10); // CHANGE

money.money = 10; // CHANGE

pub_money->publish(money);
RCLCPP_INFO(this->get_logger(), "我饿了, 我要吃汉堡! 付款 %d 元", money.money); // CHANGE

}
private:
topic_interfaces::msg::KFC money; // CHANGE

rclcpp::TimerBase::SharedPtr hungry_timer;

rclcpp::Subscription<topic_interfaces::msg::KFC>::SharedPtr sub_hamburger; // CHANGE

rclcpp::Publisher<topic_interfaces::msg::KFC>::SharedPtr pub_money; // CHANGE

rclcpp::Subscription<topic_interfaces::msg::KFC>::SharedPtr sub_advertisement; // CHANGE

void hamburger_callback(const topic_interfaces::msg::KFC::SharedPtr msg) // CHANGE
{
RCLCPP_INFO(this->get_logger(), "这是我吃的 %s ", msg->txt.c_str()); // CHANGE
}

void hungry_timer_callback()
{
RCLCPP_INFO(this->get_logger(), "我又饿了, 还想再吃一个! 付款 %d 元", money.money); // CHANGE
pub_money->publish(money);
}

void advertisement_callback(const topic_interfaces::msg::KFC::SharedPtr msg) // CHANGE
{
RCLCPP_INFO(this->get_logger(), "我收到了一条广告: %s ", msg->txt.c_str()); // CHANGE
}
};

int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<CustomerNode>("Customer");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}

新建文件KFC_with_interfaces.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "rclcpp/rclcpp.hpp"
#include "topic_interfaces/msg/kfc.hpp" // CHANGE

using namespace std::chrono_literals;

using std::placeholders::_1;

class KFCNode : public rclcpp::Node
{
public:
KFCNode(std::string name) : Node(name), count(1)
{
RCLCPP_INFO(this->get_logger(), "大家好, 我是%s的服务员.",name.c_str());

pub_hamburger = this->create_publisher<topic_interfaces::msg::KFC>("hamburger", 10); // CHANGE

pub_advertisement = this->create_publisher<topic_interfaces::msg::KFC>("advertisement", 10); // CHANGE

advertisement_timer = this->create_wall_timer(5000ms, std::bind(&KFCNode::advertisement_timer_callback, this));

sub_money = this->create_subscription<topic_interfaces::msg::KFC>("money_of_hamburger", 10, std::bind(&KFCNode::money_callback, this, _1)); // CHANGE
}
private:
size_t count;

rclcpp::TimerBase::SharedPtr advertisement_timer;

rclcpp::Publisher<topic_interfaces::msg::KFC>::SharedPtr pub_hamburger; // CHANGE

rclcpp::Subscription<topic_interfaces::msg::KFC>::SharedPtr sub_money; // CHANGE

rclcpp::Publisher<topic_interfaces::msg::KFC>::SharedPtr pub_advertisement; // CHANGE

void advertisement_timer_callback()
{
auto str_advertisement = topic_interfaces::msg::KFC(); // CHANGE
str_advertisement.txt = "大鸡腿降价啦"; // CHANGE
RCLCPP_INFO(this->get_logger(), "KFC发布了一个广告:%s", str_advertisement.txt.c_str()); // CHANGE
pub_advertisement->publish(str_advertisement);
}

void money_callback(const topic_interfaces::msg::KFC::SharedPtr msg) // CHANGE
{
if(msg->money == 10) // CHANGE
{
RCLCPP_INFO(this->get_logger(), "收款 %d 元", msg->money); // CHANGE

auto str_hamburger_num = topic_interfaces::msg::KFC(); // CHANGE
str_hamburger_num.txt = "第" + std::to_string(count++) + "个汉堡"; // CHANGE
RCLCPP_INFO(this->get_logger(), "这是我卖出的%s", str_hamburger_num.txt.c_str()); // CHANGE

pub_hamburger->publish(str_hamburger_num);
}

}
};

int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<KFCNode>("KFC");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}

修改Cmakelist.txt, 添加/修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
find_package(topic_interfaces REQUIRED)

add_executable(Customer_with_interfaces_node src/Customer_with_interfaces.cpp)
ament_target_dependencies(Customer_with_interfaces_node rclcpp topic_interfaces)

add_executable(KFC_with_interfaces_node src/KFC_with_interfaces.cpp)
ament_target_dependencies(KFC_with_interfaces_node rclcpp topic_interfaces)

install(TARGETS
Customer_node
KFC_node
Customer_with_interfaces_node
KFC_with_interfaces_node
DESTINATION lib/${PROJECT_NAME}
)

修改package.xml, 添加:

1
<depend>topic_interfaces</depend>

编译并刷新环境

1
2
colcon build --packages-select customer_and_kfc
source ~/.bashrc

开启两个终端, 分别运行使用自定义接口的KFC与Customer

1
2
ros2 run customer_and_kfc Customer_with_interfaces_node
ros2 run customer_and_kfc KFC_with_interfaces_node

成功~

详细查看接口

要想详细查看接口, 可以使用ros2 interface命令

  1. 查看包下所有接口

    1
    ros2 interface package topic_interfaces

    ros2-interface-package-topic-interfaces

  2. 查看内容

    1
    ros2 interface show topic_interfaces/msg/Ad

    ros2-interface-show-topic-interfaces-msg-Ad

  3. 显示属性

    1
    ros2 interface proto topic_interfaces/msg/Ad

    ros2-interface-proto-topic-interfaces-msg-Ad

自定义服务接口

自定义服务接口将在下一篇文章讲解服务时一起讲解.