ROS学习笔记(3):通信机制之话题通信

ROS学习笔记(3):通信机制之话题通信

StarHui Lv3

前置知识

在介绍通信机制之前,咱们先来了解ROS中的两个个核心概念:节点(node)节点管理器(ROS Master)
节点是ROS软件包中的一个可执行文件。ROS节点使用ROS客户端库与其他节点通信。 不同的节点甚至可以使用不同的编程语言,以及运行在不同的主机上。 更加详细的介绍可以看这:理解ROS节点

ROS Master 为 ROS 系统中的其余节点提供命名注册服务。它跟踪主题和服务的发布者和订阅者。 Master 的作用是使各个 ROS 节点能够相互定位。一旦这些节点找到彼此,它们就会进行点对点通信。
在使用roscore 命令时,该命令加载 ROS Master 以及其他基本组件。
ROS Master

概述

ROS是机器人操作系统,机器人是一种比较复杂的系统性实现,在机器人上面可能有很多传感器以及运动控制实现,也就意味着有很多个节点。那么我们想要实现指定动作的时候,需要用到很多节点,节点之间是如何通信呢?

ROS基本通信机制有如下三种

  • 话题通信(发布订阅模式)

  • 服务通信(请求响应模式)

  • 参数服务器(参数共享模式)

接下来让我们详细看看这三种通信机制。

话题通信

话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,即:一个节点发布消息,另外一个节点订阅该消息。话题通信适用于不断更新的数据传输相关的应用场景。
接下来让我们看一下话题通信的理论模型吧。

理论模型

在话题通信机制中,有三个角色 ROS Master(管理者)、Talker(发布者)、Listener(订阅者)。

职责分布:ROS Master会保存 Talker、Listener的注册信息,当Listener订阅的主题存在时,就会帮助Listener与 发布该主题的Talker 建立联系。联系建立后,ROS Master就没有什么作用了,当Talker再次发布消息,然后Listener就能看到 Talker发布的消息。
接下来让我们看一下底层是如何实现的。

咱们来详细讲一下每一个流程。

  1. 当Talker启动时,会在ROS Maser那边登记信息,有主题、RPC地址等信息

  2. 当Listener启动时,也会在ROS Maser登记信息,有需要订阅消息的话题名等

  3. ROS Master进行匹配,如果 Listener 需要订阅的话题存在的话,就会通过RPC给 Listener发送 发布该主题 Talker的RPC地址信息。

  4. Listener通过 RPC向 Talker发送连接信息

  5. Talker确认连接信息后,发送自身的TCP地址信息。

  6. Listener 通过TCP地址信息 与 Talker建立连接。

  7. 当Talker再次发布消息时,Listener就能接收到消息,完成了订阅该主题。

其中,Talker、Listener注册无先后顺序。而且Talker、Listener可以有多个,可以一个Talker对多个Listener。
现在话题通信已经比较成熟了,被封装成包了,咱们不需要实现那么多的底层操作,只需要设置一些参数即可。

C++实现话题通信

咱们来用C++实现一个话题通信的实例.

编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。

首先就是创建工作空间、创建ROS包等。

1
2
3
4
5
6
7
8
#创建工作空间并编译
mkdir -p topic_communication/src
cd topic_communication
catkin_make

#创建ROS包并添加依赖
cd src/
catkin_create_pkg pub_sub roscpp rospy std_msgs

可以使用 tree 命令查看文件结构,目前该工作空间结构如下图(只显示了文件夹,没有显示文件)

talker实现

然后咱们在 topic_communication/src/pub_sub/src 子目录下创建一个cpp文件,就可以开始写程序了。

1
2
cd ~/topic_communication/src/pub_sub/src
vim pub.cpp

由于是初次接触,先有易到难编写程序,即先不停发送hello,后面在修改为按照频率发送消息。
下面为程序内容

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
#include "ros/ros.h"
#include "std_msgs/String.h"

