二、话题
1.概述
使用话题进行通信时,我们需要创建发布者
、订阅者
以及消息接口
。
打开终端,输入
ros2 interface list
此时,就会显示出ros2中已经定义好的接口,简单起见,我们现在只使用其中标准消息接口下的的String
。
可以看到,String位于std_msgs
包下,所以待会要导入这个包。
2. Python版本话题
2.1 创建发布者
(1)创建功能包
在src下,输入
ros2 pkg create topic_demo_py --build-type ament_python --dependencies rclpy std_msgs --license Apache-2.0
创建这个功能包相比之前讲节点时,仅仅多加了一个std_msgs依赖,事实上,在创建功能包时,你可以不写依赖,不过为了项目结构更清晰,建议加上,如果忘记写了,之后也可以在package.xml
文件里加上,如图
(2)编写代码
在src/topic_demo_py/topic_demo_py/
下创建py文件,然后输入代码
import rclpy
from rclpy.node import Node
from std_msgs.msg import String#导入消息接口
class PubNode(Node):#自定义一个节点
def __init__(self,node_name):
super().__init__(node_name)
#创建一个定时器,用于定时发布消息,的一个参数是间隔时间,单位是秒
#第二个参数是一个回调函数,用与周期性执行操作
self.timer = self.create_timer(1,self.timer_callback)
#创建发布者,第一个参数是信息接口,第二个参数是话题名字,第三个参数很久之后才会细讲,目前填个10先
self.publisher = self.create_publisher(String,'py_topic',10)
def timer_callback(self):
msg = String()#创建消息接口
msg.data = '你好'
self.publisher.publish(msg)#发布消息
def main():
rclpy.init()
node = PubNode('xxx')
rclpy.spin(node)
rclpy.shutdown()
这个节点会每隔一秒钟在这个话题中发一个你好
再附一张截图
2.2 创建订阅者
再创建一个py文件,输入代码
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SubNode(Node):
def __init__(self,node_name):
super().__init__(node_name)
#创建订阅者,第一个参数是消息接口,第二个参数是话题名字,第三个参数是回调函数,
#回调函数接收一个参数,即接收到的消息,第四个参数很久之后才会细讲,目前填个10先
self.subscriber = self.create_subscription(String,'py_topic',self.sub_callback,10)
def sub_callback(self,msg):
self.get_logger().info(msg.data)#打印接收到的消息
def main():
rclpy.init()
node = SubNode('yyy')
rclpy.spin(node)
rclpy.shutdown()
2.3 运行
接下来,注册节点,然后编译,运行,这些在之前讲节点时就已经讲过,就不再赘述了,不过还是附一张截图。
我们打开两个终端,一个运行发布者节点,另一个运行订阅者节点,注意两个终端都应该source一下,把可执行文件临时加入环境变量,方便运行。结果如图。
确实是每隔一秒钟接收到一次消息。
3. C++版本话题
换个编程语言,思路也是完全一样的,仅仅语法不同罢了。
(1)创建功能包
命令行输入
ros2 pkg create topic_demo_cpp --dependencies rclcpp std_msgs --license Apache-2.0
(2)编写代码
创建cpp文件,然后输入代码,创建发布者的节点。
#include <iostream>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std;
class PubNode:public rclcpp::Node{
private:
//定义发布者
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher;
//定时器,用于周期性向话题发布消息
rclcpp::TimerBase::SharedPtr timer;
public:
PubNode():Node("pubnode"){
//创建发布者,泛型类型指定为string,方法第一个参数为话题名字,第二个参数很久之后才会细讲,填10先
publisher = this->create_publisher<std_msgs::msg::String>("topic_cpp",10);
//创建定时器,第一个参数为间隔时间,第二个参数是一个回调函数,无参无返回值
timer = this->create_wall_timer(1000ms,[&](){
//创建消息
auto msg = std_msgs::msg::String();
//写入内容
msg.data = "pjp";
//发布消息
publisher->publish(msg);
});
}
};
int main(int argc,char** argv){
rclcpp::init(argc,argv);
auto node = make_shared<PubNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
再创建cpp文件,然后输入代码,创建订阅者的节点。
#include <iostream>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std;
class SubNode:public rclcpp::Node{
private:
//定义订阅者
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription;
public:
SubNode():Node("subnode"){
//创建订阅者,指定泛型类型为string,方法第一个参数是话题名称,第二个参数填个10先
//第三个参数是一个函数,这里用lambda,这个函数接受一个参数,即收到的消息
subscription = this->create_subscription<std_msgs::msg::String>("topic_cpp",10,
[&](std_msgs::msg::String msg){
string data= msg.data;
//打印INFO级别日志,这个c_str()函数用于将字符串转换为C风格的字符串
RCLCPP_INFO(get_logger(),"%s",data.c_str());
});
}
};
int main(int argc,char** argv){
rclcpp::init(argc,argv);
auto node = make_shared<SubNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
接下来就是编写CMakelists.txt
文件
add_executable(pubnode src/publisher.cpp)
add_executable(subnode src/subscriber.cpp)
ament_target_dependencies(pubnode rclcpp std_msgs)
ament_target_dependencies(subnode rclcpp std_msgs)
install(TARGETS pubnode subnode DESTINATION lib/${PROJECT_NAME})
还是放一张截图
(4)编译运行
通过上张截图,可以看到我们已经在src下创建了好几个功能包了,用colcon build
命令,会在当前目录下生成build
、install
、log
这些目录,这也是为什么我们在用这个命令时要切到与src同级目录,这样会使结构清晰。
我们暂时用不到其他功能包,colcon build会编译所有功能包,因此可以用--packages-select
来选择编译指定功能包。
colcon build --packages-select topic_demo_cpp
4. 自定义消息接口
为了实现特定的功能,ros2提供的消息接口不够用,此时我们可以自定义消息接口,大致步骤可分为:创建功能包、编写msg文件、编写package.xml文件、编写CMakeLists.txt文件。
4.1创建功能包
就像创建包含节点的功能包一样,不过我们需要包含rosidl_default_generators
依赖,自定义的消息可以依赖于其他消息,为了演示,我这里还添加了sensor_msgs/msg/Image
依赖,这是ros2提供的消息接口。
你可能会问rosidl中的idl是什么,不太好记住啊,idl是Interface Definition Language
(接口定义语言)的缩写。
ros2 pkg create my_msg --dependencies rosidl_default_generators sensor_msgs --licence Apache-2.0
4.2编写msg文件
在这个功能包中我们用不到include
和src
文件夹,删除就好,然后我们创建msg
文件夹,并且在里面创建msg
文件,如图
在ros2中,支持的原始类型有:int8
、int16
、int32
、int64
、uint8
、uint16
、uint32
、uint64
、float32
、float64
、string
、byte
以及对应的数组类型,如int8[]
。
简单起见,我们整点简单的,在A.msg里面输入
sensor_msgs/Image image #格式为:类型 名称
string str
我们定义了一个sensor_msgs包下的Image,还定义了一个字符串,注意,本来Image的路径应该是sensor_msgs/msg/Image
,但是这里写的是sensor_msgs/Image
,不用管为什么,记住就好了。
4.3编写package.xml
打开package.xml
,在里面添加
<member_of_group>rosidl_interface_packages</member_of_group>
以标识这是一个包含接口的功能包。
为防止其他东西漏写,放出一张截图
4.4编写cmake
打开CMakeLists.txt,添加如下代码
rosidl_generate_interfaces(
${PROJECT_NAME}
"msg/A.msg"
DEPENDENCIES sensor_msgs
)
这个函数是ros2提供的,它能将我们编写的.msg文件转化成代码,默认转成C++和python,也可以指定成其他语言,目前只讲最基本的用法。第一个参数是项目名称,用${PROJECT_NAME}就好,第二个参数是.msg文件,填相对路径,如果有多个.msg文件,用空格隔开,然后是依赖的其他消息接口,同样,如果依赖了多个消息接口,也是用空格隔开。
4.4编译运行
像之前一样,命令行输入
colcon build --packages-select my_msg
这样我们的消息接口就创建好了,我们可以查看一下,输入
source install/setup.bash
ros2 interface show my_msg/msg/A
结果如图
可以看到,里面还显示出了seneor_msgs/Image的详细信息。
4.5使用
自定义消息接口的使用,和系统提供的,也是完全一样了,下面给出两张截图,相信都能看懂,就不再赘述了。