Python 编写 ROS node 实例/模板

引言

目前很多编程语言都提供了 ROS 库 (library) 用来编写 ros node 文件,官方语言是 C++ 和 Python。在我们的项目中,如果对实时性要求比较高,还会用到 Julia,它也有自己的 ROS library —— RobotOS

一般来说,如果程序不是太复杂,处理时间可以忍受,我都首选 Python 编写,一方面是语法比较简单,另外一方面,也是很重要的一点,不需要设置额外的 CMakeList, package 等文件用于编译过程。通常只需要一个 .py 文件就可以创建一个简单的 ros node 文件,设置成可执行文件,运行即可。

虽然我用 python 写过不少 ros node 文件,但是如果让我在一个空白的文件中逐字逐句的编写一个完整的 node,不借助任何参考,我可能很难写出来。

就像很多写学术论文的人都在用 latex,但是几乎没有人是从无到有的写出一个 latex 文件。一般都要基于一些的模板,修改其中的 tilte, abstract, ref 等内容。

本文主要目的是通过例子展示常见的 python 编写 ros node 的格式,总结基本框架,作为以后的参考模板。

本文中有些例子来自 ros wiki ,我会加上尽量详细的注释。另外的例子则包含了我比较常用的编程格式。

例1: Publisher node

功能:创建一个 ros node,以每秒 10 次的频率发布 “hello world +时间” 信息。

#!/usr/bin/env python    
# 上边一行称为 shebang line, 指定运行该文件的程序。
# 此处是说用从 PATH 环境变量中找到的第一个 python 程序执行该文件。
# 如果要更加具体地指定执行文件,可以用 #!<path>/python 
# 指明用该路径下的 python 程序执行该文件。

import rospy   
# 调用 rospy module,这是所有 python node 需要加载的一个 module
from std_msgs.msg import String  
# 由于后边在构造 Publish message 时用到了 std_msgs/String 类型的 ROS 数据
# 所以要先调用相关的 module 中的 class,即 std_msgs.msgs.String 类。
# 在构造 msg 时,可以用 msg=String() 的方式初始化对象
# 注意:用 python 编写的 node 程序本质上依然是 python 程序文件
# 里面用到的 import 等调用命令还是要去 PYTHONPATH 指定的路径寻找相应的 module。
# 所以 ROS 的各种 module 必须在 PYTHONPATH 的路径中才行。
# 其实在 source ROS 的各种 .bash 文件或者 install 各种 ROS package 的时候
# 就已经将这些 module 放到 PYTHONPATH 中了。

def talker():
    pub = rospy.Publisher('chatter', String, queue_size=10) 
    # 创建一个 pub 对象,pub 对象有个 method —— pub.publish() 可以被用来
    # 向 chatter 这个 topic 上发送类型为 String 的数据。
    # 这里就用到了 String class,所以需要在开头的时候调用相应的 module
    # 我感觉创建 pub 的这句命令放到后边更合适,将创建 node 的 init_node 命令做为最早的命令
    rospy.init_node('talker', anonymous=True)
    # 初始化 node,名字为 talker,有了名字,node 才开始与 master 以及其他 node 通信 
    # ROS graph 中可能存在多个重名的 node,例如多个 turtle node,当令 anonymous=True 时,
    # 重名 node 会自动在名字后边加上随机数以示区别。
    rate = rospy.Rate(10) 
    # 产生一个 rospy.Rate Class 的对象 rate,它有一个 method —— rate.sleep()
    # 可以控制 loop 的频率。
    # 比如每秒钟 loop 10次,如果一次 loop 小于 1/10 秒,则 sleep 等待,确保 1秒 完成10个 loop。
    # 很显然这里的前提是,每个 loop 不能超过 1/10 秒,否则单靠 sleep 是无法达到期望的 loop 频率的 
    while not rospy.is_shutdown():
        # 监控着是否程序是否被关闭,例如 ctrl + c 等
        hello_str = "hello world %s" % rospy.get_time()
        # 这里的字符串就是 std_msgs/String 类型的。rospy.get_time() 可以返回当前时间
        rospy.loginfo(hello_str)
        # 默认情况下 info 会被写到三个地方:1.屏幕,2.log 文件,3.rosout topic
        pub.publish(hello_str)
        rate.sleep()

