msg和srv简介

  • msg: msg文件是描述ROS消息字段的简单文本文件。它们用于为不同语言的消息生成源代码
  • srv: srv文件描述服务。它由两部分组成:请求和响应。

msg 文件存储在包的 msg 目录中,srv 文件存储在 srv 目录中。

msg 只是简单的文本文件,每行有一个字段类型和字段名。可以使用的字段类型有:

  • int8, int16, int32, int64 (plus uint*)
  • float32, float64
  • string
  • time, duration
  • other msg files
  • variable-length array[] and fixed-length array[C]

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

下面是一个使用标头、字符串原语和其他两个 msg 的 msg 示例:

  Header header
  string child_frame_id
  geometry_msgs/PoseWithCovariance pose
  geometry_msgs/TwistWithCovariance twist

srv 文件与 msg 文件类似,只是它们包含两部分:请求和响应。这两部分用一条 --- 线隔开。以下是 srv 文件的示例:

int64 A
int64 B
---
int64 Sum

在上面的示例中,A 和 B 是请求,Sum 是响应。

使用 msg

创建消息

让我们在上一个教程中创建的包中定义一个新消息。

roscd <pkg>
mkdir msg
echo "int64 num" > msg/Num.msg

上面的示例 .msg 文件只包含一行。当然,您可以通过添加多个元素(每行一个)来创建更复杂的文件,如下所示:

string first_name
string last_name
uint8 age
uint32 score

不过,还有一步。我们需要确保 MSG 文件变成 C++、Python 和其他语言的源代码:

打开 package.xml,确保其中有两行未注释:

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

注意,在构建时,我们需要 message_generation,而在运行时,我们只需要 message_runtime

在您喜爱的文本编辑器中打开 CMakeLists.txt

message_generation 依赖项添加到 find_package 中,该调用已存在于CMakeLists.txt 中,以便生成消息。您只需将消息生成添加到组件列表中即可完成此操作,如下所示:

# Do not just add this to your CMakeLists.txt, modify the existing text to add message_generation before the closing parenthesis
find_package(catkin REQUIRED COMPONENTS
   roscpp
   rospy
   std_msgs
   message_generation
)

您可能会注意到,有时即使没有调用具有所有依赖项的 find_package,您的项目也可以正常构建。这是因为 catkin 将您的所有项目合并为一个项目,因此如果早期的项目调用 find_package,您的项目将使用相同的值进行配置。但是忘记调用意味着您的项目在孤立构建时很容易中断。

还要确保导出消息运行时依赖项。

catkin_package(
  ...
  CATKIN_DEPENDS message_runtime ...
  ...)

查找以下代码块:

# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )

通过删除 # 符号取消注释,然后将替代消息 *.msg 文件替换为你的 .msg 文件,如下所示:

add_message_files(
  FILES
  Num.msg
)

通过手动添加 .msg 文件,我们可以确保在添加其他 .msg 文件后,CMake 知道何时必须重新配置项目。

现在我们必须确保调用 generate_messages() 函数。

对于 ROS Hydro 和更高版本,您需要取消注释这些行:

# generate_messages(
#   DEPENDENCIES
#   std_msgs
# )

所以它看起来像:

generate_messages(
    DEPENDENCIES
    std_msgs
)

在早期版本中,您可能只需要取消注释一行:

generate_messages()

现在,您可以根据msg定义生成源文件了。

使用 srv

创建 srv

我们将从另一个包中复制现有的 srv 定义,而不是手动创建新的 srv 定义。

为此,roscp 是一个有用的命令行工具,用于将文件从一个包复制到另一个包。

$ roscp [package_name] [file_to_copy_path] [copy_path]

现在,我们可以从 rospy_tutorials 包中复制服务:

roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv

不过,还有一步。我们需要确保 SRV 文件变成 C++、Python 和其他语言的源代码。

除非您已经这样做了,否则请打开 package.xml,并确保其中有两行未注释:

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

和前面一样,请注意,在构建时,我们需要 “message_generation”,而在运行时,我们只需要 “message_runtime”。

除非您已经在上一步中对消息执行了此操作,否则请添加消息生成依赖项以在 CMakeLists.txt 中生成消息:

# Do not just add this line to your CMakeLists.txt, modify the existing line
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

(尽管名称不同,message_generate 对 msg 和 srv 都有效。)

此外,您还需要对 package.xml 进行与消息相同的更改,因此请查看上面所需的其他依赖项。

删除 # 以取消对以下行的注释:

# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

并为您的服务文件替换占位符 Service*.srv 文件:

add_service_files(
  FILES
  AddTwoInts.srv
)

现在,您可以根据服务定义生成源文件了。

1. 编写一个发布节点

“节点”是指连接到ROS网络的可执行文件的ROS术语。在这里,我们将创建publisher(“talker”)节点,该节点将持续广播消息。

别忘了进入到你的工作目录中。

1.1 代码

首先,让我们创建一个“script”文件夹来存储Python脚本:

$ mkdir scripts
$ cd scripts

然后将示例脚本 talker.py 下载到新脚本目录,并使其可执行:

$ wget https://raw.github.com/ros/ros_tutorials/kinetic-devel/rospy_tutorials/001_talker_listener/talker.py
$ chmod +x talker.py

我们还不会运行它。您可以使用 rosed beginner_tutorials talker.py 查看该文件,也可以查看下面的内容。

#!/usr/bin/env python
# license removed for brevity
import rospy
from std_msgs.msg import String

def talker():
    pub = rospy.Publisher('chatter', String, queue_size=10)
    rospy.init_node('talker', anonymous=True)
    rate = rospy.Rate(10) # 10hz
    while not rospy.is_shutdown():
        hello_str = "hello world %s" % rospy.get_time()
        rospy.loginfo(hello_str)
        pub.publish(hello_str)
        rate.sleep()

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

将以下内容添加到 CMakeLists.txt。这样可以确保正确安装 python 脚本,并使用正确的 python 解释器。

catkin_install_python(PROGRAMS scripts/talker.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

1.2 代码解释

现在,让我们把代码分解一下。

#!/usr/bin/env python

每个 Python ROS 节点的顶部都会有此声明。第一行确保脚本作为 Python 脚本执行。

import rospy
from std_msgs.msg import String

如果正在编写 ROS 节点,则需要导入 rospystd_msgs.msg 导入是为了我们可以重用 std_msgs/String 消息类型(一个简单的字符串容器)进行发布。

    pub = rospy.Publisher('chatter', String, queue_size=10)
    rospy.init_node('talker', anonymous=True)

这部分代码定义了说话者与其他 ROS 的接口。Publisher("chatter",String,queue_size=10) 声明您的节点正在使用消息类型字符串发布到 chatter 主题。这里的 String 实际上是 std_msgs.msg.String 类。queue_size 参数在 ROS hydro 中是新的,它限制了任何订阅者接收排队消息的速度。在旧的 ROS 分布中,只需省略参数即可。

下一行 rospy.init_node(NAME, ...) 非常重要,因为它告诉 rospy 节点的名称——在 rospy 获得此信息之前,它无法开始与 ROS 主机通信。在这种情况下,您的节点将采用名称说话者。注意:名称必须是基本名称,即不能包含任何斜杠“/”。

anonymous=True 通过在名称末尾添加随机数来确保节点具有唯一的名称。有关节点初始化选项的更多信息,请参阅 rospy 文档。

    rate = rospy.Rate(10) # 10hz

该行创建一个速率对象速率。借助其方法 sleep(),它提供了一种以所需速率循环的方便方法。其参数为 10,我们应该期望每秒循环 10 次(只要我们的处理时间不超过十分之一秒!)

    while not rospy.is_shutdown():
        hello_str = "hello world %s" % rospy.get_time()
        rospy.loginfo(hello_str)
        pub.publish(hello_str)
        rate.sleep()

这个循环是一个相当标准的 rospy 构造:检查 rospy.is_shutdown() 标志,然后执行工作。您必须检查 is_shutdown() 以检查您的程序是否应该退出(例如,如果存在 Ctrl-C 或其他方式)。在本例中,“work” 是对 pub.publish(hello_str) 的调用,它将字符串发布到我们的聊天主题。循环调用 rate.sleep(),它的睡眠时间刚好足以在循环中保持所需的速率。

这个循环还调用 rospy.loginfo(str),它执行三重任务:将消息打印到屏幕上,写入节点的日志文件,然后写入 rosout。rosout 是一个方便的调试工具:您可以使用 rqt_console 调出消息,而不必查找包含节点输出的控制台窗口。

std_msgs.msg.String 是一种非常简单的消息类型,因此您可能想知道发布更复杂的类型会是什么样子。一般的经验法则是构造函数参数的顺序与 .msg 文件中的顺序相同。您还可以不传入任何参数并直接初始化字段,例如。

msg = String()
msg.data = str

或者,您可以初始化部分字段,其余字段保留默认值:

String(data=str)

您可能想知道最后一点:

    try:
        talker()
    except rospy.ROSInterruptException:
        pass

除了标准的 Python __main__ 检查之外,它还捕获一个 rospy.ROSInterruptException 异常,当按下 Ctrl-C 或关闭节点时,rospy.sleep()rospy.Rate.sleep() 方法会抛出该异常。引发此异常的原因是为了避免在 sleep() 之后意外地继续执行代码。

现在我们需要编写一个节点来接收消息。

2 编写一个接收节点

2.1 代码

listener.py 文件下载到脚本目录中:

$ roscd beginner_tutorials/scripts/
$ wget https://raw.github.com/ros/ros_tutorials/kinetic-devel/rospy_tutorials/001_talker_listener/listener.py
$ chmod +x listener.py

文件内容:

#!/usr/bin/env python
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)

def listener():

    # In ROS, nodes are uniquely named. If two nodes with the same
    # name are launched, the previous one is kicked off. The
    # anonymous=True flag means that rospy will choose a unique
    # name for our 'listener' node so that multiple listeners can
    # run simultaneously.
    rospy.init_node('listener', anonymous=True)

    rospy.Subscriber("chatter", String, callback)

    # spin() simply keeps python from exiting until this node is stopped
    rospy.spin()