int main(int argc, char *argv[])
{
//初始化节点,节点名为 talker
ros::init(argc,argv,"talker");

//创建节点句柄
ros::NodeHandle nh;

//创建发布者对象
ros::Publisher pub = nh.advertise<std_msgs::String>("chat",10);

//创建被发布的消息
std_msgs::String msg;

//当节点存在时,一直循环
while(ros::ok())
{
//给消息添加内容
msg.data = "hello";

//发布消息
pub.publish(msg);
}

return 0;
}

关于这段代码,大家需要看一下函数用法。

1
2
//创建发布者对象
ros::NodeHandle::advertise<std_msgs::String>(const std::string &topic, uint32_t queue_size, bool latch = false)

第一个参数为topic,就是准备发布的主题名字;第二个参数为当消息堵塞时,缓存区的大小,也就是所能保留消息的条数;第三个可写可不写,如果为 "true",将保存在此主题上发布的最后一条信息,并在新用户连接时发送给他们。

还有一个疑惑,为什么要创建节点句柄,它的作用是什么?
后面找了一下官方教程,是这样解释的。
NodeHandle 是 roscpp 用于创建订阅者、发布者等的接口。该类用于编写节点。它为该进程的节点提供了一个 RAII 接口,即在创建第一个 NodeHandle 时,它会实例化该节点所需的一切,而当最后一个 NodeHandle 退出作用域时,它会关闭节点。

NodeHandle


下一步是修改CMakeLists.txt

把136行注释取消,修改 ${PROJECT_NAME}_node 以及路径
取消149~151的注释,把 ${PROJECT_NAME}_node替换为刚才修改的名字

然后回到工作空间,并编译。

1
2
cd ~/topic_communication/
catkin_make

最后就是运行这个包(启动内核,刷新环境变量,运行包)

由于咱们没有打印调试信息,所以咱们什么都看不到,可以在终端使用rostopic echo命令

1
2
3
#rostopic echo topic_name
#把topic_name替换为自己的话题名字
rostopic echo chat

咱们之前发送的消息就是 hello,说明发布部分写正确了。接下来咱们按照要求 10HZ完成发送消息的任务。代码如下

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
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int main(int argc, char *argv[])
{
//设置编码,解决中文乱码问题
setlocale(LC_ALL,"");

//初始化节点,节点名为 talker
ros::init(argc,argv,"talker");

//创建节点句柄
ros::NodeHandle nh;

//创建发布者对象
ros::Publisher pub = nh.advertise<std_msgs::String>("chat",10);

//创建被发布的消息
std_msgs::String msg;

//数据
std::string msg_front = "Hello 你好!";
int count = 0;

//设置一个定时器,帮助我们以所需频率运行循环
ros::Rate r(1);

//当节点存在时,一直循环
while(ros::ok())
{
//实例化std::stringstream对象,使用它完成字符串的拼接
std::stringstream ss;

ss << msg_front << count;

//给消息添加内容
msg.data = ss.str();

//发布消息
pub.publish(msg);

//输出调试信息
ROS_INFO("发布的消息为:%s",ss.str().c_str());

//达到睡眠时间,自动休眠
r.sleep();

count++;

}

return 0;
}

下面是效果图

listener实现

接下来把listener实现一下。

首先进入到 topic_communication/src/pub_sub/src ,然后开始写订阅程序。

1
2
3
#如果路径不同,请替换为自己的路径
cd ~/topic_communication/src/pub_sub/src/
vim sub.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
#include "ros/ros.h"
#include "std_msgs/String.h"

//回调函数处理消息
void handle_msg(const std_msgs::String::ConstPtr& msg_p)
{
ROS_INFO("接收到的消息:%s",msg_p -> data.c_str());
}

int main(int argc, char *argv[])
{
//设置编码格式
setlocale(LC_ALL,"");

//初始化节点 listener
ros::init(argc,argv,"listener");

//创建节点话柄
ros::NodeHandle nh;

//创建订阅者对象
ros::Subscriber sub = nh.subscribe<std_msgs::String>("chat",10,handle_msg);

//启动ROS节点的事件循环
ros::spin();

return 0;
}