if __name__ == '__main__':  
# 当该程序作为主程序运行时,满足此条件。
# 如果该程序作为 module 被另一个程序调用,则 if 条件不成立,后边的都不会执行。
    try:
        talker()
    except rospy.ROSInterruptException:  
        pass
    # handle error, 看 talker() 程序是否能够正常运行,如果可以正常运行,则按照 talker() 中的命令运行; 
    # 如果运行 talker() 程序报错,则执行 except block 中的命令,即 pass, 
    # 实际上是什么都不做,结束程序,此时不会出现 error 提示信息。
    # 如果要求不这么严谨,也可以不用 try ... except 形式,直接调用 talker() 就行。

例2: Subscriber node

作用:创建一个 node,收听信息,然后显示出来。

#!/usr/bin/env python
# 第一句依然是 shebang line,指定运行该文件的程序
import rospy
from std_msgs.msg import String

def callback(data):
    # 尽管在 Subscriber 语句中没有给 callback 加参数
    # 但是在执行中默认将接受到的 msg 作为参数传递过来
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
    # std_msgs/String 类型的变量结构为 `string: data`,
   # 即 msg 的值是存储在 string 基本类型的 data field 中。 
def listener():
    rospy.init_node('listener', anonymous=True)
    # 初始化 node,名为 listener,如果有重名的就在后边加随机数,以示区分
    rospy.Subscriber("chatter", String, callback)
    # 监听 chatter topic 上的 std_msgs/String 类型的数据,一旦收到数据,就调用 callback 函数,
    # 其中函数的第一个 argument 总是接受到的数据。

    rospy.spin()
    # 在 C++ 中,spin() 的作用是不断的循环,每次循环结束就令 callback 函数处理数据。
    # 但在 python 中 spin() simply keeps python from exiting until this node is stopped。 
   # callback functions 不受 spin() 的影响,它们有自己的处理进程

if __name__ == '__main__':
    listener()

例3: 用类与对象的形式编写 Publisher node

作用:依然是上边的 publisher node,但用 class 的方式书写。

我认为用 class 有两个好处:

  1. 变量共享,在 class 的一个函数中修改的变量可以通过 self 带到另一个函数中。这样就避免了定义全局变量。
  2. ros node 本身与 class, object 的概念很相符。一个 node 就是一个 object,它有自己的变量,也可以产生一些行为。从 class 和 object 的角度去构造 node,很自然也很直观。

该程序的语句几乎与例 1 相同,不需要再注释了。

#!/usr/bin/env python

import rospy
from std_msgs.msg import String

class Talker():
    def __init__(self):
        self.pub = rospy.Publisher('chatter', String, queue_size=10)
        rate = rospy.Rate(10)
        msg = 'Msg from talker'
        while not rospy.is_shutdown():
            self.pub.publish(msg)
            rate.sleep()


if __name__ == '__main__':
    rospy.init_node('talker')
    try:
        Talker()
    except rospy.ROSInterruptException:
        pass

例4: 用类与对象的形式编写 interpreter node

作用:结合了 sub 与 pub,从 chatter 这个 topic 接受数据,然后 pub 到 final/chatter 这个 topic 上。
这里需要注意的是,在调用 callback 时并没有指明送入什么 argument,但是默认 self 为第一个 argument,subscriber 接收到的 msg 是第二个 argument.

#!/usr/bin/env python

import rospy
from std_msgs.msg import String


class Interpreter():
    def __init__(self):
        rospy.Subscriber('chatter', String, self.callback)
        self.pub = rospy.Publisher('final/chatter', String, queue_size=10)
    # 把后边可能用到的 sub, pub 在初始化函数中定义好

    def callback(self, data):
        msg = 'I have received : (%s), and I interpret it to (Hello World)' \
            % data.data
        self.pub.publish(msg)


if __name__ == '__main__':
    rospy.init_node('interpreter')
    try:
        Interpreter()
    except rospy.ROSInterruptException:
        pass
    rospy.spin()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容