if __name__ == '__main__':
    listener()

然后,在 CMakeLists.txt 中编辑 catkin_install_python() 调用,使其如下所示:

catkin_install_python(PROGRAMS scripts/talker.py scripts/listener.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

2.2 代码解释

代码与 talker.py 类似,只是我们引入了一种新的基于回调的订阅消息机制。

    rospy.init_node('listener', anonymous=True)

    rospy.Subscriber("chatter", String, callback)

    # spin() simply keeps python from exiting until this node is stopped
    rospy.spin()

这声明您的节点订阅了类型为 std_msgs.msgs.String 的聊天主题。接收到新消息时,将调用回调,并将消息作为第一个参数。

我们还稍微修改了对 rospy.init_node() 的调用。我们添加了 anonymous=True 关键字参数。ROS 要求每个节点具有唯一的名称。如果出现具有相同名称的节点,它将碰撞上一个节点。这样,出现故障的节点就可以很容易地从网络中退出。anonymous=True 标志告诉 rospy 为节点生成一个唯一的名称,以便可以轻松运行多个 listener.py节点。

最后一个添加项 rospy.spin() 只是在关闭节点之前阻止节点退出。与 roscpp 不同,rospy.spin() 不影响订阅方回调函数,因为它们有自己的线程。

3 编写服务节点

在这里,我们将创建服务(“add_two_ints_server”)节点,该节点将接收两个int并返回总和。

请确保您已按照上一教程中的说明创建本教程中所需的服务,即创建AddTwoInts.srv(请确保在链接的wiki页面顶部选择正确版本的构建工具)。

3.1 代码

在初学者教程包中创建 scripts/add_two_ints_server.py 文件,并在其中粘贴以下内容:

#!/usr/bin/env python

from __future__ import print_function

from beginner_tutorials.srv import AddTwoInts,AddTwoIntsResponse
import rospy

def handle_add_two_ints(req):
    print("Returning [%s + %s = %s]"%(req.a, req.b, (req.a + req.b)))
    return AddTwoIntsResponse(req.a + req.b)

def add_two_ints_server():
    rospy.init_node('add_two_ints_server')
    s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
    print("Ready to add two ints.")
    rospy.spin()

if __name__ == "__main__":
    add_two_ints_server()

不要忘记使节点可执行:

    chmod +x scripts/add_two_ints_server.py

将以下内容添加到 CMakeLists.txt。这样可以确保正确安装 python 脚本,并使用正确的 python 解释器。

catkin_install_python(PROGRAMS scripts/add_two_ints_server.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

3.2 代码解释

现在,让我们把代码分解一下。

使用 rospy 编写服务几乎没有什么意义。我们使用 init_node() 声明节点,然后声明服务:

    s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)

这声明了一个名为 AddTwoInts 的新服务,其服务类型为 AddTwoInts。所有请求都传递给 handle_add_two_ints 函数。

handle_add_two_int通过 AddTwoIntsRequest 的实例调用,并返回AddTwoIntsResponse 的实例。与订阅示例一样,rospy.spin() 会阻止代码退出,直到服务关闭。

4 编写客户端节点

4.1 代码

创建 scripts/add_two_ints_client.py 文件,并在其中粘贴以下内容:

#!/usr/bin/env python

from __future__ import print_function

import sys
import rospy
from beginner_tutorials.srv import *

def add_two_ints_client(x, y):
    rospy.wait_for_service('add_two_ints')
    try:
        add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
        resp1 = add_two_ints(x, y)
        return resp1.sum
    except rospy.ServiceException as e:
        print("Service call failed: %s"%e)

def usage():
    return "%s [x y]"%sys.argv[0]

if __name__ == "__main__":
    if len(sys.argv) == 3:
        x = int(sys.argv[1])
        y = int(sys.argv[2])
    else:
        print(usage())
        sys.exit(1)
    print("Requesting %s+%s"%(x, y))
    print("%s + %s = %s"%(x, y, add_two_ints_client(x, y)))

不要忘记使节点可执行:

$ chmod +x scripts/add_two_ints_client.py

然后,在 CMakeLists.txt 中编辑 catkin_install_python() 调用,使其如下所示:

catkin_install_python(PROGRAMS scripts/add_two_ints_server.py scripts/add_two_ints_client.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

4.2 代码解释

现在,让我们把代码分解一下。

调用服务的客户端代码也很简单。对于客户端,不必调用 init_node()。我们首先呼叫:

    rospy.wait_for_service('add_two_ints')

这是一种方便的方法,在名为 add_two_ints 的服务可用之前一直阻塞。

接下来,我们创建一个用于调用服务的句柄:

       add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)

我们可以像使用普通函数一样使用此句柄并调用它:

       resp1 = add_two_ints(x, y)
       return resp1.sum

因为我们已经将服务的类型声明为 AddTwoInts,所以它会为您生成 AddTwoIntsRequest 对象(您可以自由地传递自己的对象)。返回值是 AddTwoIntsResponse 对象。如果调用失败,可能会抛出 rospy.ServiceException,因此您应该设置相应的 try/except 块。

results matching ""

    No results matching ""