前文链接
【轻量级开源ROS 的机器人设备(4)】--(2)通信实现_无水先生的博客-CSDN博客
六、数据流
数据流 虽然 XML-RPC 为远程方法调用提供了一种简单而干净的协议,但其冗长和以文本为中心的编码使其不适合高带宽和低延迟任务。数据流就是这种情况,例如由传感器或执行器发送的数据流。 ROS 为此类流定义了自定义二进制协议;这允许在节点之间传输原始数据,所需的消息长度最小(带宽最大化)并且几乎没有处理时间(延迟最小化)。有关主题和服务消息语法的详细信息 。
6.1 类型描述符
数据流基于特定类型的消息交换,在注册时由主题/服务指定(参见上一节)。消息是一系列值,按照类型描述符定义的顺序排列。 ROS中有两种,主题描述符和服务描述符。我们将首先考虑主题描述符,因为服务类型只是一个简单的扩展。 主题类型 ROS 主题消息描述符被编写为纯文本文件,其中文件名与类型名称一致,加上 .msg 扩展名。
每行都可以定义一个强类型变量,其中类型可以是原始类型,也可以是另一个消息类型描述符的名称,甚至可以是任何此类类型的数组(固定或变量)。因此,消息可以嵌套,并且由于是强类型,它们的流式传输得到简化(无需处理动态类型)。描述符还可以声明原始类型的常量名称。
清单 4.3 显示了 rosgraph_msgs/msg/Log.msg 的内容,而清单 4.4 是其干净的扩展版本,由 rosmsg show 生成。
服务类型 虽然主题是单向的,因此只需要定义一种消息类型,但服务以请求/响应方式工作
1 ##
2 ## Severity level constants
3 ##
4 byte DEBUG=1 #debug level
5 byte INFO=2 #general level
6 byte WARN=4 #warning level
7 byte ERROR=8 #error level
8 byte FATAL=16 #fatal/critical level
9 ##
10 ## Fields
11 ##
12 Header header
13 byte level
14 string name # name of the node
15 string msg # message
16 string file # file the message came from
17 string function # function the message came from
18 uint32 line # line the message came from
19 string[] topics # topic names that the node publishes
Listing 4.3: Contents of rosgraph_msgs/msg/Log.msg
byte DEBUG=1
2 byte INFO=2
3 byte WARN=4
4 byte ERROR=8
5 byte FATAL=16
6 std_msgs/Header header
7 uint32 seq
8 time stamp
9 string frame_id
10 byte level
11 string name
12 string msg
13 string file
14 string function
15 uint32 line
16 string[] topics
Listing 4.4: Clean, expanded contents of rosgraph_msgs/msg/Log.ms
因此,服务涉及两种类型,一种用于请求,一种用于响应。服务类型描述符的存储方式与主题类型类似,但扩展名为 .srv。描述符被 -- 分隔线分成两部分,其中第一部分是请求消息描述符,第二部分是响应消息描述符。清单 4.5 是 dynamic_reconfigure/srv/Reconfigure.srv 内容的示例,而清单 4.6 是其干净的扩展版本,由 rossrv show 生成。
散列 因为节点之间的描述符文本可能不同,而它们的名称可以相同,所以必须有一种方法来确定两个节点是否实际上共享相同的类型。对于此类任务,选择 MD5 和来比较两种不同的类型描述。比较函数对每个类型描述符执行以下操作:
1.评论被删除;
2. 去除空格;
3.去除依赖包名;
4. 常量移到变量声明之前,保持它们的排列;
5. 对于嵌套类型,计算它们的散列文本并按照它们出现的顺序附加到当前散列文本;
6.计算整个哈希文本的MD5和。如果 MD5 和相同,则两个描述符相等。
1 Config config
2 ---
3 Config config
Listing 4.5: Contents of dynamic_reconfigure/srv/Reconfigure.srv
1 dynamic_reconfigure/Config config
2 dynamic_reconfigure/BoolParameter[] bools
3 string name
4 bool value
5 dynamic_reconfigure/IntParameter[] ints
6 string name
7 int32 value
8 dynamic_reconfigure/StrParameter[] strs
9 string name
10 string value
11 dynamic_reconfigure/DoubleParameter[] doubles
12 string name
13 float64 value
14 dynamic_reconfigure/GroupState[] groups
15 string name
16 bool state
17 int32 id
18 int32 parent
19 ---
20 dynamic_reconfigure/Config config
21 dynamic_reconfigure/BoolParameter[] bools
22 string name
23 bool value
24 dynamic_reconfigure/IntParameter[] ints
25 string name
26 int32 value
27 dynamic_reconfigure/StrParameter[] strs
28 string name
29 string value
30 dynamic_reconfigure/DoubleParameter[] doubles
31 string name
32 float64 value
33 dynamic_reconfigure/GroupState[] groups
34 string name
35 bool state
36 int32 id
37 int32 parent
Listing 4.6: Clean, expanded contents of dynamic_reconfigure/srv/Reconfigure.srv
ROS type C99 type
uint8 uint8_t
uint16 uint16_t
uint32 uint32_t
uint64 uint64_t
int8 int8_t
int16 int16_t
int32 int32_t
int64 int64_t
time uros_time_t
duration uros_time_t
string struct UrosString { size_t length; char *datap; };
type[] struct UrosTcpRosArray { uint32_t length; void *entriesp; };
byte uint8_t
char char
uint uint32_t
int int32_t
Table 4.1: ROS to C type mapping
6.2 消息数据结构:( Message structure )
消息以小端二进制形式序列化。这使得市场上大多数小端 CPU 架构(例如基于 x86 或 ARM 设计的架构)的处理变得简单。因此,数据流消息很像 C 结构变量的序列化;例如,在 x86 架构上,消息是变量本身的一对一转储,因为内存中的 8 位字段对齐(相比之下,ARM 内核为 32 位)。
基本类型可以直接映射到 C 类型,如表 4.1 所示。可变长度数组和字符串(字符数组)是一种特殊情况,因为它们提供 uint32 长度的条目数,后跟序列化条目本身。相比之下,固定长度数组被视为指定类型的条目序列。任何嵌套类型都被扩展为它们各自的基本类型或数组。
在刚刚描述的消息内容之前,uint32 值预计内容本身的总长度。
6.3 话题消息:Topic messages
主题消息简单地遵循上面描述的长度+内容方案,因为主题涉及相同类型的单向消息。 “你好,世界!”的一个例子std_msgs/String 类型的消息如图 4.4 所示。流偏移量以粗体字写为十六进制索引;如果可打印,值将写为 ASCII 字符,否则将写为十六进制数字。右侧的注释表示正在流式传输的特定字段及其人类可读的值。
00 01 02 03 uint32 message_length
11 00 00 00 17
04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 string data
0D 00 00 00 H e l l o , W o r l d ! 13 "Hello, World!"
Figure 4.4: Dump of a "Hello, World!" message of type std_msgs/String
00 01 02 03 uint32 message_length
10 00 00 00 16
04 05 06 07 08 09 0A 0B int64 a
D2 04 00 00 00 00 00 00 1234
0C 0D 0E 0F 10 11 12 13 int64 b
2E 16 00 00 00 00 00 00 5678
Figure 4.5: Dump of a call to service of type rospy_tutorials/AddTwoInts
服务消息 服务请求的序列化方式与主题消息相同。相反,响应的序列化需要考虑响应的状态,它可以是实际的响应值,也可以是错误消息。
在响应之前发送一个所谓的 ok 字节。如果为 1,则表示后面的流数据是预期的服务响应类型。如果为 0 则表示在处理请求时发生错误,后面的流值是字符串类型,代表人类可读的错误文本。
rospy_tutorials/AddTwoInts服务请求和响应示例如图4.5和图4.6所示。相反,可以在图 4.7 中看到报告未知错误的服务响应。
6.4 连接头 Connection header
一旦主题/服务客户端和相关服务器连接,它们就必须就流参数达成一致。它们包括主题/服务名称和类型、多个服务请求的可能性、带宽优化的使用等。所有这些参数都由连接标头设置,连接标头是两个端点在整个流的最开始发送的特殊消息。首先,客户端发送一个标头,作为一个请求。然后服务器将使用另一个标头进行回复,该标头充当响应。
连接标头由字段组成。它们像字符串一样被编码,因此告诉字符串长度的 uint32 在字符串字符之前发送。该值遵循 field=value 格式,其中可用字段及其语义取决于主题/服务请求/响应标头。连接标头由一个包含整个标头长度的 uint32 引入,就像普通消息一样。
00 uint8 ok
01 acknowledge
01 02 03 04 uint32 message_length
08 00 00 00 8
05 06 07 08 09 0A 0B 0C int64 sum
00 1B 00 00 00 00 00 00 6912
Figure 4.6: Dump of the response to the service call of Figure 4.5
Figure 4.6: Dump of the response to the service call of Figure 4.5
00 uint8 ok
00 error
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 string error_text
0D 00 00 00 U n k n o w n e r r o r 13 "Unknown error"
Figure 4.7: Dump of a service error response
表 4.2 总结了连接标头的字段及其存在,在主题、服务或错误标头 (X) 中,其中一些是可选的 ((X))或者可以通过请求标头探测(字段值等于 *)。
图 4.8 和图 4.9 分别显示了 /turtlesim 节点的 /turtle1/command_velocity 的请求和响应连接头,通过 turtlesim_teleop 节点。
00 01 02 03 uint32 header_length
91 00 00 00 145
04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A (string field)
13 00 00 00 c a l l e r i d = / t u r t l e s i m
1B 1C 1D 1E 1F 20 21 22 23 24 25 (string field)
27 00 00 00 m d 5 s u m =
26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35
9 d 5 c 2 d c d 3 4 8 a c 8 f 7
36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45
6 c e 2 a 4 3 0 7 b d 6 3 a 1 3
46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 (string field)
0D 00 00 00 t c p _ n o d e l a y = 0
57 58 59 5A 5B 5C 5D 5E 5F 60 (string field)
1F 00 00 00 t o p i c =
61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79
/ t u r t l e 1 / c o m m a n d _ v e l o c i t y
7A 7B 7C 7D 7E 7F 80 81 82 (string field)
17 00 00 00 t y p e =
83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94
t u r t l e s i m / V e l o c i t y
Figure 4.8: Dump of a connection header request
00 01 02 03 uint32 header_length
A5 00 00 00 165
04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E
13 00 00 00 c a l l e r i d = / t e l e o p _ t u r t l e
1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C
0A 00 00 00 l a t c h i n g = 0
2D 2E 2F 30 31 32 33 34 35 36 37
27 00 00 00 m d 5 s u m =
38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47
9 d 5 c 2 d c d 3 4 8 a c 8 f 7
48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57
6 c e 2 a 4 3 0 7 b d 6 3 a 1 3
58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E
32 00 00 00 m e s s a g e _ d e f i n i t i o n =
6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D
f l o a t 3 2 l i n e a r \n
7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D
f l o a t 3 2 a n g u l a r \n
8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8
17 00 00 00 t y p e = t u r t l e s i m / V e l o c i t y
Figure 4.9: Dump of a response to the connection header in Figure 4.8