关于这段代码,有两个需要解释的地方

1
ros::Subscriber sub = nh.subscribe<std_msgs::String>("chat",10,handle_msg);

这个是创建一个订阅者对象,ros::Subscriber 声明了类型,通过使用nh的subscribe函数来生成了一个对象。关于subscribe函数,有13种重载(使用方法),大家可以选择任意一种

这里使用的是第二种,第一个参数为需要订阅的话题;第二个参数为缓冲区大小,即当消息堵塞时,所能保留消息的条数;第三个是一个回调函数,用于处理消息。关于回调函数,请看 回调函数(callback)是什么?一文理解回调函数(callback)

1
ros::spin();

这个代码起什么作用?
ros::spin()的作用是使订阅者对象(sub)开始监听来自"chat"话题的消息。通过调用ros::spin(),程序将持续监听并处理接收到的消息,直到节点被关闭。当有新的消息到来时,ROS会自动触发回调函数handle_msg(),并将收到的消息作为参数传递给回调函数进行处理。有点STM32里面中断那味。
可以详细看一下这个 ros::spin()、ros::spinOnce():使用细节、区别 ,并于 ros::spinonce()做一个区分.


然后修改CMakeLists.txt

然后回到工作空间并编译

1
2
cd ~/topic_communication/
catkin_make

最后看一下效果

记得在运行节点前,要刷新一下环境变量哈

为了更加直观显示通信的逻辑,咱们可以使用 rqt_graph 显示当前运行的节点和话题。

1
2
3
4
#如果没有安装的话。请放开注释 进行安装
#sudo apt-get install ros-<distro>-rqt
#sudo apt-get install ros-<distro>-rqt-common-plugins
rosrun rqt_graph rqt_graph

注意:当进行通信的时候,才能查看到内容。

可以看到,talker、listener节点在通过 chat话题进行通信。

Python实现话题通信

还是上面那个实例,咱们用rospy来实现一下。由易到难实现。

talker实现

首先在ROS包下创建一个 scripts文件夹,并在该目录下创建pub.py

1
2
3
4
cd ~/topic_communication/src/pub_sub/
mkdir scripts
cd scripts
vim pub.py

pub.py内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#! /usr/bin/env python

import rospy
from std_msgs.msg import String

if __name__ == "__main__":

#初始化节点
rospy.init_node("talker_py")

#创建发布者对象
pub = rospy.Publisher("chat_py",String,queue_size=10)

#创建字符串类型消息
msg = String()

#当节点没有被关闭时,一直发送消息
while not rospy.is_shutdown():

msg.data = "hello_py"

#发布消息
pub.publish(msg)

对于这段代码,大家需要看一下Publisher、is_shutdown这两个函数的用法。

1
pub = rospy.Publisher("chat_py",String,queue_size=10)

创建 rospy.Publisher 所需的参数只有主题名称、消息类别和队列大小。还可以使用其他高级选项来配置 "发布者"

is_shutdown函数用于判断节点是否关闭,如果关闭的话,返回True,否则返回False。


保存并退出后,给该文件添加可执行权限。

1
chmod +x pub.py

然后去ROS包子目录下修改

1
2
#此时路径为:topic_communication/src/pub_sub/scripts
vim ../CMakeLists.txt

取消注释,并把py文件路径修改为自己的路径

回到工作空间,进行编译

1
2
cd ~/topic_communication/
catkin_make

接下来运行节点,并使用rostopic echo 查看消息

最后,再按照实例要求,完成按照频率发布消息的任务。

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
#! /usr/bin/env python

import rospy
from std_msgs.msg import String

if __name__ == "__main__":

#初始化节点
rospy.init_node("talker_py")

#创建发布者对象
pub = rospy.Publisher("chat_py",String,queue_size=10)

