爱折腾-创客智造实验室 - ROS与Matlab入门教程
通过ROS与Matlab入门教程学习，了解ROS与Matlab如何做交互，（来自 MATLAB-ROS工具箱教程）
说明： ROS与Matlab语言入门教程介绍 本专栏转载《MATLAB-ROS工具箱教程》 来源地址：https://linhuican.gitbooks.io/matlab-ros/content/ 作者邮箱：huicanlin@gmail.com 获得作者授权转载至此.感谢作者提供完善专栏。 目录： ROS网络设置 开始使用ROS 连接到ROS网络 基本ROS消息的使用 使用发布器和订阅器交换数据 专业ROS消息的使用 请求和提供服务 访问ROS参数服务器 rosbag Logfiles的使用 访问ROS中的tf变换树 开始在Simulink中使用ROS 从Simulink连接到ROS使能的机器人 使能ROS机器人的反馈控制 从Simulink中创建单独的ROS节点 在Simulink中使用ROS消息 在复杂环境中进行路径规划 差动机器人的路径跟踪 从距离传感器更新占有网格地图 开始使用实际的TurtleBot 开始使用Gazebo和仿真的TurtleBot TurtleBot的通信 探索TurtleBot的基本行为 使用操纵杆控制TurtleBot 使用TurtleBot进行壁障 目标跟踪 从Gazebo读取模型和仿真性能 在Gazebo中增加、建立和移除对象 在Gazebo中应用力和力矩 在仿真中测试机器人的自主性
设置ROS网络使得不同的设备之间能够通信。不同的参与者或者节点通过注册到同一个ROS主控节点实现信息共享。ROS主控节点是唯一的并且每一个ROS网络只有一个主控节点。虽然一个设备能够运行多个节点，但是通常每个节点对电影一个单独的设备。当与ROS进行通信的时，MATLAB®作为参与其中的一个节点。为使ROS连接可用，所有的设备必须连接到相同的实际或虚拟网络。你可以在MATLAB当中创建一个新的ROS主控节点，或可以连接到一个在其他设备运行着的ROS主控节点。如果你想要连接到一个外部的主控节点，那么你需要知道该设备的IP地址或者主机名。通过调用rosinit能够完成ROS主控节点连接的初始化工作。有关设置和使用ROS网络的更多信息，请参阅Network Connection and Exploration.。 网络连接布局 通过使用被称为发布器、订阅器和服务的实体发送信息来实现数据的传输。发布器通过话题名称发布数据，同时订阅器在网络中通过该话题名称接收数据。服务通过客户端向服务器请求信息实现数据的传输。有关数据传输的更多信息，请参阅Publishers, Subscribers, and Services。 ROS网络连接是所有节点的集合，并且这些节点都连接到ROS主控节点。根据你的应用和设备，节点的数目可以是非常巨大的。当节点注册都主控节点之后，那么该节点与所有其它节点的通信将成为可能。为了与其他节点交换信息，每个节点注册了不懂的发布器、订阅器和服务。虽然在ROS网络中所有的节点都注册到一个主控节点，但是数据的交换可以直接在节点之间进行。（相当于在互联网中，每台机器只要按照一定的协议连接到互联网当中，那么就可以与其它设备任意通信）。下面的图1.1展示了一个具有两个节点的ROS网络。所有节点之间能够在网络中进行双向的数据共享是非常重要的。在设置的过程当中，确认这些连接是非常重要的。 每一个节点向主控节点注册自己的节点URI，每个节点有自己独有的URI。ROS网络中的其它参与者将通过该URI与该节点取得联系。此外，在ROS网络中其它每一个节点都能通过该URI与对应节点通信。调用rosinit可以在MATLAB中创建节点，如果ROS主控节点已经存在，MATLAB能够检测到它并能够恰当的设置该节点的URI。 默认情况下，每一个MATLAB实例拥有一个“全局”节点。该节点被分配给一个随机产生的独一无二的名称。所有的发布器、订阅器、服务终端和服务器都将在这个全局节点运行。
机器人操作系统（Robot Operating System ROS）是一个能够让机器人系统的不同部分发现、传输和接收数据的通信接口。MATLAB®支持ROS是基于一个函数库，该函数库使得你在MATLAB可以和基于ROS的实际机器人或者仿真机器人如Gazebo®进行数据交换。 ROS术语 ROS网络有机器人系统的不同部分组成，比如规划器或者摄像头接口，在ROS上进行通信。网络可以分布在多个机器上。 ROS主控节点负责协调ROS网络的各个部分。通过主控URI（Uniform Resource Identifier）鉴别主控节点，URI指定了主控节点运行的计算机上的宿主名或者IP地址。 ROS节点是包含着与ROS功能有关的集合的实体，比如发布器、订阅器和服务。一个ROS网络可以包含多个ROS节点。 发布器、订阅器和服务是ROS中处理数据的不同实体，它们通过消息交换数据。 发布器将消息传送到指定的话题（比如“里程计”），订阅器通过该话题接收消息。允许多个发布器和订阅器共用一个话题。 更多信息，查阅ROS官网：Robot Operating System (ROS)和Concepts。 初始化ROS网络 使用指令“rosinit”初始化ROS。默认情况下， “rosinit” 指令在MATLAB中创建一个ROS主控节点并开始了一个“global node”节点，该节点与主控节点相连。“global node”节点能够自动的被其它ROS函数使用。代码运行示例： rosinit Initializing ROS master on http://bat5136glnxa64.mathworks.com:11311/. Initializing global node /matlab_global_node_8332 with NodeURI http://bat5136glnxa64:58596/ 使用指令“rosnode list”查看ROS网络中的所有节点，注意，这里只有由“rosinit”创建的一个全局节点。代码运行示例： rosnode list /matlab_global_node_8332 使用指令“exampleHelperROSCreateSampleNetwork”给ROS网络添加三个节点以及采样的发布器和订阅器，代码运行示例： exampleHelperROSCreateSampleNetwork 再次使用“rosnode list”指令，可以观察到新增加了三个节点（node_1，node_2和node_3），代码运行示例： rosnode list /matlab_global_node_8332 /node_1 /node_2 /node_3 当前ROS网络的视觉可视化如图2.1所示，接下来探索该例子过程中，可以结构图为参考。当前MATLAB的全局节点（/matlab_global_node）由于没有任何发布器、订阅器或服务，因此处于离线状态。 话题 使用“rostopic list”指令查看ROS网络中可获得的话题，可以观察到/pose, /rosout, and /scan三个话题。其中，rosout是默认的记录话题，它在ROS网络中总是存在的。另外两个话题作为采样网络而创建。代码运行示例： rostopic list /pose /rosout /scan 使用“rostopic info”指令获得指定话题的相关信息，下面的指令显示“/node_1”发布“/pose”话题，而“node_2”订阅该话题。代码运行示例： rostopic info /pose Type: geometry_msgs/Twist Publishers: * /node_1 (http://bat5136glnxa64:51246/) Subscribers: * /node_2 (http://bat5136glnxa64:55141/) 使用“rosnode info”指令获得指令节点的信息，下面的指令显示节点“node_1”发布数据到话题“/pose”和“/rosout”，同时订阅了话题“/scan”的数据。代码运行示例： rosnode info /node_1 Node: [/node_1] URI: [http://bat5136glnxa64:51246/] Publications (2 Active Topics): * /pose * /rosout Subscriptions (1 Active Topics): * /scan Services (0 Active): 服务 ROS服务提供了ROS网络中的“程序调用”的机制。服务客户端向服务器发送请求信息，服务器处理请求信息并返回响应信息。（查看Calling and Providing ROS Services） 使用“rosservice list”指令查看ROS网络中所有可用的服务。运行下面的指令显示有两个可用的服务（/add和/reply）。 rosservice list /add /reply 使用“rosservice info”指令获取指定服务的信息。 rosservice info /add Node: /node_3 URI: rosrpc://bat5136glnxa64:47000/ Type: roscpp_tutorials/TwoInts Args: A B 消息 发布器、订阅器和服务使用ROS消息进行信息交换。每个ROS消息拥有一个相关的消息类型，定义了消息中的数据类型和数据结构。（查看 Working with Basic ROS Messages） 使用“rostopic type”指令查看话题使用的消息类型，下面的指令显示了“/pose”话题使用了消息类型为“geometry_msgs/Twist”。代码运行示例： rostopic type /pose geometry_msgs/Twist 使用“rosmsg show”指令查看消息类型的属性，“geometry_msgs/Twist”消息类型有两种属性，分别是“Linear”和“Angular”。每个属性是一个“geometry_msgs/Vector3”类型的消息，该类型消息由三个属性为“double”的数据组成。代码运行示例： rosmsg show geometry_msgs/Twist % This expresses velocity in free space broken into its Linear and Angular parts. Vector3 Linear Vector3 Angular rosmsg show geometry_msgs/Vector3 % This represents a vector in free space. double X double Y double Z 使用“rosmsg list”指令查看MATLAB中所有可用的消息类型，代码运行示例： rosmsg list ackermann_msgs/AckermannDrive ackermann_msgs/AckermannDriveStamped actionlib_msgs/GoalID actionlib_msgs/GoalStatus actionlib_msgs/GoalStatusArray adhoc_communication/BroadcastCMgrRobotUpdate adhoc_communication/BroadcastCMgrRobotUpdateRequest adhoc_communication/BroadcastCMgrRobotUpdateResponse adhoc_communication/BroadcastString adhoc_communication/BroadcastStringRequest adhoc_communication/BroadcastStringResponse adhoc_communication/CMgrDimensions adhoc_communication/CMgrRobotUpdate adhoc_communication/ChangeMCMembership adhoc_communication/ChangeMCMembershipRequest adhoc_communication/ChangeMCMembershipResponse adhoc_communication/ExpAuction adhoc_communication/ExpCluster adhoc_communication/ExpFrontier adhoc_communication/ExpFrontierElement adhoc_communication/GetGroupState adhoc_communication/GetGroupStateRequest adhoc_communication/GetGroupStateResponse adhoc_communication/GetNeighbors adhoc_communication/GetNeighborsRequest adhoc_communication/GetNeighborsResponse adhoc_communication/MmControl adhoc_communication/MmListOfPoints adhoc_communication/MmMapUpdate adhoc_communication/MmPoint ... 关闭ROS网络 使用“exampleHelperROSShutDownSampleNetwork”指令从ROS网络中移除采样节点、发布器和订阅器。该指令仅在采样节点已经创建的情况下需要使用。 使用“rosshutdown”指令关闭MATLAB中的ROS网络，该指令关闭了由“rosinit”指令创建的ROS主控节点并删除全局节点。建议你在完成ROS网络相关工作后使用“rosshutdown”指令。运行代码示例： rosshutdown Shutting down global node /matlab_global_node_8332 with NodeURI http://bat5136glnxa64:58596/ Shutting down ROS master on http://bat5136glnxa64.mathworks.com:11311/.
一个ROS网络包含一个ROS主控节点和多个ROS节点，ROS主控节点通过与所有活动的ROS实体保持联系使得ROS网络能够通信。为能够与网络中的其它节点通信，每个节点都需要向ROS主控节点注册。MATLAB®可以启动ROS主控节点或者由MATLAB之外如其它计算机来启动主控节点。所有的节点都向主控节点注册并确认该节点在网络中的地址。 当你使用ROS时，通常需要如下步骤： 连接到ROS网络。为连接到ROS网络，你可以在MATLAB中创建ROS主控节点或者连接到已存在的ROS主控节点。这两种情况，MATLAB都需要创建自己的ROS节点（称作MATLAB“global node”）并注册到ROS主控节点。 交换数据。一旦连接到ROS网络，MATLAB通过发布器、订阅器和服务与其它ROS节点进行数据交换。 断开与ROS网络的连接。调用“rosshutdown”指令断开MATLAB与ROS网络的连接。 在MATLAB中创建ROS主控节点。 连接到外部的ROS主控节点。 在MATLAB中创建ROS主控节点 在MATLAB中创建ROS主控节点，调用“rosinit”指令，不需要任何参数。这也会创建“global node”节点，该节点用于与ROS网络中的其它节点进行通信。 此时，MATLAB外部的ROS节点可以连接到ROS网络中，外部节点可以通过使用运行MATLAB的计算机的宿主名或者IP地址连接到MATLAB上的ROS主控节点。 你可以通过调用“rosshutdown”指令关闭ROS主控节点和全局节点。 连接到外部的ROS主控节点 你还可以使用“rosinit”命令连接到外部的ROS主控节点（如运行在机器人上或者虚拟设备上）。两种指定主控节点地址的方法：运行主控节点的计算机的IP地址或者宿主名。 当运行过“rosinit”指令之后，再重新运行“rosinit”指令之前，需要先调用“rosshutdown”指令。简洁起见，下文将省略调用“rosshutdown”指令的步骤。 在这个例程中，以“master_host”作为外部主控节点的宿主名称，以“192.168.1.1”作为外部主控节点的IP地址。使用的时候需要根据外部主控节点的实际情况调整宿主名或者IP地址。注意，下面的指令可能运行失败，因为在指定的地址无法找到主控节点。 rosinit('192.168.1.1') rosinit('master_host') 两种方式调用“rosinit”都假定主控节点通过端口11311连接到网络，该端口是标准的ROS主控节点端口。 如果一个主控节点运行在不同的端口，那么你可以采用第二个参数的形式指定端口。例如，为了连接到一个运行在宿主名为“master_node”端口为12000的主控节点，使用下面的指令： rosinit('master_host', 12000) 如果你了解主控节点的完整URI（Uniform Resource Identifier），你可以使用如下的指令创建全局节点并连接到该主控节点： rosinit('http://192.168.1.1:12000') 节点主机详述 在一些情况下，你的计算机可能连接到多个网络，拥有多个IP地址。看下图所示的例子。 左下角运行MATLAB的计算机连接了两个网络，在一个子网络中，它的IP地址是73.195.120.50，另一个子网络中它的IP地址是192.168.1.100。这个计算机连接到ROS主控节点上，该节点运行在IP地址为192.168.1.1的TurtleBot ®计算机上。作为注册到主控节点的一部分，MATLAB的全局节点需要指定自己的宿主名或者IP地址，这样其它ROS节点才能够与MATLAB上的节点进行数据交换。 以主控节点IP地址为参数调用“rosinit”指令，系统将会尝试检测与主控节点连接的网络接口，并作为MATLAB的全局节点的IP地址。 如果自动检测失败，你需要通过“rosinit”指令的“NodeHost”名称值明确地指定IP地址或者宿主名。所有的优先的调用“rosinit”的方法，在增加“NodeHost”名称值对的情况下仍然可用。 如下的指令，假定你想要将自己计算机的IP通知给IP地址为168.1.100的ROS网络： rosinit('192.168.1.1', 'NodeHost', '192.168.1.100') rosinit('http://192.168.1.1:11311', 'NodeHost', '192.168.1.100') rosinit('master_host', 'NodeHost', '192.168.1.100') 一旦节点注册到ROS网络，你可以通过使用“rosnode info NODE”指令查看地址。指令中的“NODE”是ROS网络中的节点名称。你可以通过“rosnode list”指令查看所有注册节点的名称。 ROS环境变量 在高级应用中，你可能想要通过标准的ROS环境变量指定ROS主控节点的地址以及你发布的节点的地址。上述章节介绍的调用语法，应该能够满足你大部分的应用。 如果没有为“rosinit”指令指定任何参数，该函数仍会检测标准ROS环境变量的值，包括ROS_MASTER_URI，ROS_HOSTNAME和 ROS_IP。使用“getenv”指令你可以看到它们的当前值。运行示例如下： getenv('ROS_MASTER_URI') getenv('ROS_HOSTNAME') getenv('ROS_IP') 你还可以通过使用“setenv”指令设定上述环境变量的值。设定环境变量后，不带参数的调用“rosinit”指令，ROS主控节点的地址被“ROS_MASTER_URI”指定，全局变量的地址由“ROS_IP”或“ROS_HOSTNAME”给定。如果为“rosinit”指令附加参数，那么相应的环境变量值将被更新。代码运行示例： setenv('ROS_MASTER_URI','http://192.168.1.1:11311') setenv('ROS_IP','192.168.1.100') rosinit 连接核实 为让ROS连接能够正确工作，你必须确保所有的节点能够与主控节点通信，并且节点之间能够有效通信。所有独立节点必须能够与主控节点通信，才能够注册订阅器、发布器和服务，它们还必须能够彼此通信，才能够发送和接收数据。如果ROS网络没有正确设置，那么节点之间的通信可能只能发送不能接收或者只能接收不能发送。 下图是ROS网络通信的示意图。图中有一个主控节点和两个不同的节点注册到主控节点，每一个节点都能连接主控节点，进而发现ROS网络中发布的其它节点的地址。节点之间互相知道对方的地址，数据交换可以在主控节点不参与的情况下进行。
在ROS数据交换中消息是最主要的容器，主题（通过发布器和订阅器交换数据）和服务（请求和提供服务）都是使用消息在节点之间传递数据。每个消息都有消息类型来确认数据结构，例如，激光扫描仪的传感器数据通常以“typesensor_msgs/LaserScan”的消息类型传递。每一个消息类型都确定了包含在消息中的数据元素。每个消息类型的名称都由包名、斜杠“/”和类型名组成 MATLAB支持在机器人学应用领域常见的许多ROS消息类型，在本例中，你将会在MATLAB中尝试几种创建、探索和发布ROS消息的方法。预备知识：1.2开始使用ROS和1.3连接到ROS网络。 查找消息类型 初始化ROS主控节点和全局变量。代码运行如下： rosinit Initializing ROS master onhttp://bat5136glnxa64.mathworks.com:11311/. Initializing global node /matlab_global_node_8332 with NodeURIhttp://bat5136glnxa64:33534/ 使用“exampleHelperROSCreateSampleNetwork”指令给ROS网络填充三个节点和发布器、订阅器例子。代码运行示例： exampleHelperROSCreateSampleNetwork 现在系统中有了若干不同的节点和主题以及相关的发布器和订阅器。你可以使用“rostopic list”查看所有可用的主题，“/scan”存在主题列表当中。 rostopic list /pose /rosout /scan 如果你想要进一步了解通过“/scan”主题传递的数据类型，可以使用“rostopic info/scan”指令检查。“/scan”的消息类型是“sensor_msgs/LaserScan”。代码运行示例： scandata = rosmessage('sensor_msgs/LaserScan') scandata = ROS LaserScan message with properties: MessageType: 'sensor_msgs/LaserScan' Header: [1x1 Header] AngleMin: 0 AngleMax: 0 AngleIncrement: 0 TimeIncrement: 0 ScanTime: 0 RangeMin: 0 RangeMax: 0 Ranges: [0x1 single] Intensities: [0x1 single] 创建的消息“scandata”拥有许多与激光扫描仪接收的数据相关的属性，例如，最小的传感器距离存在“RangeMin”属性，最大传感器距离存储在“RangeMax”属性。 如果你想要定义消息的类型，可以使用“rostype”指令，该指令可以方便的使用一系列消息类型。“rostype”支持tap键填充指令并能够寻找与先前的字符创匹配的消息类型。代码运行示例： scantype = rostype.sensor_msgs_LaserScan scantype = sensor_msgs/LaserScan 使用这个字符串，可以创建一个与之前的消息相同类型的空消息： scandata = rosmessage(scantype) scandata = ROS LaserScan message with properties: MessageType: 'sensor_msgs/LaserScan' Header: [1x1 Header] AngleMin: 0 AngleMax: 0 AngleIncrement: 0 TimeIncrement: 0 ScanTime: 0 RangeMin: 0 RangeMax: 0 Ranges: [0x1 single] Intensities: [0x1 single] 使用“rosmsg list”查看主题和服务所能使用的所有消息类型： rosmsg list ackermann_msgs/AckermannDrive ackermann_msgs/AckermannDriveStamped actionlib_msgs/GoalID actionlib_msgs/GoalStatus actionlib_msgs/GoalStatusArray adhoc_communication/BroadcastCMgrRobotUpdate adhoc_communication/BroadcastCMgrRobotUpdateRequest adhoc_communication/BroadcastCMgrRobotUpdateResponse adhoc_communication/BroadcastString adhoc_communication/BroadcastStringRequest adhoc_communication/BroadcastStringResponse adhoc_communication/CMgrDimensions adhoc_communication/CMgrRobotUpdate adhoc_communication/ChangeMCMembership adhoc_communication/ChangeMCMembershipRequest adhoc_communication/ChangeMCMembershipResponse adhoc_communication/ExpAuction adhoc_communication/ExpCluster adhoc_communication/ExpFrontier adhoc_communication/ExpFrontierElement adhoc_communication/GetGroupState adhoc_communication/GetGroupStateRequest adhoc_communication/GetGroupStateResponse adhoc_communication/GetNeighbors adhoc_communication/GetNeighborsRequest adhoc_communication/GetNeighborsResponse adhoc_communication/MmControl adhoc_communication/MmListOfPoints adhoc_communication/MmMapUpdate adhoc_communication/MmPoint 探索消息结构和获取消息数据 ROS消息是对象，消息数据存储在属性里，MATLAB提供了方便的方法发现和探索消息里的内容。如果你要订阅了“/scan”主题，你可以接收和检测被传输的数据。代码运行示例： posesub = rossubscriber('/pose') posesub = Subscriber with properties: posesub = rossubscriber('/pose') TopicName: '/pose' MessageType: 'geometry_msgs/Twist' LatestMessage: [0x1 Twist] BufferSize: 1 NewMessageFcn: [] 使用“receive”函数，能够从订阅器获得数据，一旦接收到新的消息，函数返回并存储数据到“posedata”变量。代码运行示例： posedata = receive(posesub, 10) posedata = ROS Twist message with properties: MessageType: 'geometry_msgs/Twist' Linear: [1x1 Vector3] Angular: [1x1 Vector3] 消息的类型是“geometry_msgs/Twist”，消息中含有两个字段：“Linear”和“Angular”，你可以直接地获取消息中这些字段的值，代码运行示例： posedata.Linear ans = ROS Vector3 message with properties: MessageType: 'geometry_msgs/Vector3' X: 0.0438 Y: 0.0385 Z: 0.0520 posedata.Angular ans = ROS Vector3 message with properties: MessageType: 'geometry_msgs/Vector3' X: -0.0288 Y: 0.0484 Z: 0.0528 显然，消息中的字段的每一个数值事实上本身也是个消息，消息类型是“geometry_msgs/Vector3”。“ geometry_msgs/Twist”的消息由两个“geometry_msgs/Vector3”类型的消息组成。获得这些嵌套的消息的方法其实和获得其它消息一样，例如，获得“Linear”的组成“X”的指令： xpos = posedata.Linear.X xpos = 0.0438 “showdetails”函数可以快速的查看消息中包含的全部数据，“showdetails”对所有消息类型均可用，并且循环地显示消息的数据属性。 showdetails(posedata) Linear X : 0.0438049105 Y : 0.03847741206 Z : 0.052015073 Angular X : -0.02882663895 Y : 0.04838687119 Z : 0.05278724967 “showdetails”能够帮助你在调试的时候，快速地查看消息中的内容。 设定消息数据 你还能够设定消息属性的值，创建“geometry_msgs/Twist”类型的消息： twist = rosmessage(rostype.geometry_msgs_Twist) twist = ROS Twist message with properties: MessageType: 'geometry_msgs/Twist' Linear: [1x1 Vector3] Angular: [1x1 Vector3] 消息的属性值会被默认设置为“0”，你可以任意的修改消息的属性值，例如设置“Linear.Y”的值为5。 twist.Linear.Y = 5; 显示消息的数据确认所做的修改的效果： twist.Linear ans = ROS Vector3 message with properties: MessageType: 'geometry_msgs/Vector3' X: 0 Y: 5 Z: 0 一旦消息被填充数据，你可以通过“publishers”，“ subscribers”，“ services”使用消息。查看通过发布器和订阅器交换数据和请求和提供服务的例子。 复制消息 有两种方式复制消息中的内容： ① 可以创建一个“reference copy”，复制的消息和原消息共享同样的数据 ② 可以创建一个“deep copy”，复制的消息和原消息拥有各自的消息。 当想要在不同的函数和对象之间共享消息数据时，“reference copy”是非常有用的，而如果你想要独立的复制消息，“deep copy”是必须的方法。 使用赋值符“=”创建“reference copy”，这种方法创建的变量与原变量拥有一样的消息内容。代码运行示例： twistCopyRef = twist twistCopyRef = ROS Twist message with properties: MessageType: 'geometry_msgs/Twist' Linear: [1x1 Vector3] Angular: [1x1 Vector3] 修改“twistCopyRef”的字段“Linear.Z”，发现“twist”中的相应字段的内容也发生变化： twistCopyRef.Linear.Z = 7; twist.Linear ans = ROS Vector3 message with properties: MessageType: 'geometry_msgs/Vector3' X: 0 Y: 5 Z: 7 创建一个“twist”的“deep copy”，这样在改变复制消息的内容的时候不会影响原数据。使用“copy”指令创建新消息“twistCopyDeep”： twistCopyDeep = copy(twist) twistCopyDeep = ROS Twist message with properties: MessageType: 'geometry_msgs/Twist' Linear: [1x1 Vector3] Angular: [1x1 Vector3] 修改“twistCopyDeep”消息的字段“Linear.X”的内容，注意到“twist”中相应的字段没有变化： twistCopyDeep.Linear.X = 100; twistCopyDeep.Linear ans = ROS Vector3 message with properties: MessageType: 'geometry_msgs/Vector3' X: 100 Y: 5 Z: 7 twist.Linear ans = ROS Vector3 message with properties: MessageType: 'geometry_msgs/Vector3' X: 0 Y: 5 Z: 7 保存和下载消息 你可以保存消息的内容用于后期使用。 从订阅器获得新消息： posedata = receive(posesub,10) posedata = ROS Twist message with properties: MessageType: 'geometry_msgs/Twist' Linear: [1x1 Vector3] Angular: [1x1 Vector3] 使用MATLAB的“save”函数，保存姿态数据到.mat文件。 save('posedata.mat','posedata') 在重新加载文件到工作空间之前，清除“posedata”变量： clear posedata 现在可以调用“load”函数载入消息数据，上述“posedata”数据将被载入“messageData”结构中，“posedata”是该结构的数据字段： messageData = load('posedata.mat') messageData = posedata: [1x1 Twist] 检查“messageData.posedata”查看消息的内容： messageData.posedata ans = ROS Twist message with properties: MessageType: 'geometry_msgs/Twist' Linear: [1x1 Vector3] Angular: [1x1 Vector3] 删除MAT文件的方法如下： delete('posedata.mat') 消息中的对象阵列 ROS中的一些消息存放在对象阵列中，这与典型的数据阵列不同。 在工作空间中，“tf”变量包含了一些样本消息（“exampleHelperROSCreateSampleNetwork”脚本创建该变量），本例中，这是一个表示坐标变换的： tf tf = ROS tfMessage message with properties: MessageType: 'tf/tfMessage' Transforms: [53x1 TransformStamped] “tf”拥有两个字段：“MessageType”包含了标准的数据阵列，“Transforms”包含了对象阵列。“Transforms”里包含了53个对象，每一个对象具有相同的结构。 展开“tf”的“Transforms“查看其结构： tf.Transforms ans = 53x1 ROS TransformStamped message array with properties: MessageType Header ChildFrameId Transform 从代码的运行输出可以看出，“Transforms”里的每一个对象都有四个属性，查看其中的“Transform”字段： tf.Transforms.Transform ans = ROS Transform message with properties: MessageType: 'geometry_msgs/Transform' Translation: [1x1 Vector3] Rotation: [1x1 Quaternion] Use showdetails to show the contents of the message ans = ROS Transform message with properties: MessageType: 'geometry_msgs/Transform' Translation: [1x1 Vector3] Rotation: [1x1 Quaternion] Use showdetails to show the contents of the message ans = ROS Transform message with properties: MessageType: 'geometry_msgs/Transform' Translation: [1x1 Vector3] Rotation: [1x1 Quaternion] ... 注意，得到53个独立的输出，因为每一个对象都被评估并返回各自的“Transform”字段的值。这种格式并不总是有效，你可以转换如下的方法： cellTransforms = {tf.Transforms.Transform} cellTransforms = Columns 1 through 4 [1x1 Transform] [1x1 Transform] [1x1 Transform] [1x1 Transform] Columns 5 through 8 [1x1 Transform] [1x1 Transform] [1x1 Transform] [1x1 Transform] Columns 9 through 12 [1x1 Transform] [1x1 Transform] [1x1 Transform] [1x1 Transform] Columns 13 through 16 [1x1 Transform] [1x1 Transform] [1x1 Transform] [1x1 Transform] Columns 17 through 20 [1x1 Transform] [1x1 Transform] [1x1 Transform] [1x1 Transform] Columns 21 through 24 [1x1 Transform] [1x1 Transform] [1x1 Transform] [1x1 Transform] Columns 25 through 28 [1x1 Transform] [1x1 Transform] [1x1 Transform] [1x1 Transform] ... 该方法将53个对象实体放到一个单元阵列中，这允许用户通过索引的方式访问每个对象实体。 此外，用户还可用访问标准的MATLAB向量的方法访问对象阵列的元素。 tf.Transforms(5) ans = ROS TransformStamped message with properties: MessageType: 'geometry_msgs/TransformStamped' Header: [1x1 Header] ChildFrameId: '/imu_link' Transform: [1x1 Transform] 用户可以通过下述方法访问单个阵列元素的属性： tf.Transforms(5).Transform.Translation ans = ROS Vector3 message with properties: MessageType: 'geometry_msgs/Vector3' X: 0.0599 Y: 0 Z: -0.0141 关闭ROS网络 从ROS网络中移除示例节点、发布器、订阅器： exampleHelperROSShutDownSampleNetwork 关闭ROS主控节点并删除全局节点： rosshutdown Shutting down global node /matlab_global_node_8332 with NodeURIhttp://bat5136glnxa64:33534/Shutting down ROS master onhttp://bat5136glnxa64.mathworks.com:11311/
ROS与Matlab语言入门教程-使用发布器和订阅器交换数据
ROS节点最基本的交换数据的方式是发送和接收消息。消息通过主题传输，在ROS网络中，每一个主题都有唯一的名称。如果一个节点想要共享数据，那么它就要使用发布器将数据发送到主题。一个节点想要接收数据，需要使用接收器监听相应的主题。除了具有唯一的名称，每个主题都具有消息类型，决定主题所能传输的消息类型。 发布器/接收器具有如下特征： 主题用于多对多通信，多个发布器可以发送消息到同样的主题，多个接收器也可以接收同样的主题的数据。 发布器和接收器与主题是解耦的不挂钩的，它们可以用任何规则创建或者销毁。在主题没有接收器的情况下，消息仍然可以发送到主题上。 主题、发布器和接收器的概念如下图所示： 这个例子展示了在ROS网络中怎样发布和订阅主题，另外还展示了： 等待直到接收新的消息。 使用后台调用的形式处理新消息。 需要的预备知识：1.2开始使用ROS，1.3连接到ROS网络。 订阅和等待消息 在MATLAB®中启动ROS主控节点，创建一个带有若干发布器和接收器的采样ROS网络。代码运行示例： rosinit exampleHelperROSCreateSampleNetwork Initializing ROS master onhttp://bat5136glnxa64.mathworks.com:11311/. Initializing global node /matlab_global_node_8332 with 使 用“rostopic list”指令查看可用的主题，假设你想要订阅的主题是“/scan”。代码运行示例：NodeURIhttp://bat5136glnxa64:35671/ rostopic list /pose /rosout /scan 使用“rostopic info”指令检查是否有节点发布数据到“/scan”主题，下面的指令显示“node_3”节点发布数据到“/scan”主题。 rostopic info /scan Type: sensor_msgs/LaserScan Publishers: /node_3 (http://bat5136glnxa64:53950/) Subscribers: /node_1 (http://bat5136glnxa64:49726/) /node_2 (http://bat5136glnxa64:59432/) 使用“rossubscriber”指令订阅“/scan”主题，如果该主题已经存在于ROS网络中（如本例），“rossubscriber”会自动的检测消息类型，你无须指定。代码运行示例： laser = rossubscriber('/scan') laser = Subscriber with properties: TopicName: '/scan' MessageType: 'sensor_msgs/LaserScan' LatestMessage: [0x1 LaserScan] BufferSize: 1 NewMessageFcn: [] 使用“receive”指令等待新消息（第二个参数是超时时间（s）），输出“scandata”包含了接收的数据。代码运行示例： scandata = receive(laser,10) scandata = ROS LaserScan message with properties: MessageType: 'sensor_msgs/LaserScan' Header: [1x1 Header] AngleMin: -0.5216 AngleMax: 0.5243 AngleIncrement: 0.0016 TimeIncrement: 0 ScanTime: 0.0330 RangeMin: 0.4500 RangeMax: 10 Ranges: [640x1 single] Intensities: [0x1 single] 一些数据类型有与之相关的方便的可视化的方式，对于LaserScan数据，“plot”指令能够绘制曲线图，其中“MaximumRange”指定了曲线的最大值范围。运行示例： plot(scandata,'MaximumRange',7) 使用回调函数订阅数据 你可以指定一个回调函数接收新的消息，而不必采用“receive”指令的方法。这种方法使得其它MATLAB代码能够在订阅器等待新消息的同时继续执行。如果想要使用多个订阅器，那么回调是必须的方法。 使用“exampleHelperROSPoseCallback”回调函数，订阅“/pose”主题。一种在主工作空间和回调函数中共享数据的方法是采用全局变量。定义两个全局变量“pos”和“orient”。代码运行示例： robotpose = rossubscriber('/pose',@exampleHelperROSPoseCallback) global pos global orient robotpose = Subscriber with properties: TopicName: '/pose' MessageType: 'geometry_msgs/Twist' LatestMessage: [0x1 Twist] BufferSize: 1 NewMessageFcn: @exampleHelperROSPoseCallback 从“/pose”主题中接收到新消息时，全局变量“pos”和“orient”将在“exampleHelperROSPoseCallback”回调函数中赋值。 等待几秒钟，确认订阅器能够接收到消息，当前的位置和方向数据，将总是存放在 “pos”和“orient” 变量。代码运行示例： pause(2) pos orient pos = -0.0725 -0.0805 -0.0628 orient = 0.0364 0.1791 0.0041 如果在命令行里输入几次的“pos”和“orient”，将会看到相应的数据不断的更新。 清除订阅器变量的方式停止位姿订阅器。代码运行示例： clear robotpose 注意：除了使用全局变量，还有很多种方法从回调函数中提取信息，例如，可以传递一个句柄对象作为附加的参数到回调函数中。关于回调函数的定义详见Callback Definition文档。 发布消息 创建一个发布器，用于发送ROS字符串消息到“/chatter”主题（下回分解）。代码运行示例： chatterpub = rospublisher('/chatter',rostype.std_msgs_String) pause(2) % Wait to ensure publisher is setup chatterpub = Publisher with properties: TopicName: '/chatter' IsLatching: 1 NumSubscribers: 0 MessageType: 'std_msgs/String' 使用“rostopic list”命令，确认“/chatter”主题在ROS网络中是可用的。代码运行示例： rostopic list /chatter /pose /rosout /scan 为“/chatter”主题定义一个订阅器，当接收到新的消息时，“exampleHelperROSChatterCallback”将会被调用，显示消息中的字符串内容。代码运行示例： chattersub = rossubscriber('/chatter', @exampleHelperROSChatterCallback) chattersub = Subscriber with properties: TopicName: '/chatter' MessageType: 'std_msgs/String' LatestMessage: [0x1 String] BufferSize: 1 NewMessageFcn: @exampleHelperROSChatterCallback 创建并填充一个ROS消息，用于发送到“chatter”主题。 chattermsg = rosmessage(chatterpub); chattermsg.Data = 'hello world' chattermsg = ROS String message with properties: MessageType: 'std_msgs/String' Data: 'hello world' Use showdetails to show the contents of the message 发布一个消息到“/chatter”主题，观察到字符串被订阅器回调函数显示。 send(chatterpub,chattermsg) pause(2) Chatter Callback message data: ans = hello world 一旦你发布字符串数据，“exampleHelperROSChatterCallback”函数就会被调用。 关闭ROS网络 从ROS网络中移除采样节点、发布器和订阅器，同时清除全局变量“pos”和“orient”。代码运行示例： exampleHelperROSShutDownSampleNetwork clear global pos orient 关闭ROS主控节点并删除全局节点。代码运行示例： rosshutdown Shutting down global node /matlab_global_node_8332 with NodeURIhttp://bat5136glnxa64:35671/ Shutting down ROS master onhttp://bat5136glnxa64.mathworks.com:11311/.
一些常见的ROS消息存储的格式，后期在使用之前需要进行转换。MATLAB能够帮助你转换常见的ROS消息格式，有利于简化应用。在本例，你可以探索激光扫描仪的消息类型，有关解压和压缩图片以及点云方面的处理工作。 预备知识：基本ROS消息的使用。 载入消息实例 首先，载入本例将使用的消息样例，这些消息来自多种机器人传感器。调用“exampleHelperROSLoadMessages”载入消息： exampleHelperROSLoadMessages 激光扫描仪消息 在机器人学领域，激光扫描仪是常见的传感器，通过创建合适类型的空消息，你可以看到ROS中激光扫描仪的标准消息格式。 使用“rosmessage”创建消息： emptyscan = rosmessage(rostype.sensor_msgs_LaserScan) emptyscan = ROS LaserScan message with properties: MessageType: 'sensor_msgs/LaserScan' Header: [1x1 Header] AngleMin: 0 AngleMax: 0 AngleIncrement: 0 TimeIncrement: 0 ScanTime: 0 RangeMin: 0 RangeMax: 0 Ranges: [0x1 single] Intensities: [0x1 single] 由于你创建的是空消息，因此“emptyscan”没有包含任何有意义的数据。方便起见，“exampleHelperROSLoadMessages”函数载入了激光扫描仪的消息，该消息已经填充好数据并存储在“scan”变量中。检测“scan”变量，消息中最主要的数据时“Ranges”字段，“Ranges”中的数据时一个640*1的以很小的角度增量存储障碍距离阵列。 scan scan = ROS LaserScan message with properties: MessageType: 'sensor_msgs/LaserScan' Header: [1x1 Header] AngleMin: -0.5467 AngleMax: 0.5467 AngleIncrement: 0.0017 TimeIncrement: 0 ScanTime: 0.0330 RangeMin: 0.4500 RangeMax: 10 Ranges: [640x1 single] Intensities: [0x1 single] 使用“readCartesian”函数，使用者可以获得笛卡尔坐标系下的测量点。 xy = readCartesian(scan) xy = 0.7886 -0.3803 0.7887 -0.3786 0.7907 -0.3780 0.7908 -0.3763 0.7909 -0.3747 0.7929 -0.3740 0.7924 -0.3721 0.7924 -0.3705 0.7945 -0.3698 0.7945 -0.3682 0.7966 -0.3675 0.7966 -0.3659 0.7967 -0.3642 0.7977 -0.3631 0.7978 -0.3615 0.7978 -0.3598 0.7979 -0.3582 0.7999 -0.3575 0.7974 -0.3547 0.7975 -0.3531 0.7995 -0.3524 0.7995 -0.3508 0.8036 -0.3509 0.8016 -0.3484 0.8017 -0.3468 0.5188 -0.2234 0.5168 -0.2215 ... 该函数返回一系列在有效测量范围计算的[x,y]坐标。 用户可以使用“plot”函数可视化扫描消息。 plot(scan,'MaximumRange',5) 图像消息 MATLAB为图像消息提供了支持，消息格式总是“sensor_msgs/Image”。 使用“rosmessage”指令创建一个空消息，查看标准的ROS图像消息格式。 emptyimg = rosmessage(rostype.sensor_msgs_Image) emptyimg = ROS Image message with properties: MessageType: 'sensor_msgs/Image' Header: [1x1 Header] Height: 0 Width: 0 Encoding: '' IsBigendian: 0 Step: 0 Data: [0x1 uint8] 简化起见，“exampleHelperROSLoadMessages”函数载入了图像消息，该消息已经填有数据并存储在“img”变量中。 检查工作空间中的消息变量“img”，图片的尺寸信息存储在“Width”和“Height”属性中，ROS使用一维阵列传输真正的图像数据，存放在“Data”字段。 img img = ROS Image message with properties: MessageType: 'sensor_msgs/Image' Header: [1x1 Header] Height: 480 Width: 640 Encoding: 'rgb8' IsBigendian: 0 Step: 1920 Data: [921600x1 uint8] “Data”字段存储的是原始的图像数据，在MATLAB中并不能用于直接处理或可视化，用户可以使用“readImage”函数恢复出与MATLAB兼容的图像格式。 imageFormatted = readImage(img); 原始的图像的编码格式为“rgb8”，默认情况下，“readImage”函数返回一个标准的4806403的uint8的格式，用户可以使用“imshow”函数查看图像。 imshow(imageFormatted) 图6.2 MATLAB支持所有的ROS图像编码格式，“readImage”函数处理转换图像的复杂工作，除了彩色图像，MATLAB还支持单色图像和深度图像。 压缩的消息 许多ROS系统以压缩的格式传输图像数据，MATLAB提供对这些压缩图像消息的支持。 使用“rosmessage”创建一个空的压缩图像消息，在ROS中的压缩图像的消息类型是“sensor_msgs/CompressedImage”，拥有标准的格式。 emptyimgcomp = rosmessage(rostype.sensor_msgs_CompressedImage) emptyimgcomp = ROS CompressedImage message with properties: MessageType: 'sensor_msgs/CompressedImage' Header: [1x1 Header] Format: 'bgr8; jpeg compressed bgr8' Data: [30376x1 uint8] 与图像消息类似，用户可以使用“readImage”函数标准的RGB格式的图像，即使压缩图像的原始编码格式是bgr8，“readImage”也会做相应的转换。 compressedFormatted = readImage(imgcomp); 用户可用使用“imshow”函数可视化图像。 imshow(compressedFormatted) 图6.3 大部分的图像格式都支持压缩图像类型，“16UC1”和“32FC1”编码不支持压缩深度图像，单色和彩色图像编码支持压缩图像类型。 点云 点云可用由机器人领域的多种传感器采集，如LIDARS、Kinect和立体摄像机。ROS中最常用的传输点云的消息类型是“sensor_msgs/PointCloud2”，MATLAB提供了一些专业的函数供用户使用点云数据。 用户可以通过创建合适类型的空消息查看ROS的标准点云格式： emptyptcloud = rosmessage(rostype.sensor_msgs_PointCloud2) emptyptcloud = ROS PointCloud2 message with properties: PreserveStructureOnRead: 0 MessageType: 'sensor_msgs/PointCloud2' Header: [1x1 Header] Height: 0 Width: 0 IsBigendian: 0 PointStep: 0 RowStep: 0 IsDense: 0 Fields: [0x1 PointField] Data: [0x1 uint8] 查看工作空间中的已经填充数据的点云消息，该消息存储在“ptcloud”变量中： ptcloud ptcloud = ROS PointCloud2 message with properties: PreserveStructureOnRead: 0 MessageType: 'sensor_msgs/PointCloud2' Header: [1x1 Header] Height: 480 Width: 640 IsBigendian: 0 PointStep: 32 RowStep: 20480 IsDense: 0 Fields: [4x1 PointField] Data: [9830400x1 uint8] 点云信息编码在消息的“Data”字段，用户可以通过调用“readXYZ”函数提取[x,y,z]坐标，存储在为N*3的矩阵。 xyz = readXYZ(ptcloud) xyz = NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... 点云数据中的NaN表明一些[x,y,z]的值无效。这是Kinect传感器的一种伪像，用户可以安全的移除所有的NaN值： xyzvalid = xyz(~isnan(xyz(:,1)),:) xyzvalid = 0.1378 -0.6705 1.6260 0.1409 -0.6705 1.6260 0.1433 -0.6672 1.6180 0.1464 -0.6672 1.6180 0.1502 -0.6705 1.6260 0.1526 -0.6672 1.6180 0.1556 -0.6672 1.6180 0.1587 -0.6672 1.6180 0.1618 -0.6672 1.6180 0.1649 -0.6672 1.6180 0.1680 -0.6672 1.6180 0.1710 -0.6672 1.6180 0.1741 -0.6672 1.6180 0.1764 -0.6643 1.6110 0.1803 -0.6672 1.6180 0.1834 -0.6672 1.6180 0.1865 -0.6672 1.6180 0.1895 -0.6672 1.6180 0.1918 -0.6643 1.6110 0.1957 -0.6672 1.6180 0.1979 -0.6643 1.6110 0.2010 -0.6643 1.6110 0.2049 -0.6672 1.6180 0.2080 -0.6672 1.6180 0.2111 -0.6672 1.6180 0.2142 -0.6672 1.6180 0.2163 -0.6643 1.6110 ... 一些点云传感器还给点云中的每个点赋RGB彩色的值，如果这些彩色值存在，用户可以调用“readRGB”恢复彩色数据： rgb = readRGB(ptcloud) rgb = 0.8392 0.7059 0.5255 0.8392 0.7059 0.5255 0.8392 0.7137 0.5333 0.8392 0.7216 0.5451 0.8431 0.7137 0.5529 0.8431 0.7098 0.5569 0.8471 0.7137 0.5569 0.8549 0.7098 0.5569 0.8588 0.7137 0.5529 0.8627 0.7137 0.5490 0.8627 0.7216 0.5412 0.8627 0.7333 0.5373 0.8549 0.7294 0.5333 0.8471 0.7216 0.5294 0.8706 0.7373 0.5216 0.8941 0.7412 0.5333 0.8863 0.7255 0.5451 0.8824 0.7176 0.5529 0.8824 0.7216 0.5569 0.8784 0.7333 0.5686 0.8784 0.7255 0.5765 0.8784 0.7176 0.5765 0.8784 0.7412 0.5725 0.8784 0.7569 0.5647 0.8784 0.7451 0.5569 0.8824 0.7373 0.5608 0.8863 0.7490 0.5608 ... 在“scatter3”函数的帮助下，用户可以对点云进行可视化，“scatter3”函数将会自动的提取[x,y,z]坐标值，RGB数据如果存在的话，就会以3D散点图的形式显示。“scatter3”函数忽略所有的值为NaN的[x,y,z]坐标，即使该点的RGB值存在。 scatter3(ptcloud)
ROS支持两种主要的通信机制：话题和服务。话题拥有发布器和订阅器，用于发送和接收消息（参阅使用发布器和订阅器交换数据）。另一方面，服务通过允许请求和应答通信实现紧耦合。服务终端发送请求到服务器，并等待响应。服务器根据请求里的数据构建响应消息并回传给终端。每个服务都有一个类型用于决定请求和响应消息的结构。服务还拥有ROS网络中独一无二的名称。 服务通信具有如下特征： ①服务请求用于一对一通信。单独的节点发起请求，也只有一个节点接收请求并发送回去相应。 ②当服务请求执行的时候，服务终端和服务器紧密地联系在一起。当服务请求发送的时候必须要有服务器存在，一旦请求发送出去，服务终端将会阻塞直到接收到相应。 服务的概念阐述如下图： 图7.1 预备知识：开始使用ROS，连接到ROS网络，使用发布器和订阅器交换数据。 创建服务器 在查看服务的概念之前，在MATLAB中启动ROS节点管理器和ROS网络实例。“exampleHelperROSCreateSampleNetwork”将创建一些服务器以仿真真实的ROS网络。 rosinit exampleHelperROSCreateSampleNetwork Initializing ROS master on http://bat5136glnxa64.mathworks.com:11311/. Initializing global node /matlab_global_node_8332 with NodeURI http://bat5136glnxa64:40154/ 在创建服务器之前，查看可用的服务类型： rostype.getServiceList ans = 'adhoc_communication/BroadcastCMgrRobotUpdate' 'adhoc_communication/BroadcastString' 'adhoc_communication/ChangeMCMembership' 'adhoc_communication/GetGroupState' 'adhoc_communication/GetNeighbors' 'adhoc_communication/SendCMgrRobotUpdate' 'adhoc_communication/SendExpAuction' 'adhoc_communication/SendExpCluster' 'adhoc_communication/SendExpFrontier' 'adhoc_communication/SendMmControl' 'adhoc_communication/SendMmMapUpdate' 'adhoc_communication/SendMmPoint' 'adhoc_communication/SendMmRobotPosition' 'adhoc_communication/SendOccupancyGrid' 'adhoc_communication/SendQuaternion' 'adhoc_communication/SendString' 'adhoc_communication/SendTwist' 'adhoc_communication/ShutDown' 'app_manager/GetAppDetails' 'app_manager/GetInstallationState' 'app_manager/InstallApp' 'app_manager/ListApps' 'app_manager/StartApp' 'app_manager/StopApp' 'app_manager/UninstallApp' 'arbotix_msgs/Enable' 'arbotix_msgs/Relax' ... 假设你想要在创建一个简单的服务器，当你请求服务的时候显示“A service client is calling”。使用“rossvcserver”指令创建服务，指定服务的名称和服务消息的类型。同时定义回调函数“exampleHelperROSEmptyCallback”。服务器的回调函数有非常明确的签名。详细信息，查阅文档rossvcserver。 testserver = rossvcserver('/test', rostype.std_srvs_Empty, @exampleHelperROSEmptyCallback) testserver = ServiceServer with properties: ServiceName: '/test' ServiceType: 'std_srvs/Empty' NewRequestFcn: @exampleHelperROSEmptyCallback 此时，当用户在ROS网络中罗列所有的服务，可以看到新创建的服务“/test”。 rosservice list /add /reply /test 用户可以使用“rosservice info”查看有关服务的更多信息，全局节点也被列出，这是因为在该节点下，服务才是可用的，同时，用户还能够看到服务类型“itsstd_srvs/Empty”。 rosservice info /test Node: /matlab_global_node_8332 URI: rosrpc://bat5136glnxa64:46049/ Type: std_srvs/Empty Args: 创建服务终端 使用服务终端从ROS服务器获得请求信息，使用“rossvcclient”指令创建服务器，该指令需要服务作为参数。 为上一节创建的服务创建服务终端“ testclient = rossvcclient('/test') testclient = ServiceClient with properties: ServiceName: '/test' ServiceType: 'std_srvs/Empty' 为服务创建一个空的请求消息，使用“rosmessage”函数并传递服务终端作为第一个参数，这会创建服务请求函数，拥有由服务指定的消息类型。 testreq = rosmessage(testclient) testreq = ROS EmptyRequest message with properties: MessageType: 'std_srvs/EmptyRequest' 当想要从服务器获得相应，使用“call”函数请求参数服务器并返回响应。用户之前创建的服务器将会返回一个空的效应。此外，还会调用“exampleHelperROSEmptyCallback”显示字符串“A service client is calling”。用户还可以定义“Timeout”参数，该参数指示服务器应该等待响应的时间。 testresp = call(testclient,testreq,'Timeout',3); A service client is calling 用户能够看到回调函数输出的字符串。 创建服务做加法运算 目前为止，服务器没有做任何有意义的工作，但是用户现在可以使用服务完成计算或者数据处理，创建服务完成两个整数的加法运算。 已经存在服务类型“roscpp_tutorials/TwoInts”能够完成该任务。用户可以使用“rosmsg show”查看请求和相应消息的结构，请求包含两个整型数A和B，相应包含他们的和Sum。 rosmsg show roscpp_tutorials/TwoIntsRequest int64 A int64 B rosmsg show roscpp_tutorials/TwoIntsResponse int64 Sum 创建一个带有这种消息类型的服务器，以及一个回调函数执行加法运算。为方便用户，“exampleHelperROSSumCallback”函数已经实现了加法计算，指定该函数作为回调函数。 sumserver = rossvcserver('/sum', rostype.roscpp_tutorials_TwoInts, @exampleHelperROSSumCallback) sumserver = ServiceServer with properties: ServiceName: '/sum' ServiceType: 'roscpp_tutorials/TwoInts' NewRequestFcn: @exampleHelperROSSumCallback 为了调用服务器，用户需要创建一个服务终端，注意，服务终端可以在ROS网络中的任何地方创建。结合本例需要，我们将在MATLAB中为“/sum”服务创建终端。 sumclient = rossvcclient('/sum') sumclient = ServiceClient with properties: ServiceName: '/sum' ServiceType: 'roscpp_tutorials/TwoInts' 创建请求消息，用户可以定义两个整型变量A和B，这两个数在调用“call”指令是将用于做加法运算。 sumreq = rosmessage(sumclient); sumreq.A = 2; sumreq.B = 1 sumreq = ROS TwoIntsRequest message with properties: MessageType: 'roscpp_tutorials/TwoIntsRequest' A: 2 B: 1 执行的结果是两个数的和为3，为了调用服务，使用如下的代码。服务响应消息包含了“Sum”属性，存储A和B的加法结果。 sumresp = call(sumclient,sumreq,'Timeout',3) sumresp = ROS TwoIntsResponse message with properties: MessageType: 'roscpp_tutorials/TwoIntsResponse' Sum: 3 关闭ROS网络 从ROS网络中移除示例节点、发布器、订阅器： exampleHelperROSShutDownSampleNetwork 关闭ROS主控节点并删除全局节点： rosshutdown Shutting down global node /matlab_global_node_8332 with NodeURI http://bat5136glnxa64:40154/ Shutting down ROS master on http://bat5136glnxa64.mathworks.com:11311/.
本例程将探索如何在ROS参数服务器上增加和恢复参数，参数服务器和ROS管理节点运行在相同的设备上。参数通常在ROS网络中全局都是可用的，并且可以存储静态数据例如配置参数。支持的数据类型包括字符串、整型数、双精度、逻辑和单元阵列。 预备知识：1.2开始使用ROS和1.3连接到ROS网络。 创建参数树 在MATLAB中启动ROS管理节点和参数服务器。 rosinit Initializing ROS master on http://bat5136glnxa64.mathworks.com:11311/. Initializing global node /matlab_global_node_8332 with NodeURI http://bat5136glnxa64:60770/ 创建参数树对象与参数服务器进行交互，参数树可让用户与参数服务器进行交互，并提供函数，例如“set”，“get”，“del”，“has”和“search”。由于我们在本例中启动一个新的参数服务器，因此目前没有存储任何参数。 ptree = rosparam ptree = ParameterTree with properties: AvailableParameters: {} 增加新参数 使用参数名“ROBOT_IP”设定机器人的IP地址参数值。使用“has”函数，检查是否存在相同名称的参数。 has(ptree,'ROBOT_IP') ans = 0 “has”返回0（false）作为输出，这说明当前在参数服务器中没有名称为“ROBOT_IP”的参数。 使用“set”函数，增加指定机器人IP地址的参数到参数服务器。 set(ptree,'ROBOT_IP','192.168.1.1'); set(ptree, '/myrobot/ROBOT_IP','192.168.1.100'); 现在，所有连接到当前ROS管理节点的节点，都能够使用“ROBOT_IP”参数，用户还可以为参数指定命名空间，例如此处的“/myrobot/ROBOT_IP”参数的命名空间是“/myrobot”。 设定参数的不同数据类型： set(ptree,'MAX_SPEED',1.5); 在“set”函数中使用单元阵列，为机器人设定全局坐标参数{x,y,z}： set(ptree, '/myrobot/ROBOT_NAME','TURTLE'); set(ptree, '/myrobot/MAX_SPEED',1.5); set(ptree, '/newrobot/ROBOT_NAME','NEW_TURTLE'); 获得参数值 当用户想用连接到机器人时，可以从“ROBUT_IP”参数获得机器人的IP地址，用于连接到该机器人。总共有两个名称为“ROBOT_IP”的参数，用户仅需要“myrobot”下的地址。用户可用“get”函数，获得该参数值： robotIP = get(ptree, '/myrobot/ROBOT_IP') robotIP = 192.168.1.100 获得所有参数列表 检查“AvailableParameters”的属性，可以获得存储在参数服务器上的所有参数列表，该列表包含了用户在上述章节增加的参数： plist = ptree.AvailableParameters plist = '/MAX_SPEED' '/ROBOT_IP' '/goal' '/myrobot/MAX_SPEED' '/myrobot/ROBOT_IP' '/myrobot/ROBOT_NAME' '/newrobot/ROBOT_NAME' 修改已有参数 用户还可用“set“函数修改参数值。注意，参数的修改是不可逆的，因为参数服务器会用新的值覆盖参数。用户可以使用“has”函数验证参数是否已经存在。 修改“MAX_SPEED”参数： set(ptree, 'MAX_SPEED', 1.0); 对参数进行修改的数值可以与之前分配的数据类型不同，例如，“MAX_SPEED”的数据类型是双精度，现在可以设置“MAX_SPEED”参数为字符串。 set(ptree, 'MAX_SPEED', 'none'); 删除参数 用户可以使用“del”函数从参数服务器中删除参数。 删除“goal”参数： del(ptree, 'goal'); 使用“has”函数检查“goal”参数是否已经成功删除： has(ptree, 'goal') ans = 0 返回0（false），意味着“goal”参数已经从参数服务器中删除。 搜索参数 使用“search”指令所有包含“myrobot”的参数： results = search(ptree, 'myrobot') results = '/myrobot/MAX_SPEED' '/myrobot/ROBOT_IP' '/myrobot/ROBOT_NAME' 关闭ROS网络 关闭ROS网络并删除全局节点。 rosshutdown Shutting down global node /matlab_global_node_8332 with NodeURI http://bat5136glnxa64:60770/ Shutting down ROS master on http://bat5136glnxa64.mathworks.com:11311/.
“rosbag”或“bag”是ROS中存储消息数据的文件格式，这些包通常是通过订阅一个或多个ROS话题创建的，并用一种高效的文件结构存储接收到的消息数据。MATLAB可以读取rosbag文件，并有助于过滤和提取消息数据。本例，用户将会载入rosbag文件并学习如何选择并恢复包含的消息。 预备知识：1.5 基本ROS消息的使用。 载入rosbag 使用“rosbag”指令载入示例文件，采取绝对路径或者相对路径的方式指定记录文件的路径： filepath = fullfile(fileparts(which('ROSWorkingWithRosbagsExample')), 'data', 'ex_multiple_topics.bag'); bag = rosbag(filepath) bag = BagSelection with properties: FilePath: '/mathworks/devel/bat/BR2015ad/build/matlab/toolbox/r...' StartTime: 201.3400 EndTime: 321.3400 NumMessages: 36963 AvailableTopics: [4x3 table] MessageList: [36963x4 table] 调用“rosbag”返回的对象是“BagSelection”，“rosbag”文件中的所有信息都在该对象里。 对象的属性显示了文件包含多少消息（NumMessages）、记录第一个（StartTime）和最后一个（EndTime）消息的时间等详细信息。 获取“AvailableTopics”属性值，查看记录文件中有关话题和消息类型的更多消息。 bag.AvailableTopics ans = NumMessages MessageType MessageDefinition ___________ ______________________ _________________ /clock 12001 rosgraph_msgs/Clock [1x185 char] /gazebo/link_states 11999 gazebo_msgs/LinkStates [1x1247 char] /odom 11998 nav_msgs/Odometry [1x2918 char] /scan 965 sensor_msgs/LaserScan [1x2123 char] “AvailableTopics”属性将包含在“rosbag”文件中的话题做成列表，该列表存储了消息的数量、消息的类型和为话题定义的消息。初始化“rosbag”文件仅仅是MATLAB建立了索引，并没有读取任何实际的消息。 在消息载入MATLAB内存之前，用户可能想要基于索引尽可能的滤除和筛选需要的消息。 选择消息 在恢复任何消息之前，用户必须基于时间戳、话题名称或消息类型选择一系列的消息。 用户可以查看当前选择的所有消息： bag.MessageList ans = Time Topic MessageType FileOffset ______ ___________________ ______________________ __________ 201.34 /clock rosgraph_msgs/Clock 4524 201.34 /odom nav_msgs/Odometry 7666 201.34 /gazebo/link_states gazebo_msgs/LinkStates 9866 201.35 /clock rosgraph_msgs/Clock 10962 201.35 /clock rosgraph_msgs/Clock 12876 201.35 /odom nav_msgs/Odometry 12112 201.35 /gazebo/link_states gazebo_msgs/LinkStates 11016 201.36 /odom nav_msgs/Odometry 14026 201.36 /gazebo/link_states gazebo_msgs/LinkStates 12930 201.37 /clock rosgraph_msgs/Clock 14790 201.37 /odom nav_msgs/Odometry 14844 201.37 /gazebo/link_states gazebo_msgs/LinkStates 15608 201.38 /clock rosgraph_msgs/Clock 16704 201.38 /odom nav_msgs/Odometry 17854 201.38 /gazebo/link_states gazebo_msgs/LinkStates 16758 201.39 /clock rosgraph_msgs/Clock 18618 201.39 /gazebo/link_states gazebo_msgs/LinkStates 18672 201.4 /clock rosgraph_msgs/Clock 19768 201.4 /gazebo/link_states gazebo_msgs/LinkStates 19822 201.41 /clock rosgraph_msgs/Clock 20918 201.41 /odom nav_msgs/Odometry 22068 201.41 /odom nav_msgs/Odometry 22832 201.41 /odom nav_msgs/Odometry 23596 201.41 /gazebo/link_states gazebo_msgs/LinkStates 20972 ... “MessageList”列表的每一行包含着记录包里的每一条消息（本例中共有超过3000条消息），列表中的每一行按照消息记录的时间戳排序，时间戳存放在每一行的第一列。 由于列表非常大，用户可以根据熟悉的选择行和列的方法选择感兴趣的部分进行显示： bag.MessageList(500:505,:) ans = Time Topic MessageType FileOffset ____ ___________________ ______________________ __________ 203 /clock rosgraph_msgs/Clock 339384 203 /odom nav_msgs/Odometry 336328 203 /odom nav_msgs/Odometry 337092 203 /odom nav_msgs/Odometry 337856 203 /odom nav_msgs/Odometry 338620 203 /gazebo/link_states gazebo_msgs/LinkStates 331944 使用“select”函数筛选消息，“select”函数作用于“bag”对象。用户可以通过时间、话题名称、消息类型或三者的任意组合筛选消息。 使用如下的“select”指令，选择所有发布到“/odom”话题的消息： bagselect1 = select(bag, 'Topic', '/odom') bagselect1 = BagSelection with properties: FilePath: '/mathworks/devel/bat/BR2015ad/build/matlab/toolbox/r...' StartTime: 201.3400 EndTime: 321.3300 NumMessages: 11998 AvailableTopics: [1x3 table] MessageList: [11998x4 table] 调用“select”函数返回另一个“BagSelection”对象，可用于进一步的选择或者恢复消息数据。所有选中的消息之间相互独立，所以用户在完成处理后可以从工作空间中清除它们。 用户可以结合两种标准筛选，为了获得记录文件中，前30秒发布到“/odom”话题的消息列表，使用如下的指令： start = bag.StartTime bagselect2 = select(bag, 'Time', [start start + 30], 'Topic', '/odom') start = 201.3400 bagselect2 = BagSelection with properties: FilePath: '/mathworks/devel/bat/BR2015ad/build/matlab/toolbox/r...' StartTime: 201.3400 EndTime: 231.3200 NumMessages: 2997 AvailableTopics: [1x3 table] MessageList: [2997x4 table] 使用最后的一个筛选规则更进一步地缩小时间窗口。 bagselect3 = select(bagselect2, 'Time', [205 206]) bagselect3 = BagSelection with properties: FilePath: '/mathworks/devel/bat/BR2015ad/build/matlab/toolbox/r...' StartTime: 205.0200 EndTime: 205.9900 NumMessages: 101 AvailableTopics: [1x3 table] MessageList: [101x4 table] 最后步骤的筛选对“bagselect2”进行操作，并返回一个新的“bagselect3”对象。 用户还可以将筛选的选项保存到阵列中，并作为“select”函数的输入： selectOptions = {'Time', [start, start+1; start+5, start+6], 'MessageType', {'sensor_msgs/LaserScan', 'nav_msgs/Odometry'}}; bagselect4 = select(bag, selectOptions{:}) bagselect4 = BagSelection with properties: FilePath: '/mathworks/devel/bat/BR2015ad/build/matlab/toolbox/r...' StartTime: 201.3400 EndTime: 207.3300 NumMessages: 209 AvailableTopics: [2x3 table] MessageList: [209x4 table] 读取选择的消息数据 在筛选完消息之后，用户可以在MATLAB中真正的读取消息数据，根据用户选择的消息的大小，读取消息数据可能需要很长时间或者消耗很多的计算机内存。 使用“readMessages”函数，恢复用户选择的消息并存储到单元阵列中： msgs = readMessages(bagselect3); size(msgs) ans = 101 1 结果阵列中包含的元素的数量与选择的对象的“NumMessages”属性指示的数量相等。 在读取消息数据过程中，用户可以有选择性的恢复指定目录的消息，下面的例子恢复了四个消息： msgs = readMessages(bagselect3, [1 2 3 7]) msgs{2} msgs = [1x1 Odometry] [1x1 Odometry] [1x1 Odometry] [1x1 Odometry] ans = ROS Odometry message with properties: MessageType: 'nav_msgs/Odometry' Header: [1x1 Header] ChildFrameId: 'base_footprint' Pose: [1x1 PoseWithCovariance] Twist: [1x1 TwistWithCovariance] 单元阵列中的每个消息都是标准的MATLAB ROS消息对象，有关消息的更多信息，查看基本ROS消息的使用的例子。 按时间序列提取消息数据 用户有时对完整的消息不感兴趣，仅对所有选中的消息中共有的指定的属性感兴趣。在这种情况下，使用时间序列的方式恢复消息数据将会是很有帮助的。时间序列是根据时间采样数据向量，代表随时间演变的一个或多个动态特性。有关MATLAB时间序列支持的更多消息，请参阅Time Series文档。 对于“rosbag”文件中的ROS消息，时间序列帮助表达某个消息元素随时间的变化过程，用户可以通过“timeseries”函数提取该信息。这是一种节省存储空间的方式，因为并没有存储完整的消息数据。 ts = timeseries(bagselect3, 'Pose.Pose.Position.X', 'Twist.Twist.Angular.Z') timeseries Timeseries contains duplicate times. Common Properties: Name: '/odom Properties' Time: [101x1 double] TimeInfo: tsdata.timemetadata Data: [101x2 double] DataInfo: tsdata.datametadata 返回值是一个“timeseries”对象，可用于进一步的分析和处理。注意，这种提取数据的方法，仅当当前选择包含单个的话题且只有一种消息类型。 访问“Data”属性可以查看消息序列中包含的数据： ts.Data ans = 0.0003 0.0003 0.0003 0.0003 0.0003 -0.0006 0.0003 -0.0006 0.0003 -0.0010 0.0003 -0.0010 0.0003 -0.0003 0.0003 -0.0003 0.0003 -0.0003 0.0003 -0.0003 0.0003 -0.0016 0.0003 -0.0016 0.0003 -0.0001 0.0003 -0.0001 0.0003 0.0003 0.0003 0.0003 0.0003 -0.0006 0.0003 -0.0006 0.0003 -0.0010 0.0003 -0.0010 0.0003 -0.0003 0.0003 -0.0003 0.0003 -0.0003 0.0003 -0.0003 0.0003 -0.0016 0.0003 -0.0016 0.0003 -0.0005 ... 还有很多种方法处理时间序列数据，例如计算数据列的均值： mean(ts) ans = 1.0e-03 * 0.3213 -0.4616 或者可以画出时间序列数据： figure plot(ts, 'LineWidth', 3)