# 4.2 话题通信Python实现 本节小鱼将带你一起,通过Python实现王二和李四之间的话题通信,完成李四发布话题(sexy_girl),王二订阅话题(sexy_girl)的代码。 大家都知道白嫖是不对的(看了小鱼视频一定要一键三连),所以李四还要订阅一个话题来收稿费(sexy_girl_money),王二要发布(sexy_girl_money)话题来支付稿费。 接下来快和小鱼一起动手学习编写Python的话题发布者和订阅者吧~ ## 1.发布话题(sexy_girl) ### 1.1 如何编写 如果编写一个话题发布者呢?其实很简单,大家记得我们创建李四这个类的时候让其继承了Node节点,像下面这样: ``` class Li4Node(Node): ``` 这是什么意思呢?如果没有学过面向对象的同学可能很懵逼,其实很简单,这种在自己名字里写一个Node的意思就是让Li4Node继承于Node,这样李四就能拥有Node所具备的属性和能力。 大家可以把Node理解成人类,Li4Node继承于Node,也就是说李四是一个人类,这样李四就拥有了人类所具备的手和脚等器官,且获得说话吃饭刷B站和关注小鱼公众号的能力了。 那代码里的Li4Node继承Node之后,会具备什么能力呢?在本节中用到了以下四个能力: - 创建一个话题发布者的能力 - 创建一个定时器的能力 - 创建一个话题订阅者的能力 - 获取日志打印器的能力 接下来我们就依次调用Li4Node所继承的能力来实现订阅发布功能。 ### 1.2 编写程序 用VsCode打开上一章中town_ws工作空间,并打开li4.py。我们在其中添加代码即可。 添加完成后Li4Node类中代码如下: ``` #!/usr/bin/env python3 import rclpy from rclpy.node import Node from std_msgs.msg import String class Li4Node(Node): """ 创建一个李四节点,并在初始化时输出一句话 """ def __init__(self): super().__init__("li4") #给节点一个名字 li4 self.get_logger().info("大家好,我是李四,我是一名作家!") #来个自我介绍 self.write = self.create_publisher(String,"sexy_girl", 10) timer_period = 1 #李四的手速,每1s写一段话,够不够快 self.timer = self.create_timer(timer_period, self.timer_callback) #启动一个定时装置,每 1 s,调用一次time_callback函数 self.i = 0 # i 是个计数器,用来算章节编号的 def timer_callback(self): """ 定时器,用于定时发布小说 """ msg = String() msg.data = '第%d回:潋滟湖 %d 次偶遇胡艳娘' % (self.i,self.i) self.write.publish(msg) #将小说内容发布出去 self.get_logger().info('李四:我发布了艳娘传奇:"%s"' % msg.data) #打印一下发布的数据,供我们看 self.i += 1 #章节编号+1 ``` ### 1.3 代码讲解 #### 创建发布者 ``` self.create_publisher(String,"sexy_girl", 10) ``` 小鱼这里使用create_publisher方法来创建的发布者,该方法一共有三个参数,第一个是方法类型,第二个是话题名称,第三个是消息队列长度,第一个参数我们这里添了String,需要注意的是,这里的String并非Python自带的字符串类型,我们使用 ``` from std_msgs.msg import String ``` 从`std_msgs.msg`中导入了String类,那std_msgs是什么呢? `std_msgs`是ROS2自带的接口类型,其中规定了我们常用的大多数消息类型,可以使用下面的指令来查看`std_msgs`中所有的消息类型。 ``` ros2 interface package std_msgs ``` ![image-20210804030652134](4.2话题通信实现(Python)/imgs/image-20210804030652134.png) 大家可以根据自己的传输需要选择适合自己的消息类型,比如我们接下来想让李四收钱,我们将消息类型设置为UInt32,无符号整型,毕竟收钱没有收成负数的。 > 还可以使用 `ros2 interface list`查看所有ros2自带的消息类型。 > > 需要注意的是,ros2中自带的类型基本上能够满足我们日常做机器人时的使用,但如果ros2中的消息类型不能满足我们的需求时,也可以选择自己定义消息类型。 #### 定时器 这里小鱼使用了一个方法来创建一个定时器 ``` self.create_timer(timer_period, self.timer_callback) ``` 这个定时器的作用就是根据传入的`timer_period`时间周期,每`隔一个timer_period`秒,调用一次`self.timer_callback`函数。 在`self.timer_callback`函数里,我们使用publish方法将数据(小说内容)发送出去。也就是说每1s中发送一次小说内容。 ``` self.write.publish(msg) #将小说内容发布出去 ``` ## 2.测试是否发布成功 完成上面的代码后,我们就可以编译运行节点了。 在VsCode中可以使用下面的命令打开和拆分终端: ![image-20210804031437443](4.2话题通信实现(Python)/imgs/image-20210804031437443.png) 单独编译李家村,可以使用下面的指令来单独编译某一个功能包。 ``` colcon build --packages-select village_li ``` ![image-20210804031727080](4.2话题通信实现(Python)/imgs/image-20210804031727080.png) 运行节点 ``` source install/setup.bash ros2 run village_li li4_node ``` ![image-20210804032603175](4.2话题通信实现(Python)/imgs/image-20210804032603175.png) ## 3.订阅收钱话题(sexy_girl_money) 毕竟李四还要过生活的,不能给别人免费看,他就建立了一个收钱话题(sexy_girl_money),专门用来收艳娘传奇的稿费。 ### 3.1 代码编写 在上一部分代码的基础上添加了创建订阅器的函数: ``` self.create_subscription(UInt32,"sexy_girl_money",self.recv_money_callback,10) ``` 这句话的意思是创建订阅者,订阅话题`sexy_girl_money`,话题类型为`UInt32`,每次收到钱就去调用`self.recv_money_callback`函数存起来。 完整代码如下: ``` #!/usr/bin/env python3 import rclpy from rclpy.node import Node from std_msgs.msg import String from std_msgs.msg import UInt32 class Li4Node(Node): """ 创建一个李四节点,并在初始化时输出一个话 """ def __init__(self): super().__init__("li4") #给节点一个名字 li4 self.get_logger().info("大家好,我是李四,我是一名作家!") #来个自我介绍 self.write = self.create_publisher(String,"sexy_girl", 10) timer_period = 1 #李四的手速,每1s写一段话,够不够快 self.timer = self.create_timer(timer_period, self.timer_callback) #启动一个定时装置,每 1 s,调用一次time_callback函数 self.i = 0 # i 是个计数器,用来算章节编号的 # 账户钱的数量 self.account = 0 # 开启收钱箱 self.sub_ = self.create_subscription(UInt32,"sexy_girl_money",self.recv_money_callback,10) def timer_callback(self): """ 定时器,用于定时发布小说 """ msg = String() msg.data = '第%d回:潋滟湖 %d 次偶遇胡艳娘' % (self.i,self.i) self.write.publish(msg) #将小说内容发布出去 self.get_logger().info('李四:我发布了艳娘传奇:"%s"' % msg.data) #打印一下发布的数据,供我们看 self.i += 1 #章节编号+1 def recv_money_callback(self,money): self.account += money.data self.get_logger().info('李四:我已经收到了%d的稿费' % self.account) def main(args=None): """ ros2运行该节点的入口函数,可配置函数名称 """ rclpy.init(args=args) # 初始化rclpy node = Li4Node() # 新建一个节点 rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C) rclpy.shutdown() # rcl关闭 ``` ## 4.测试是否订阅成功 再次编译运行li4节点。 ``` colcon build --packages-select village_li source install/setup.bash ros2 run village_li li4_node ``` 使用`Ctrl+Shift+5`切分一个终端出来,然后通过命令行发布话题数据: ROS2支持下面两种方式,其中方式2最后是有一个空格的,大家注意 ``` ros2 topic pub /sexy_girl_money std_msgs/msg/UInt32 {data:\ 10} ``` ``` ros2 topic pub /sexy_girl_money std_msgs/msg/UInt32 data:\ 10\ ``` 你可以看到,李四已经收到共计50块钱的稿费了。 ![image-20210804044734763](4.2话题通信实现(Python)/imgs/image-20210804044734763.png) ## 5.总结 至此,相信你已经掌握了,如何编写一个Python的节点并进行话题订阅和发布了。 下一节小鱼将带你一起,动手帮王二订阅sexy_girl话题,并支付稿费,让我们继续加油吧~