#创建字符串类型消息
msg = String()
msg_front = "hello_py"
count = 0

#设置频率
rate = rospy.Rate(1)

#当节点没有被关闭时,一直发送消息
while not rospy.is_shutdown():

msg.data = msg_front + str(count)

#发布消息
pub.publish(msg)

rospy.loginfo("发布的消息:%s",msg.data)

rate.sleep()
count += 1

最后重新编译,刷新环境变量,重新启动节点,使用 rostopic echo命令查看消息

listener实现

首先进入scripts文件夹内,并在这里创建sub.py

1
2
cd ~/topic_communication/src/pub_sub/scripts
vim sub.py

sub.py内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import rospy
from std_msgs.msg import String

def handle_msg(msg):
rospy.loginfo("接收到的消息:%s",msg.data)

if __name__ == "__main__":
#初始化节点
rospy.init_node("sub_py")

#创建订阅者对象
sub = rospy.Subscriber("chat_py",String,handle_msg,queue_size=10)

#设置循环调用回调函数
rospy.spin()

其中大部分与C++实现类似,大家着重看一下函数用法

1
sub = rospy.Subscriber("chat_py",String,handle_msg,queue_size=10)

一般只需要这几个参数,第一个参数为需要订阅话题的名字;第二个是消息类型;第三个是回调函数;第四个缓冲区大小


保存并退出后,给该文件添加可执行权限。

1
chmod +x sub.py

然后去ROS包子目录下修改

1
2
#此时路径为:topic_communication/src/pub_sub/scripts
vim ../CMakeLists.txt


最后回到工作空间,编译,并运行sub_py、pub_py节点

最后再使用 rosrun rqt_graph rqt_graph查看图

大家可以看一下rospy官方的示例: 官方示例

C++与Python节点通信

在ROS中,使用不同编程语言的节点之间也可以进行通信。很神奇吧。

咱们在前面写过的代码里稍微修改一下,就可以实现了。
这里发布者节点为用的是C++ 节点(节点名:sub),订阅者节点使用的是Python节点(节点名:sub_py)。
为了让它们之间通信,需要修改一下sub_py节点订阅的话题。sub节点的话题为 chat,咱们需要把sub_py节点订阅的话题修改为chat。

1
sub = rospy.Subscriber("chat",String,handle_msg,queue_size=10)

修改这一句话就可以了。
然后就是重新编译,刷新环境变量 运行节点。

自定义msg

在前面的案例中,我们传输的数据是 std_msgs中的String数据类型,只包含了一个data字段,有一定局限性,std_msgs中其他的数据类型Int32、Int64、Char、Bool、Empty也是如此。

当传输复杂的数据的时候,比如激光雷达的信息,std_msgs就不太行了,这时候咱们可以选择自定义消息类型。

msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:

  • int8, int16, int32, int64 (或者无符号类型: uint*)

  • float32, float64

  • string

  • time, duration(时间、持续时间)

  • other msg files(其他msg文件)

  • variable-length array[] and fixed-length array[C](可变长度数组、固定长度数组)

ROS中还有一种特殊类型:Header,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。

为了直观,这里咱们直接实际创建一个自定义msg。
要求:消息包含人的信息:姓名、身高、年龄等 大致流程如下

  1. 按照固定格式创建 msg 文件

  2. 编辑配置文件

  3. 编译生成可以被 Python 或 C++ 调用的中间文件

定义msg文件

首先回到功能包目录下,创建一个msg目录

1
2
3
cd ~/topic_communication/src/pub_sub/
mkdir msg
#msg不能变,必须这一个名字

然后进入msg子目录下,创建一个 .msg文件

1
2
3
cd msg/
vim Person.msg
#.msg文件名可以随便起

Person.msg内容如下

1
2
3
string name
uint16 age
float64 height

