话题
节点之间传递数据
注意,话题是一个抽象的概念,其具体执行还是由节点完成,故每个话题都至少会有一个节点传输数据,一个节点收到数据
面向对象编程相比于面向过程编程有显著的开发优势,在以后的内容中我们使用面向对象编程风格
传输数据
在编写一个只向话题传输数据的节点时的代码很简单,只需要在类初始化时调用Node类继承过来的方法create_publisher和create_timer。需要注意的是,前者参数为(#{message type},#{topic name},#{缓存长度})这里缓存长度指的是当数据没有发出时程序会在缓存区保留多少帧的数据
以下为话题的发布者节点实现代码:
class publisher(Node):
def __init__(self,name):
super().__init__(name)
self.pub=self.create_publisher(String,"topic_a",10)
self.timer=self.create_timer(0.5,self.doPublish)
def doPublish(self):
self.pub.publish("This is a MESSAGE!")
self.get_logger().info("Already sent a message called 'This is a MESSAGE!'")
def main(args=None)
rclpy.init(args=args)
node_a=publisher("publisher_a")
rclpy.spin(node_a)
node.destroy_node()
rclpy.shutdown()
create_timer方法分析
这里深入分析一下create_timer的执行逻辑:
create_timer是node.py中的一个方法,通过查阅其参数表
def create_timer(
self,
timer_period_sec: float,
callback: Callable,
callback_group: CallbackGroup = None,
clock: Clock = None,
) -> Timer:
我们可以判断,我们的传入参数分别给到了timer_period_sec以及callback中。
再往后查阅create_time的代码,可以发现callback在下一次提及是:
timer = Timer(callback, callback_group, timer_period_nsec, clock, context=self.context)
这里新建立了类Timer,Timer是ROS2的一个计时器,通过该计时器进行callback函数的反复调用。
代码解读
我们现在解读一下这串代码:
与先前的编写节点一样,rclpy.__init__(args=args)、node.destroy_node()、rclpy.shutdown()、super().__inity__(self,name)仍是必须的
在python语法中,类内方法中初始化变量使用self.#{name}=#{赋值}语句进行声明,在类内方法外的声明为静态变量,同java中被修饰符static修饰的变量。
在这里我们调用父类中Node的方法create_publisher以及create_timer,publisher的参数为信息类型、话题名称、缓存时间长度,timer的参数为执行周期以及被执行的函数。
此处被调用的函数为timer_callback。在这个方法中,我们使用了node.pub.publish方法中来发布消息,并使用node.get_logger().info()方法来在pub节点宣布已经发送的消息。
Timer是ROS2一个很重要的库,它可以帮助完成很多事情,要善于利用
多种实现
需要注意的是,面向对象编程的规律并不是一成不变的,也并不是公式化的,多种编程风格都可以完成上述pub节点的编写。例如:
class PublisherNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.pub = self.create_publisher(String, "topic_a", 10) # 创建发布者对象(消息类型、话题名、队列长度)
while(rclpy.ok()):
self.pub.publish('Hello World') # 发布话题消息
self.get_logger().info("Publishing:"+'Hello World') # 输出日志信息,提示已经完成话题发布
time.sleep(0.5)
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = PublisherNode("topic_helloworld_pub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
class PublisherNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
self.pub = self.create_publisher(String, "topic_a", 10) # 创建发布者对象(消息类型、话题名、队列长度)
self.timer = self.create_timer(0.5, self.timer_callback) # 创建一个定时器(单位为秒的周期,定时执行的回调函数)
def timer_callback(self): # 创建定时器周期执行的回调函数
msg = String() # 创建一个String类型的消息对象
msg.data = 'Hello World' # 填充消息对象中的消息数据
self.pub.publish(msg) # 发布话题消息
self.get_logger().info('Publishing: "%s"' % msg.data) # 输出日志信息,提示已经完成话题发布
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = PublisherNode("topic_helloworld_pub") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
不同的实现都可以完成任务,我们要做的是努力封装每一个部分,使得程序整体易于阅读、易于操作。
接受数据
接收数据节点的代码类似传输数据节点:
class sub(Node):
def __init__(self,name):
super().__init__(name)
self.suber=self.create_subscription(String,"topic_a",self.callback,10)
def callback(self,msg):
self.get_logger().info("heard:"+msg.data)
def main(args=None):
rclpy.init(args=args)
node_b=sub("sub_a")
rclpy.spin(node_b)
node_b.destroy_node()
rclpy.shutdown()
需要注意的是,为了保证传输数据和接受数据使用同一个话题,需要将传输节点与接受节点中话题的名称,即create_subscription函数中第二个参数值设定为相同。
在这段代码中,因为与传输数据流程较为相似,都是创建一个节点后调用Node继承得到的方法激活回调函数,因此大部分代码重合率较高
不同的是此处调用的Node函数中使用的是create_subscription函数,此函数相比于create_publisher多了一个参数,即本代码中的self.callback,其含义为在收到所关注的话题的消息后执行该函数。
这里create_subscription的第四个参数与上文中create_publisher作用相似,在此不多赘述
与ROS2-节点一文中提到的一样,话题节点编写后也务必记得在setup.py中配置好入口