我来解释一下语法。
.msg文件每行具有字段类型和字段名称。第一行定义了一个字符串类型的name字段,第二行定义了一个无符号16位的整数age字段,第三行定义了一个64位浮点数的height字段。
有一点点像C++的结构体!

编辑配置文件

我在CMakeLists.txt看到了下图,配置步骤。

接下来需要修改ROS包文件夹下的package.xml文件

在这里添加编译依赖项、执行依赖项

1
2
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

Package.xml是干什么的呢?

package.xml文件定义了package的属性。(例如:包名,版本号,作者,一来等等),相当于一个包的自我描述。
详细解读可以看:package.xml的使用说明和作用


然后修改ROS包下的CMakeLists.txt文件
(1)在find_package那里添加包

1
2
3
4
5
6
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)

find_package是查找编译依赖的其他CMake/Catkin包(声明依赖库)。自定义msg编译时需要message_generation。

注意:message_genration是基于std_msgs实现的,std_msgs不能删除

(2) 在add_message_files添加自定义msg文件,并且把注释放开

1
2
3
4
add_message_files(
FILES
Person.msg #替换为自己的msg文件名
)

add_message_files这个是消息生成器。当自定义msg需要在这里添加msg文件

(3) 把generate_messages注释放开,去生成消息

在这里调用了消息生成器,用于生成自定义的msg。

(4)catkin_package添加包

1
2
3
4
5
6
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES pub_sub
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)

catkin_package()是一个由catkin提供的CMake宏。需要指定特定的catkin信息到编译系统,而这些信息又会被用于生成pkg-config和CMake文件。

在这里,给它添加了当前项目依赖的其他catkin项目

到这里,修改配置文件已经结束了。 希望大家再看看package.xml、CMakeLists.txt的解读,不能知其然而不知其所以然!!!

ROS的CMakeList文件详解
package.xml的使用说明和作用

查看msg的中间文件

首先回到工作空间,重新编译一下。

1
2
cd ~/topic_communication/
catkin_make

编译成功后,可以在工作空间下的devel文件夹内查看中间文件。
C++可以用的msg中间文件在 topic_communication/devel/include/pub_sub/Person.h topic_communication为我的工作空间名字,pub_sub为我的ROS包名字。
注意:大家路径可能不一样,因为工作空间、ROS包的名字不一样。

虽然看不太懂,但是还是可以看到已经成功创建了name、age、height这三个字段的。
当我们使用C++想要发送自定义消息的时候,就可以include这个头文件了


下面咱们可以看一下适用与Python的中间文件。
路径:topic_communication/devel/lib/python3/dist-packages/pub_sub/msg
topic_communication为我的工作空间名字,pub_sub为我的ROS包名字

同样看不懂,但是还是能看到我们创建的字段。
Python调用的时候使用from import调用即可

C++使用自定义msg

和前面的案例一样,10HZ的频率发布消息。不同的是,消息的数据类型变了。
还是在之前的ROS包下的src文件夹内创建一个cpp文件。

1
2
cd ~/topic_communication/src/pub_sub/src/
vim pub_custom_msg.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
#include "ros/ros.h"
#include "pub_sub/Person.h"

int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");

//初始化节点
ros::init(argc,argv,"A");

//创建节点句柄
ros::NodeHandle nh;

//创建发布者对象
ros::Publisher pub = nh.advertise<pub_sub::Person>("chat_person",10);

//创建自定义msg
pub_sub::Person p;
p.name = "starhui";
p.age = 19;
p.height = 1.83;

ros::Rate r(1);

while (ros::ok())
{
pub.publish(p);
ROS_INFO("我叫:%s,今年%d岁,高%.2f米", p.name.c_str(), p.age, p.height);

r.sleep();
p.age += 1;
p.height += 0.01;
}


return 0;
}

接下来修改CMakeLists.txt

这里咱们多修改了一个内容

1
2
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(pub_custom_msg ${PROJECT_NAME}_generate_messages_cpp)

添加这个的原因是:

告诉Catkin构建系统在构建该节点之前先构建所依赖的目标。通俗解释就是
咱们前面定义了msg文件并且配置好了,如果还没有编译的话;此时把cpp文件也写好了,也需要编译,但是cpp文件是依赖msg的中间文件的,那么编译器会先编译cpp文件,在编译msg,这样就会有报错。

最后编译,刷新环境变量,运行pub_custom_msg节点,使用rostopic echo命令查看消息。

注意:当使用 rostopic echo命令报如下错误的时候,需要进入工作空间,并使用source ./devel/setup.bash刷新环境变量。
是因为没有把这个添加到~/.bashrc,当开新终端的时候,不知道自定义msg的路径,需要刷新一下。


接下来完成订阅方。
还是在之前的ROS包下的src文件夹内创建一个cpp文件。

1
2
cd ~/topic_communication/src/pub_sub/src/
vim sub_custom_msg.cpp

程序内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "ros/ros.h"
#include "pub_sub/Person.h"

void handle_msg(const pub_sub::Person::ConstPtr& msg_p)
{
ROS_INFO("接收的信息:%s, %d, %.2f", msg_p->name.c_str(), msg_p->age, msg_p->height);
}

int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");

ros::init(argc,argv,"B");

ros::NodeHandle nh;

ros::Subscriber sub = nh.subscribe<pub_sub::Person>("chat_person",10,handle_msg);

ros::spin();

return 0;
}

和前面一样配置CMakeLists.txt

然后编译、刷新环境变量,运行A、B这两个节点

Python使用自定义msg

和前面的案例一样,10HZ的频率发布消息。

还是在之前的ROS包下的scripts文件夹内创建一个py文件。

1
2
cd ~/topic_communication/src/pub_sub/scripts/
vim pub_custom_msg.py

程序内容

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
#! /usr/bin/env python

import rospy
from pub_sub.msg import Person
if __name__ == "__main__":

#初始化节点
rospy.init_node("C")

#传建发布者对象
pub = rospy.Publisher("chat_person_py",Person,queue_size=10);

#传建自定义msg
p = Person()
p.name = "starhui"
p.age = 19
p.height = 1.83

#设定频率
rate = rospy.Rate(1)

#当节点没有关闭的时候,一直发送消息
while not rospy.is_shutdown():
#发布消息
pub.publish(p)

#输出调试信息
rospy.loginfo("from python 姓名:%s, 年龄:%d, 身高:%.2f",p.name, p.age, p.height)

#休眠
rate.sleep()

#更新数据
p.age += 1
p.height += 0.01

接下来给该python文件添加可执行权限。

1
chmod +x pub_custom_msg.py

修改CMakeLists.txt,给catkin_install_python添加刚才的python文件

最后重新编译 刷新一下环境变量,运行节点


最后我们来写订阅者

还是在之前的ROS包下的scripts文件夹内创建一个py文件。

1
2
cd ~/topic_communication/src/pub_sub/scripts/
vim sub_custom_msg.py

程序内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#! /usr/bin/env python

import rospy
from pub_sub.msg import Person

def handle_msg(msg_p):
rospy.loginfo("from python 接受的信息:%s,%d,%.2f",msg_p.name,msg_p.age,msg_p.height)

if __name__ == "__main__":

#初始化节点
rospy.init_node("D")

#传建订阅者对象
sub = rospy.Subscriber("chat_person_py",Person,handle_msg,queue_size=10)

#循环处理回调函数
rospy.spin()

接下来给该python文件添加可执行权限。

1
chmod +x sub_custom_msg.py

修改CMakeLists.txt,给catkin_install_python添加刚才的python文件

编译、刷新环境变量,运行节点

  • Title: ROS学习笔记(3):通信机制之话题通信
  • Author: StarHui
  • Created at : 2023-12-03 21:53:00
  • Updated at : 2023-12-03 22:52:45
  • Link: https://renyuhui0415.github.io/post/topic_communication.html
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments