rosruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rubyroscore +5 -0
- data/lib/ros.rb +25 -0
- data/lib/ros/duration.rb +63 -0
- data/lib/ros/graph_manager.rb +408 -0
- data/lib/ros/log.rb +72 -0
- data/lib/ros/master.rb +408 -0
- data/lib/ros/master_proxy.rb +256 -0
- data/lib/ros/message.rb +65 -0
- data/lib/ros/name.rb +88 -0
- data/lib/ros/node.rb +442 -0
- data/lib/ros/package.rb +144 -0
- data/lib/ros/parameter_manager.rb +127 -0
- data/lib/ros/parameter_subscriber.rb +47 -0
- data/lib/ros/publisher.rb +96 -0
- data/lib/ros/rate.rb +41 -0
- data/lib/ros/ros.rb +10 -0
- data/lib/ros/roscore.rb +29 -0
- data/lib/ros/service.rb +37 -0
- data/lib/ros/service_client.rb +83 -0
- data/lib/ros/service_server.rb +92 -0
- data/lib/ros/slave_proxy.rb +153 -0
- data/lib/ros/subscriber.rb +119 -0
- data/lib/ros/tcpros/client.rb +108 -0
- data/lib/ros/tcpros/header.rb +89 -0
- data/lib/ros/tcpros/message.rb +74 -0
- data/lib/ros/tcpros/server.rb +137 -0
- data/lib/ros/tcpros/service_client.rb +104 -0
- data/lib/ros/tcpros/service_server.rb +132 -0
- data/lib/ros/time.rb +109 -0
- data/lib/ros/topic.rb +47 -0
- data/lib/ros/xmlrpcserver.rb +40 -0
- data/samples/add_two_ints_client.rb +25 -0
- data/samples/add_two_ints_server.rb +20 -0
- data/samples/gui.rb +126 -0
- data/samples/sample_log.rb +16 -0
- data/samples/sample_param.rb +20 -0
- data/samples/sample_publisher.rb +20 -0
- data/samples/sample_subscriber.rb +19 -0
- data/scripts/genmsg_ruby.py +1135 -0
- data/scripts/genmsg_ruby.pyc +0 -0
- data/scripts/gensrv_ruby.py +105 -0
- data/scripts/gensrv_ruby.pyc +0 -0
- data/scripts/rosruby_genmsg.py +67 -0
- data/scripts/run-test.rb +21 -0
- data/test/test_header.rb +36 -0
- data/test/test_log.rb +45 -0
- data/test/test_master_proxy.rb +73 -0
- data/test/test_message.rb +13 -0
- data/test/test_node.rb +166 -0
- data/test/test_package.rb +10 -0
- data/test/test_param.rb +27 -0
- data/test/test_pubsub.rb +154 -0
- data/test/test_rate.rb +16 -0
- data/test/test_service.rb +34 -0
- data/test/test_slave_proxy.rb +49 -0
- data/test/test_time.rb +39 -0
- metadata +170 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ros'
|
4
|
+
|
5
|
+
def main
|
6
|
+
node = ROS::Node.new('/hoge')
|
7
|
+
sleep(1)
|
8
|
+
node.loginfo('some information')
|
9
|
+
node.logdebug('some debug information')
|
10
|
+
node.logerr('some error information')
|
11
|
+
node.logerror('some error information')
|
12
|
+
node.logfatal('some fatal information')
|
13
|
+
sleep(1)
|
14
|
+
end
|
15
|
+
|
16
|
+
main
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ros'
|
4
|
+
require 'std_msgs/String'
|
5
|
+
|
6
|
+
def main
|
7
|
+
node = ROS::Node.new('/rosruby/sample_publisher')
|
8
|
+
publisher = node.advertise('/chatter', Std_msgs::String)
|
9
|
+
sleep(1)
|
10
|
+
msg = Std_msgs::String.new
|
11
|
+
|
12
|
+
while node.ok?
|
13
|
+
msg.data = "local param = #{node.get_param('~message')}, global = #{node.get_param('/message')}"
|
14
|
+
publisher.publish(msg)
|
15
|
+
node.loginfo(msg.data)
|
16
|
+
sleep (1.0)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
main
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ros'
|
4
|
+
require 'std_msgs/String'
|
5
|
+
|
6
|
+
def main
|
7
|
+
node = ROS::Node.new('/rosruby/sample_publisher')
|
8
|
+
publisher = node.advertise('/chatter', Std_msgs::String)
|
9
|
+
sleep(1)
|
10
|
+
msg = Std_msgs::String.new
|
11
|
+
i = 0
|
12
|
+
while node.ok?
|
13
|
+
msg.data = "Hello, rosruby!: #{i}"
|
14
|
+
publisher.publish(msg)
|
15
|
+
sleep(1.0)
|
16
|
+
i += 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
main
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ros'
|
4
|
+
require 'std_msgs/String'
|
5
|
+
|
6
|
+
def main
|
7
|
+
node = ROS::Node.new('/rosruby/sample_subscriber')
|
8
|
+
node.subscribe('/chatter', Std_msgs::String) do |msg|
|
9
|
+
puts "message come! = \'#{msg.data}\'"
|
10
|
+
end
|
11
|
+
|
12
|
+
while node.ok?
|
13
|
+
node.spin_once
|
14
|
+
sleep(1)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
main
|
@@ -0,0 +1,1135 @@
|
|
1
|
+
#! /usr/bin/env python
|
2
|
+
# Software License Agreement (BSD License)
|
3
|
+
#
|
4
|
+
# Copyright (c) 2012, Takashi Ogura
|
5
|
+
#
|
6
|
+
# based on
|
7
|
+
#
|
8
|
+
# Copyright (c) 2008, Willow Garage, Inc.
|
9
|
+
# All rights reserved.
|
10
|
+
#
|
11
|
+
# Redistribution and use in source and binary forms, with or without
|
12
|
+
# modification, are permitted provided that the following conditions
|
13
|
+
# are met:
|
14
|
+
#
|
15
|
+
# * Redistributions of source code must retain the above copyright
|
16
|
+
# notice, this list of conditions and the following disclaimer.
|
17
|
+
# * Redistributions in binary form must reproduce the above
|
18
|
+
# copyright notice, this list of conditions and the following
|
19
|
+
# disclaimer in the documentation and/or other materials provided
|
20
|
+
# with the distribution.
|
21
|
+
# * Neither the name of Willow Garage, Inc. nor the names of its
|
22
|
+
# contributors may be used to endorse or promote products derived
|
23
|
+
# from this software without specific prior written permission.
|
24
|
+
#
|
25
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
26
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
27
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
28
|
+
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
29
|
+
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
30
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
31
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
32
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
33
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
34
|
+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
35
|
+
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
36
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
37
|
+
#
|
38
|
+
# Revision $Id:$
|
39
|
+
|
40
|
+
"""
|
41
|
+
Library for Ruby message generation.
|
42
|
+
|
43
|
+
The structure of the serialization descends several levels of serializers:
|
44
|
+
- msg_generator: generator for an individual msg file
|
45
|
+
- serialize_fn_generator: generator for msg.serialize()
|
46
|
+
- serializer_generator
|
47
|
+
- field-type-specific serializers
|
48
|
+
- deserialize_fn_generator: generator for msg.deserialize()
|
49
|
+
- serializer_generator
|
50
|
+
- field-type-specific serializers
|
51
|
+
"""
|
52
|
+
|
53
|
+
# NOTE: genpy must be in the roslib package as placing it in rospy
|
54
|
+
# creates circular deps
|
55
|
+
|
56
|
+
import os
|
57
|
+
import shutil
|
58
|
+
import atexit
|
59
|
+
import itertools
|
60
|
+
import sys
|
61
|
+
import tempfile
|
62
|
+
import traceback
|
63
|
+
import struct
|
64
|
+
|
65
|
+
try:
|
66
|
+
from cStringIO import StringIO # Python 2.x
|
67
|
+
except ImportError:
|
68
|
+
from io import StringIO # Python 3.x
|
69
|
+
|
70
|
+
import roslib.exceptions
|
71
|
+
import roslib.gentools
|
72
|
+
import roslib.msgs
|
73
|
+
import roslib.packages #for get_pkg_dir
|
74
|
+
|
75
|
+
# indent width
|
76
|
+
INDENT = ' '
|
77
|
+
|
78
|
+
class MsgGenerationException(roslib.exceptions.ROSLibException):
|
79
|
+
"""
|
80
|
+
Exception type for errors in roslib.genpy
|
81
|
+
"""
|
82
|
+
pass
|
83
|
+
|
84
|
+
def get_registered_ex(type_):
|
85
|
+
"""
|
86
|
+
wrapper for roslib.msgs.get_registered that wraps unknown types with a MsgGenerationException
|
87
|
+
@param type_: ROS message type
|
88
|
+
@type type_: str
|
89
|
+
"""
|
90
|
+
try:
|
91
|
+
return roslib.msgs.get_registered(type_)
|
92
|
+
except KeyError:
|
93
|
+
raise MsgGenerationException("Unknown type [%s]. Please check that the manifest.xml correctly declares dependencies."%type_)
|
94
|
+
|
95
|
+
################################################################################
|
96
|
+
# Primitive type handling for ROS builtin types
|
97
|
+
|
98
|
+
PYTHON_RUBY_DICT = { #see python module struct
|
99
|
+
'b': 'c',
|
100
|
+
'B': 'C',
|
101
|
+
'h' : 's',
|
102
|
+
'H' : 'S',
|
103
|
+
'i' : 'l',
|
104
|
+
'I' : 'L',
|
105
|
+
'q' : 'q',
|
106
|
+
'Q' : 'Q',
|
107
|
+
'f': 'f',
|
108
|
+
'd': 'd'
|
109
|
+
}
|
110
|
+
|
111
|
+
SIMPLE_TYPES_DICT = { #see python module struct
|
112
|
+
'int8': 'b',
|
113
|
+
'uint8': 'B',
|
114
|
+
# Python 2.6 adds in '?' for C99 _Bool, which appears equivalent to an uint8,
|
115
|
+
# thus, we use uint8
|
116
|
+
'bool': 'B',
|
117
|
+
'int16' : 'h',
|
118
|
+
'uint16' : 'H',
|
119
|
+
'int32' : 'i',
|
120
|
+
'uint32' : 'I',
|
121
|
+
'int64' : 'q',
|
122
|
+
'uint64' : 'Q',
|
123
|
+
'float32': 'f',
|
124
|
+
'float64': 'd',
|
125
|
+
# deprecated
|
126
|
+
'char' : 'B', #unsigned
|
127
|
+
'byte' : 'b', #signed
|
128
|
+
}
|
129
|
+
|
130
|
+
## Simple types are primitives with fixed-serialization length
|
131
|
+
SIMPLE_TYPES = list(SIMPLE_TYPES_DICT.keys()) #py3k
|
132
|
+
|
133
|
+
def is_simple(type_):
|
134
|
+
"""
|
135
|
+
@return bool: True if type is a 'simple' type, i.e. is of
|
136
|
+
fixed/known serialization length. This is effectively all primitive
|
137
|
+
types except for string
|
138
|
+
@rtype: bool
|
139
|
+
"""
|
140
|
+
return type_ in SIMPLE_TYPES
|
141
|
+
|
142
|
+
def is_special(type_):
|
143
|
+
"""
|
144
|
+
@return True if type_ is a special type (i.e. builtin represented as a class instead of a primitive)
|
145
|
+
@rtype: bool
|
146
|
+
"""
|
147
|
+
return type_ in _SPECIAL_TYPES
|
148
|
+
|
149
|
+
def get_special(type_):
|
150
|
+
"""
|
151
|
+
@return: special type handler for type_ or None
|
152
|
+
@rtype: L{Special}
|
153
|
+
"""
|
154
|
+
return _SPECIAL_TYPES.get(type_, None)
|
155
|
+
|
156
|
+
################################################################################
|
157
|
+
# Special type handling for ROS builtin types that are not primitives
|
158
|
+
|
159
|
+
class Special:
|
160
|
+
|
161
|
+
def __init__(self, constructor, post_deserialize, import_str):
|
162
|
+
"""
|
163
|
+
@param constructor: expression to instantiate new type instance for deserialization
|
164
|
+
@type constructor: str
|
165
|
+
@param post_Deserialize: format string for expression to evaluate on type instance after deserialization is complete.
|
166
|
+
@type post_Deserialize: str
|
167
|
+
variable name will be passed in as the single argument to format string.
|
168
|
+
@param import_str: import to include if type is present
|
169
|
+
@type import_str: str
|
170
|
+
"""
|
171
|
+
self.constructor = constructor
|
172
|
+
self.post_deserialize = post_deserialize
|
173
|
+
self.import_str = import_str
|
174
|
+
|
175
|
+
def get_post_deserialize(self, varname):
|
176
|
+
"""
|
177
|
+
@return: Post-deserialization code to executed (unindented) or
|
178
|
+
None if no post-deserialization is required
|
179
|
+
@rtype: str
|
180
|
+
"""
|
181
|
+
if self.post_deserialize:
|
182
|
+
return self.post_deserialize%varname
|
183
|
+
else:
|
184
|
+
return None
|
185
|
+
|
186
|
+
_SPECIAL_TYPES = {
|
187
|
+
roslib.msgs.HEADER: Special('Std_msgs::Header.new', None, 'require "std_msgs/Header"'),
|
188
|
+
roslib.msgs.TIME: Special('ROS::Time.new', None, 'require "ros/time"'),
|
189
|
+
roslib.msgs.DURATION: Special('ROS::Duration.new', None, 'require "ros/duration"'),
|
190
|
+
}
|
191
|
+
|
192
|
+
################################################################################
|
193
|
+
# utilities
|
194
|
+
|
195
|
+
# #671
|
196
|
+
def default_value(field_type, default_package):
|
197
|
+
"""
|
198
|
+
Compute default value for field_type
|
199
|
+
@param default_package: default package
|
200
|
+
@type default_package: str
|
201
|
+
@param field_type str: ROS .msg field type
|
202
|
+
@type field_type: ROS .msg field type
|
203
|
+
@return: default value encoded in Python string representation
|
204
|
+
@rtype: str
|
205
|
+
"""
|
206
|
+
if field_type in ['byte', 'int8', 'int16', 'int32', 'int64',\
|
207
|
+
'char', 'uint8', 'uint16', 'uint32', 'uint64']:
|
208
|
+
return '0'
|
209
|
+
elif field_type in ['float32', 'float64']:
|
210
|
+
return '0.0'
|
211
|
+
elif field_type == 'string':
|
212
|
+
# strings, char[], and uint8s are all optimized to be strings
|
213
|
+
return "''"
|
214
|
+
elif field_type == 'bool':
|
215
|
+
return 'false'
|
216
|
+
elif field_type.endswith(']'): # array type
|
217
|
+
base_type, is_array, array_len = roslib.msgs.parse_type(field_type)
|
218
|
+
if base_type in ['char', 'uint8']:
|
219
|
+
# strings, char[], and uint8s are all optimized to be strings
|
220
|
+
if array_len is not None:
|
221
|
+
return "[0].pack('c')*%s"%array_len
|
222
|
+
else:
|
223
|
+
return "''"
|
224
|
+
elif array_len is None: #var-length
|
225
|
+
return '[]'
|
226
|
+
else: # fixed-length, fill values
|
227
|
+
def_val = default_value(base_type, default_package)
|
228
|
+
return '[' + ','.join(itertools.repeat(def_val, array_len)) + ']'
|
229
|
+
else:
|
230
|
+
return compute_constructor(default_package, field_type)
|
231
|
+
|
232
|
+
def flatten(msg):
|
233
|
+
"""
|
234
|
+
Flattens the msg spec so that embedded message fields become
|
235
|
+
direct references. The resulting MsgSpec isn't a true/legal
|
236
|
+
L{MsgSpec} and should only be used for serializer generation
|
237
|
+
@param msg: msg to flatten
|
238
|
+
@type msg: L{MsgSpec}
|
239
|
+
@return: flatten message
|
240
|
+
@rtype: L{MsgSpec}
|
241
|
+
"""
|
242
|
+
new_types = []
|
243
|
+
new_names = []
|
244
|
+
for t, n in zip(msg.types, msg.names):
|
245
|
+
#flatten embedded types - note: bug #59
|
246
|
+
if roslib.msgs.is_registered(t):
|
247
|
+
msg_spec = flatten(roslib.msgs.get_registered(t))
|
248
|
+
new_types.extend(msg_spec.types)
|
249
|
+
for n2 in msg_spec.names:
|
250
|
+
new_names.append(n+'.'+n2)
|
251
|
+
else:
|
252
|
+
#I'm not sure if it's a performance win to flatten fixed-length arrays
|
253
|
+
#as you get n __getitems__ method calls vs. a single *array call
|
254
|
+
new_types.append(t)
|
255
|
+
new_names.append(n)
|
256
|
+
return roslib.msgs.MsgSpec(new_types, new_names, msg.constants, msg.text)
|
257
|
+
|
258
|
+
def make_ruby_safe(spec):
|
259
|
+
"""
|
260
|
+
Remap field/constant names in spec to avoid collision with Python reserved words.
|
261
|
+
@param spec: msg spec to map to new, python-safe field names
|
262
|
+
@type spec: L{MsgSpec}
|
263
|
+
@return: python-safe message specification
|
264
|
+
@rtype: L{MsgSpec}
|
265
|
+
"""
|
266
|
+
new_c = [roslib.msgs.Constant(c.type, _remap_reserved(c.name), c.val, c.val_text) for c in spec.constants]
|
267
|
+
return roslib.msgs.MsgSpec(spec.types, [_remap_reserved(n) for n in spec.names], new_c, spec.text)
|
268
|
+
|
269
|
+
def _remap_reserved(field_name):
|
270
|
+
"""
|
271
|
+
Map field_name to a python-safe representation, if necessary
|
272
|
+
@param field_name: msg field name
|
273
|
+
@type field_name: str
|
274
|
+
@return: remapped name
|
275
|
+
@rtype: str
|
276
|
+
"""
|
277
|
+
# include 'self' as well because we are within a class instance
|
278
|
+
if field_name in ['BEGIN', 'END', 'alias', 'and', 'begin', 'break', 'case', 'class', 'def', 'defined?', 'do', 'else', 'elsif', 'end', 'ensure', 'false', 'for', 'if', 'in', 'module', 'next', 'nil', 'not', 'or', 'redo', 'rescue', 'retry', 'return', 'self', 'super', 'then', 'true', 'undef', 'unless', 'until', 'when', 'while', 'yield']:
|
279
|
+
return field_name + "_"
|
280
|
+
return field_name
|
281
|
+
|
282
|
+
################################################################################
|
283
|
+
# (de)serialization routines
|
284
|
+
|
285
|
+
def compute_struct_pattern(types):
|
286
|
+
"""
|
287
|
+
@param types: type names
|
288
|
+
@type types: [str]
|
289
|
+
@return: format string for struct if types are all simple. Otherwise, return None
|
290
|
+
@rtype: str
|
291
|
+
"""
|
292
|
+
if not types: #important to filter None and empty first
|
293
|
+
return None
|
294
|
+
try:
|
295
|
+
return ''.join([SIMPLE_TYPES_DICT[t] for t in types])
|
296
|
+
except:
|
297
|
+
return None
|
298
|
+
|
299
|
+
def compute_post_deserialize(type_, varname):
|
300
|
+
"""
|
301
|
+
Compute post-deserialization code for type_, if necessary
|
302
|
+
@return: code to execute post-deserialization (unindented), or None if not necessary.
|
303
|
+
@rtype: str
|
304
|
+
"""
|
305
|
+
s = get_special(type_)
|
306
|
+
if s is not None:
|
307
|
+
return s.get_post_deserialize(varname)
|
308
|
+
|
309
|
+
def compute_constructor(package, type_):
|
310
|
+
"""
|
311
|
+
Compute python constructor expression for specified message type implementation
|
312
|
+
@param package str: package that type is being imported into. Used
|
313
|
+
to resolve type_ if package is not specified.
|
314
|
+
@type package: str
|
315
|
+
@param type_: message type
|
316
|
+
@type type_: str
|
317
|
+
"""
|
318
|
+
if is_special(type_):
|
319
|
+
return get_special(type_).constructor
|
320
|
+
else:
|
321
|
+
base_pkg, base_type_ = compute_pkg_type(package, type_)
|
322
|
+
if not roslib.msgs.is_registered("%s/%s"%(base_pkg,base_type_)):
|
323
|
+
return None
|
324
|
+
else:
|
325
|
+
return '%s::%s.new'%(base_pkg.capitalize(), base_type_)
|
326
|
+
|
327
|
+
def compute_pkg_type(package, type_):
|
328
|
+
"""
|
329
|
+
@param package: package that type is being imported into
|
330
|
+
@type package: str
|
331
|
+
@param type: message type (package resource name)
|
332
|
+
@type type: str
|
333
|
+
@return (str, str): python package and type name
|
334
|
+
"""
|
335
|
+
splits = type_.split(roslib.msgs.SEP)
|
336
|
+
if len(splits) == 1:
|
337
|
+
return package, splits[0]
|
338
|
+
elif len(splits) == 2:
|
339
|
+
return tuple(splits)
|
340
|
+
else:
|
341
|
+
raise MsgGenerationException("illegal message type: %s"%type_)
|
342
|
+
|
343
|
+
def compute_import(package, type_):
|
344
|
+
"""
|
345
|
+
Compute python import statement for specified message type implementation
|
346
|
+
@param package: package that type is being imported into
|
347
|
+
@type package: str
|
348
|
+
@param type_: message type (package resource name)
|
349
|
+
@type type_: str
|
350
|
+
@return: list of import statements (no newline) required to use type_ from package
|
351
|
+
@rtype: [str]
|
352
|
+
"""
|
353
|
+
# orig_base_type is the unresolved type
|
354
|
+
orig_base_type = roslib.msgs.base_msg_type(type_) # strip array-suffix
|
355
|
+
# resolve orig_base_type based on the current package context.
|
356
|
+
# base_type is the resolved type stripped of any package name.
|
357
|
+
# pkg is the actual package of type_.
|
358
|
+
pkg, base_type = compute_pkg_type(package, orig_base_type)
|
359
|
+
type_str = "%s/%s"%(pkg, base_type) # compute fully-qualified type
|
360
|
+
# important: have to do is_builtin check first. We do this check
|
361
|
+
# against the unresolved type builtins/specials are never
|
362
|
+
# relative. This requires some special handling for Header, which has
|
363
|
+
# two names (Header and std_msgs/Header).
|
364
|
+
if roslib.msgs.is_builtin(orig_base_type) or \
|
365
|
+
roslib.msgs.is_header_type(orig_base_type):
|
366
|
+
# of the builtin types, only special types require import
|
367
|
+
# handling. we switch to base_type as special types do not
|
368
|
+
# include package names.
|
369
|
+
if is_special(base_type):
|
370
|
+
retval = [get_special(base_type).import_str]
|
371
|
+
else:
|
372
|
+
retval = []
|
373
|
+
elif not roslib.msgs.is_registered(type_str):
|
374
|
+
retval = []
|
375
|
+
else:
|
376
|
+
retval = ['require "%s/%s"'%(pkg, base_type)]
|
377
|
+
for t in get_registered_ex(type_str).types:
|
378
|
+
sub = compute_import(package, t)
|
379
|
+
retval.extend([x for x in sub if not x in retval])
|
380
|
+
return retval
|
381
|
+
|
382
|
+
def compute_full_text_escaped(gen_deps_dict):
|
383
|
+
"""
|
384
|
+
Same as roslib.gentools.compute_full_text, except that the
|
385
|
+
resulting text is escaped to be safe for Python's triple-quote string
|
386
|
+
quoting
|
387
|
+
|
388
|
+
@param get_deps_dict: dictionary returned by get_dependencies call
|
389
|
+
@type get_deps_dict: dict
|
390
|
+
@return: concatenated text for msg/srv file and embedded msg/srv types. Text will be escaped for triple-quote
|
391
|
+
@rtype: str
|
392
|
+
"""
|
393
|
+
msg_definition = roslib.gentools.compute_full_text(gen_deps_dict)
|
394
|
+
msg_definition = msg_definition.replace('"', '\\"')
|
395
|
+
return msg_definition
|
396
|
+
|
397
|
+
def reduce_pattern(pattern):
|
398
|
+
"""
|
399
|
+
Optimize the struct format pattern.
|
400
|
+
@param pattern: struct pattern
|
401
|
+
@type pattern: str
|
402
|
+
@return: optimized struct pattern
|
403
|
+
@rtype: str
|
404
|
+
"""
|
405
|
+
if not pattern or len(pattern) == 1 or '%' in pattern:
|
406
|
+
return pattern
|
407
|
+
prev = pattern[0]
|
408
|
+
count = 1
|
409
|
+
new_pattern = ''
|
410
|
+
nums = [str(i) for i in range(0, 9)]
|
411
|
+
for c in pattern[1:]:
|
412
|
+
if c == prev and not c in nums:
|
413
|
+
count += 1
|
414
|
+
else:
|
415
|
+
if count > 1:
|
416
|
+
new_pattern = new_pattern + str(count) + prev
|
417
|
+
else:
|
418
|
+
new_pattern = new_pattern + prev
|
419
|
+
prev = c
|
420
|
+
count = 1
|
421
|
+
if count > 1:
|
422
|
+
new_pattern = new_pattern + str(count) + c
|
423
|
+
else:
|
424
|
+
new_pattern = new_pattern + prev
|
425
|
+
return new_pattern
|
426
|
+
|
427
|
+
## @param expr str: string python expression that is evaluated for serialization
|
428
|
+
## @return str: python call to write value returned by expr to serialization buffer
|
429
|
+
def serialize(expr):
|
430
|
+
return "buff.write(%s)"%expr
|
431
|
+
|
432
|
+
# int32 is very common due to length serialization, so it is special cased
|
433
|
+
def int32_pack(var):
|
434
|
+
"""
|
435
|
+
@param var: variable name
|
436
|
+
@type var: str
|
437
|
+
@return: struct packing code for an int32
|
438
|
+
"""
|
439
|
+
return serialize('@@struct_L.pack(%s)'%var)
|
440
|
+
|
441
|
+
# int32 is very common due to length serialization, so it is special cased
|
442
|
+
def int32_unpack(var, buff):
|
443
|
+
"""
|
444
|
+
@param var: variable name
|
445
|
+
@type var: str
|
446
|
+
@return: struct unpacking code for an int32
|
447
|
+
"""
|
448
|
+
return '(%s,) = @@struct_L.unpack(%s)'%(var, buff)
|
449
|
+
|
450
|
+
#NOTE: '<' = little endian
|
451
|
+
def pack(pattern, vars):
|
452
|
+
"""
|
453
|
+
create struct.pack call for when pattern is a string pattern
|
454
|
+
@param pattern: pattern for pack
|
455
|
+
@type pattern: str
|
456
|
+
@param vars: name of variables to pack
|
457
|
+
@type vars: str
|
458
|
+
"""
|
459
|
+
# - store pattern in context
|
460
|
+
pattern = reduce_pattern(pattern)
|
461
|
+
add_pattern(pattern)
|
462
|
+
return serialize("@@struct_%s.pack(%s)"%(convert_to_ruby_pattern(pattern),
|
463
|
+
vars))
|
464
|
+
def pack2(pattern, vars):
|
465
|
+
"""
|
466
|
+
create struct.pack call for when pattern is the name of a variable
|
467
|
+
@param pattern: name of variable storing string pattern
|
468
|
+
@type pattern: struct
|
469
|
+
@param vars: name of variables to pack
|
470
|
+
@type vars: str
|
471
|
+
"""
|
472
|
+
return serialize("%s.pack(%s)"%(vars, pattern))
|
473
|
+
def unpack(var, pattern, buff):
|
474
|
+
"""
|
475
|
+
create struct.unpack call for when pattern is a string pattern
|
476
|
+
@param var: name of variable to unpack
|
477
|
+
@type var: str
|
478
|
+
@param pattern: pattern for pack
|
479
|
+
@type pattern: str
|
480
|
+
@param buff: buffer to unpack from
|
481
|
+
@type buff: str
|
482
|
+
"""
|
483
|
+
# - store pattern in context
|
484
|
+
pattern = reduce_pattern(pattern)
|
485
|
+
add_pattern(pattern)
|
486
|
+
return var + " = @@struct_%s.unpack(%s)"%(convert_to_ruby_pattern(pattern),
|
487
|
+
buff)
|
488
|
+
def unpack2(var, pattern, buff):
|
489
|
+
"""
|
490
|
+
Create struct.unpack call for when pattern refers to variable
|
491
|
+
@param var: variable the stores the result of unpack call
|
492
|
+
@type var: str
|
493
|
+
@param pattern: name of variable that unpack will read from
|
494
|
+
@type pattern: str
|
495
|
+
@param buff: buffer that the unpack reads from
|
496
|
+
@type buff: StringIO
|
497
|
+
"""
|
498
|
+
return "%s = %s.unpack(%s)"%(var, buff, pattern)
|
499
|
+
|
500
|
+
################################################################################
|
501
|
+
# numpy support
|
502
|
+
|
503
|
+
# this could obviously be directly generated, but it's nice to abstract
|
504
|
+
|
505
|
+
## maps ros msg types to numpy types
|
506
|
+
_NUMPY_DTYPE = {
|
507
|
+
'float32': 'numpy.float32',
|
508
|
+
'float64': 'numpy.float64',
|
509
|
+
'bool': 'numpy.bool',
|
510
|
+
'int8': 'numpy.int8',
|
511
|
+
'int16': 'numpy.int16',
|
512
|
+
'int32': 'numpy.int32',
|
513
|
+
'int64': 'numpy.int64',
|
514
|
+
'uint8': 'numpy.uint8',
|
515
|
+
'uint16': 'numpy.uint16',
|
516
|
+
'uint32': 'numpy.uint32',
|
517
|
+
'uint64': 'numpy.uint64',
|
518
|
+
# deprecated type
|
519
|
+
'char' : 'numpy.uint8',
|
520
|
+
'byte' : 'numpy.int8',
|
521
|
+
}
|
522
|
+
|
523
|
+
################################################################################
|
524
|
+
# (De)serialization generators
|
525
|
+
|
526
|
+
_serial_context = ''
|
527
|
+
_context_stack = []
|
528
|
+
|
529
|
+
_counter = 0
|
530
|
+
def next_var():
|
531
|
+
# we could optimize this by reusing vars once the context is popped
|
532
|
+
global _counter
|
533
|
+
_counter += 1
|
534
|
+
return '_v%s'%_counter
|
535
|
+
|
536
|
+
def push_context(context):
|
537
|
+
"""
|
538
|
+
Push new variable context onto context stack. The context stack
|
539
|
+
manages field-reference context for serialization, e.g. 'self.foo'
|
540
|
+
vs. 'self.bar.foo' vs. 'var.foo'
|
541
|
+
"""
|
542
|
+
global _serial_context, _context_stack
|
543
|
+
_context_stack.append(_serial_context)
|
544
|
+
_serial_context = context
|
545
|
+
|
546
|
+
def pop_context():
|
547
|
+
"""
|
548
|
+
Pop variable context from context stack. The context stack manages
|
549
|
+
field-reference context for serialization, e.g. 'self.foo'
|
550
|
+
vs. 'self.bar.foo' vs. 'var.foo'
|
551
|
+
"""
|
552
|
+
global _serial_context
|
553
|
+
_serial_context = _context_stack.pop()
|
554
|
+
|
555
|
+
_context_patterns = []
|
556
|
+
def add_pattern(p):
|
557
|
+
"""
|
558
|
+
Record struct pattern that's been used for (de)serialization
|
559
|
+
"""
|
560
|
+
_context_patterns.append(p)
|
561
|
+
def clear_patterns():
|
562
|
+
"""
|
563
|
+
Clear record of struct pattern that have been used for (de)serialization
|
564
|
+
"""
|
565
|
+
del _context_patterns[:]
|
566
|
+
def get_patterns():
|
567
|
+
"""
|
568
|
+
@return: record of struct pattern that have been used for (de)serialization
|
569
|
+
"""
|
570
|
+
return _context_patterns[:]
|
571
|
+
|
572
|
+
# These are the workhorses of the message generation. The generators
|
573
|
+
# are implemented as iterators, where each iteration value is a line
|
574
|
+
# of Python code. The generators will invoke underlying generators,
|
575
|
+
# using the context stack to manage any changes in variable-naming, so
|
576
|
+
# that code can be reused as much as possible.
|
577
|
+
|
578
|
+
def len_serializer_generator(var, is_string, serialize):
|
579
|
+
"""
|
580
|
+
Generator for array-length serialization (32-bit, little-endian unsigned integer)
|
581
|
+
@param var: variable name
|
582
|
+
@type var: str
|
583
|
+
@param is_string: if True, variable is a string type
|
584
|
+
@type is_string: bool
|
585
|
+
@param serialize bool: if True, generate code for
|
586
|
+
serialization. Other, generate code for deserialization
|
587
|
+
@type serialize: bool
|
588
|
+
"""
|
589
|
+
if serialize:
|
590
|
+
yield "length = %s.length"%var
|
591
|
+
# NOTE: it's more difficult to save a call to struct.pack with
|
592
|
+
# the array length as we are already using *array_val to pass
|
593
|
+
# into struct.pack as *args. Although it's possible that
|
594
|
+
# Python is not optimizing it, it is potentially worse for
|
595
|
+
# performance to attempt to combine
|
596
|
+
if not is_string:
|
597
|
+
yield int32_pack("length")
|
598
|
+
else:
|
599
|
+
yield "start = end_point"
|
600
|
+
yield "end_point += 4"
|
601
|
+
yield int32_unpack('length', 'str[start..(end_point-1)]') #4 = struct.calcsize('<i')
|
602
|
+
|
603
|
+
def string_serializer_generator(package, type_, name, serialize):
|
604
|
+
"""
|
605
|
+
Generator for string types. similar to arrays, but with more
|
606
|
+
efficient call to struct.pack.
|
607
|
+
@param name: spec field name
|
608
|
+
@type name: str
|
609
|
+
@param serialize: if True, generate code for
|
610
|
+
serialization. Other, generate code for deserialization
|
611
|
+
@type serialize: bool
|
612
|
+
"""
|
613
|
+
# don't optimize in deserialization case as assignment doesn't
|
614
|
+
# work
|
615
|
+
if _serial_context and serialize:
|
616
|
+
# optimize as string serialization accesses field twice
|
617
|
+
yield "_x = %s%s"%(_serial_context, name)
|
618
|
+
var = "_x"
|
619
|
+
else:
|
620
|
+
var = _serial_context+name
|
621
|
+
|
622
|
+
# the length generator is a noop if serialize is True as we
|
623
|
+
# optimize the serialization call.
|
624
|
+
base_type, is_array, array_len = roslib.msgs.parse_type(type_)
|
625
|
+
# - don't serialize length for fixed-length arrays of bytes
|
626
|
+
if base_type not in ['uint8', 'char'] or array_len is None:
|
627
|
+
for y in len_serializer_generator(var, True, serialize):
|
628
|
+
yield y #serialize string length
|
629
|
+
|
630
|
+
if serialize:
|
631
|
+
#serialize length and string together
|
632
|
+
|
633
|
+
#check to see if its a uint8/char type, in which case we need to convert to string before serializing
|
634
|
+
base_type, is_array, array_len = roslib.msgs.parse_type(type_)
|
635
|
+
if base_type in ['uint8', 'char']:
|
636
|
+
yield "# - if encoded as a list instead, serialize as bytes instead of string"
|
637
|
+
if array_len is None:
|
638
|
+
yield "if type(%s) in [list, tuple]"%var
|
639
|
+
yield INDENT+pack2('"La#{length}"', "[length, *%s]"%var)
|
640
|
+
yield "else"
|
641
|
+
yield INDENT+pack2('"La#{length}"', "[length, %s]"%var)
|
642
|
+
yield "end"
|
643
|
+
else:
|
644
|
+
yield "if type(%s) in [list, tuple]"%var
|
645
|
+
yield INDENT+pack('C%s'%array_len, "*%s"%var)
|
646
|
+
yield "else"
|
647
|
+
yield INDENT+pack('C%s'%array_len, var)
|
648
|
+
yield "end"
|
649
|
+
else:
|
650
|
+
# py3k: struct.pack() now only allows bytes for the s string pack code.
|
651
|
+
# FIXME: for py3k, this needs to be w/ encode, but this interferes with actual byte data
|
652
|
+
#yield pack2("'<I%ss'%length", "length, %s.encode()"%var) #Py3k bugfix (see http://docs.python.org/dev/whatsnew/3.2.html#porting-to-python-3-2)
|
653
|
+
yield pack2('"La#{length}"', "[length, %s]"%var)
|
654
|
+
else:
|
655
|
+
yield "start = end_point"
|
656
|
+
if array_len is not None:
|
657
|
+
yield "end_point += %s" % array_len
|
658
|
+
else:
|
659
|
+
yield "end_point += length"
|
660
|
+
yield "%s = str[start..(end_point-1)]" % var
|
661
|
+
|
662
|
+
def array_serializer_generator(package, type_, name, serialize, is_numpy):
|
663
|
+
"""
|
664
|
+
Generator for array types
|
665
|
+
@raise MsgGenerationException: if array spec is invalid
|
666
|
+
"""
|
667
|
+
base_type, is_array, array_len = roslib.msgs.parse_type(type_)
|
668
|
+
if not is_array:
|
669
|
+
raise MsgGenerationException("Invalid array spec: %s"%type_)
|
670
|
+
var_length = array_len is None
|
671
|
+
|
672
|
+
# handle fixed-size byte arrays could be slightly more efficient
|
673
|
+
# as we recalculated the length in the generated code.
|
674
|
+
if base_type in ['char', 'uint8']: #treat unsigned int8 arrays as string type
|
675
|
+
for y in string_serializer_generator(package, type_, name, serialize):
|
676
|
+
yield y
|
677
|
+
return
|
678
|
+
|
679
|
+
var = _serial_context+name
|
680
|
+
try:
|
681
|
+
# yield length serialization, if necessary
|
682
|
+
if var_length:
|
683
|
+
for y in len_serializer_generator(var, False, serialize):
|
684
|
+
yield y #serialize array length
|
685
|
+
length = None
|
686
|
+
else:
|
687
|
+
length = array_len
|
688
|
+
|
689
|
+
#optimization for simple arrays
|
690
|
+
if is_simple(base_type):
|
691
|
+
if var_length:
|
692
|
+
pattern = compute_struct_pattern([base_type])
|
693
|
+
yield 'pattern = "%s#{length}"'%convert_to_ruby_pattern(pattern)
|
694
|
+
if serialize:
|
695
|
+
if is_numpy:
|
696
|
+
yield pack_numpy(var)
|
697
|
+
else:
|
698
|
+
yield pack2('pattern', "*"+var)
|
699
|
+
else:
|
700
|
+
yield "start = end_point"
|
701
|
+
yield 'end_point += ROS::Struct::calc_size("#{pattern}")'
|
702
|
+
if is_numpy:
|
703
|
+
dtype = _NUMPY_DTYPE[base_type]
|
704
|
+
yield unpack_numpy(var, 'length', dtype, 'str[start..(end_point-1)]')
|
705
|
+
else:
|
706
|
+
yield unpack2(var, 'pattern', 'str[start..(end_point-1)]')
|
707
|
+
else:
|
708
|
+
pattern = "%s%s"%(length, compute_struct_pattern([base_type]))
|
709
|
+
if serialize:
|
710
|
+
if is_numpy:
|
711
|
+
yield pack_numpy(var)
|
712
|
+
else:
|
713
|
+
yield pack(pattern, "*"+var)
|
714
|
+
else:
|
715
|
+
yield "start = end_point"
|
716
|
+
yield "end_point += %s"%struct.calcsize('%s'%convert_to_ruby_pattern(pattern))
|
717
|
+
if is_numpy:
|
718
|
+
dtype = _NUMPY_DTYPE[base_type]
|
719
|
+
yield unpack_numpy(var, length, dtype, 'str[start..(end_point-1)]')
|
720
|
+
else:
|
721
|
+
yield unpack(var, pattern, 'str[start..(end_point-1)]')
|
722
|
+
if not serialize and base_type == 'bool':
|
723
|
+
# convert uint8 to bool
|
724
|
+
if base_type == 'bool':
|
725
|
+
yield "%s = map(bool, %s)"%(var, var)
|
726
|
+
|
727
|
+
else:
|
728
|
+
#generic recursive serializer
|
729
|
+
#NOTE: this is functionally equivalent to the is_registered branch of complex_serializer_generator
|
730
|
+
|
731
|
+
# choose a unique temporary variable for iterating
|
732
|
+
loop_var = 'val%s'%len(_context_stack)
|
733
|
+
|
734
|
+
# compute the variable context and factory to use
|
735
|
+
if base_type == 'string':
|
736
|
+
push_context('')
|
737
|
+
factory = string_serializer_generator(package, base_type, loop_var, serialize)
|
738
|
+
else:
|
739
|
+
push_context('%s.'%loop_var)
|
740
|
+
factory = serializer_generator(package, get_registered_ex(base_type), serialize, is_numpy)
|
741
|
+
|
742
|
+
if serialize:
|
743
|
+
yield 'for %s in %s'%(loop_var, var)
|
744
|
+
else:
|
745
|
+
yield '%s = []'%var
|
746
|
+
if var_length:
|
747
|
+
yield 'length.times do'
|
748
|
+
else:
|
749
|
+
yield '%s.times do'%length
|
750
|
+
if base_type != 'string':
|
751
|
+
yield INDENT + '%s = %s'%(loop_var, compute_constructor(package, base_type))
|
752
|
+
for y in factory:
|
753
|
+
yield INDENT + y
|
754
|
+
if not serialize:
|
755
|
+
yield INDENT + '%s.push(%s)'%(var, loop_var)
|
756
|
+
pop_context()
|
757
|
+
yield 'end'
|
758
|
+
|
759
|
+
except MsgGenerationException:
|
760
|
+
raise #re-raise
|
761
|
+
except Exception as e:
|
762
|
+
raise MsgGenerationException(e) #wrap
|
763
|
+
|
764
|
+
def complex_serializer_generator(package, type_, name, serialize, is_numpy):
|
765
|
+
"""
|
766
|
+
Generator for serializing complex type
|
767
|
+
@param serialize: if True, generate serialization
|
768
|
+
code. Otherwise, deserialization code.
|
769
|
+
@type serialize: bool
|
770
|
+
@param is_numpy: if True, generate serializer code for numpy
|
771
|
+
datatypes instead of Python lists
|
772
|
+
@type is_numpy: bool
|
773
|
+
@raise MsgGenerationException: if type is not a valid
|
774
|
+
"""
|
775
|
+
# ordering of these statements is important as we mutate the type
|
776
|
+
# string we are checking throughout. parse_type strips array
|
777
|
+
# brackets, then we check for the 'complex' builtin types (string,
|
778
|
+
# time, duration, Header), then we canonicalize it to an embedded
|
779
|
+
# message type.
|
780
|
+
_, is_array, _ = roslib.msgs.parse_type(type_)
|
781
|
+
|
782
|
+
#Array
|
783
|
+
if is_array:
|
784
|
+
for y in array_serializer_generator(package, type_, name, serialize, is_numpy):
|
785
|
+
yield y
|
786
|
+
#Embedded Message
|
787
|
+
elif type_ == 'string':
|
788
|
+
for y in string_serializer_generator(package, type_, name, serialize):
|
789
|
+
yield y
|
790
|
+
else:
|
791
|
+
if not is_special(type_):
|
792
|
+
# canonicalize type
|
793
|
+
pkg, base_type = compute_pkg_type(package, type_)
|
794
|
+
type_ = "%s/%s"%(pkg, base_type)
|
795
|
+
if roslib.msgs.is_registered(type_):
|
796
|
+
# descend data structure ####################
|
797
|
+
ctx_var = next_var()
|
798
|
+
yield "%s = %s"%(ctx_var, _serial_context+name)
|
799
|
+
push_context(ctx_var+'.')
|
800
|
+
# unoptimized code
|
801
|
+
#push_context(_serial_context+name+'.')
|
802
|
+
for y in serializer_generator(package, get_registered_ex(type_), serialize, is_numpy):
|
803
|
+
yield y #recurs on subtype
|
804
|
+
pop_context()
|
805
|
+
else:
|
806
|
+
#Invalid
|
807
|
+
raise MsgGenerationException("Unknown type: %s. Package context is %s"%(type_, package))
|
808
|
+
|
809
|
+
def simple_serializer_generator(spec, start, end_point, serialize): #primitives that can be handled with struct
|
810
|
+
"""
|
811
|
+
Generator (de)serialization code for multiple fields from spec
|
812
|
+
@param spec: MsgSpec
|
813
|
+
@type spec: MsgSpec
|
814
|
+
@param start: first field to serialize
|
815
|
+
@type start: int
|
816
|
+
@param end: last field to serialize
|
817
|
+
@type end: int
|
818
|
+
"""
|
819
|
+
# optimize member var access
|
820
|
+
if end_point - start > 1 and _serial_context.endswith('.'):
|
821
|
+
yield '_x = '+_serial_context[:-1]
|
822
|
+
vars_ = '_x.' + (', _x.').join(spec.names[start:end_point])
|
823
|
+
else:
|
824
|
+
vars_ = _serial_context + (', '+_serial_context).join(spec.names[start:end_point])
|
825
|
+
|
826
|
+
pattern = compute_struct_pattern(spec.types[start:end_point])
|
827
|
+
if serialize:
|
828
|
+
yield pack(pattern, vars_)
|
829
|
+
else:
|
830
|
+
yield "start = end_point"
|
831
|
+
yield "end_point += ROS::Struct::calc_size('%s')"%convert_to_ruby_pattern(reduce_pattern(pattern))
|
832
|
+
yield unpack('(%s,)'%vars_, pattern, 'str[start..(end_point-1)]')
|
833
|
+
|
834
|
+
# convert uint8 to bool. this doesn't add much value as Python
|
835
|
+
# equality test on a field will return that True == 1, but I
|
836
|
+
# want to be consistent with bool
|
837
|
+
bool_vars = [(f, t) for f, t in zip(spec.names[start:end_point], spec.types[start:end_point]) if t == 'bool']
|
838
|
+
for f, t in bool_vars:
|
839
|
+
#TODO: could optimize this as well
|
840
|
+
var = _serial_context+f
|
841
|
+
yield "%s = bool(%s)"%(var, var)
|
842
|
+
|
843
|
+
def serializer_generator(package, spec, serialize, is_numpy):
|
844
|
+
"""
|
845
|
+
Python generator that yields un-indented python code for
|
846
|
+
(de)serializing MsgSpec. The code this yields is meant to be
|
847
|
+
included in a class method and cannot be used
|
848
|
+
standalone. serialize_fn_generator and deserialize_fn_generator
|
849
|
+
wrap method to provide appropriate class field initializations.
|
850
|
+
@param package: name of package the spec is being used in
|
851
|
+
@type package: str
|
852
|
+
@param serialize: if True, yield serialization
|
853
|
+
code. Otherwise, yield deserialization code.
|
854
|
+
@type serialize: bool
|
855
|
+
@param is_numpy: if True, generate serializer code for numpy datatypes instead of Python lists
|
856
|
+
@type is_numpy: bool
|
857
|
+
"""
|
858
|
+
# Break spec into chunks of simple (primitives) vs. complex (arrays, etc...)
|
859
|
+
# Simple types are batch serialized using the python struct module.
|
860
|
+
# Complex types are individually serialized
|
861
|
+
if spec is None:
|
862
|
+
raise MsgGenerationException("spec is none")
|
863
|
+
names, types = spec.names, spec.types
|
864
|
+
if serialize and not len(names): #Empty
|
865
|
+
yield "pass"
|
866
|
+
return
|
867
|
+
|
868
|
+
# iterate through types. whenever we encounter a non-simple type,
|
869
|
+
# yield serializer for any simple types we've encountered until
|
870
|
+
# then, then yield the complex type serializer
|
871
|
+
curr = 0
|
872
|
+
for (i, type_) in enumerate(types):
|
873
|
+
if not is_simple(type_):
|
874
|
+
if i != curr: #yield chunk of simples
|
875
|
+
for y in simple_serializer_generator(spec, curr, i, serialize):
|
876
|
+
yield y
|
877
|
+
curr = i+1
|
878
|
+
for y in complex_serializer_generator(package, type_, names[i], serialize, is_numpy):
|
879
|
+
yield y
|
880
|
+
if curr < len(types): #yield rest of simples
|
881
|
+
for y in simple_serializer_generator(spec, curr, len(types), serialize):
|
882
|
+
yield y
|
883
|
+
|
884
|
+
def serialize_fn_generator(package, spec, is_numpy=False):
|
885
|
+
"""
|
886
|
+
generator for body of serialize() function
|
887
|
+
@param is_numpy: if True, generate serializer code for numpy
|
888
|
+
datatypes instead of Python lists
|
889
|
+
@type is_numpy: bool
|
890
|
+
"""
|
891
|
+
# method-var context #########
|
892
|
+
yield "begin"
|
893
|
+
push_context('@')
|
894
|
+
#NOTE: we flatten the spec for optimal serialization
|
895
|
+
for y in serializer_generator(package, flatten(spec), True, is_numpy):
|
896
|
+
yield " "+y
|
897
|
+
pop_context()
|
898
|
+
yield """rescue => exception
|
899
|
+
raise "some erro in serialize: #{exception}"
|
900
|
+
"""
|
901
|
+
yield "end"
|
902
|
+
# done w/ method-var context #
|
903
|
+
|
904
|
+
def deserialize_fn_generator(package, spec, is_numpy=False):
|
905
|
+
"""
|
906
|
+
generator for body of deserialize() function
|
907
|
+
@param is_numpy: if True, generate serializer code for numpy
|
908
|
+
datatypes instead of Python lists
|
909
|
+
@type is_numpy: bool
|
910
|
+
"""
|
911
|
+
yield "begin"
|
912
|
+
|
913
|
+
#Instantiate embedded type classes
|
914
|
+
for type_, name in spec.fields():
|
915
|
+
if roslib.msgs.is_registered(type_):
|
916
|
+
yield " if @%s == nil"%name
|
917
|
+
yield " @%s = %s"%(name, compute_constructor(package, type_))
|
918
|
+
yield " end"
|
919
|
+
yield " end_point = 0" #initialize var
|
920
|
+
|
921
|
+
# method-var context #########
|
922
|
+
push_context('@')
|
923
|
+
#NOTE: we flatten the spec for optimal serialization
|
924
|
+
for y in serializer_generator(package, flatten(spec), False, is_numpy):
|
925
|
+
yield " "+y
|
926
|
+
pop_context()
|
927
|
+
# done w/ method-var context #
|
928
|
+
|
929
|
+
# generate post-deserialization code
|
930
|
+
for type_, name in spec.fields():
|
931
|
+
code = compute_post_deserialize(type_, "@%s"%name)
|
932
|
+
if code:
|
933
|
+
yield " %s"%code
|
934
|
+
|
935
|
+
yield " return self"
|
936
|
+
yield "rescue => exception"
|
937
|
+
yield """ raise \"message DeserializationError: #{exception}\" #most likely buffer underfill
|
938
|
+
end"""
|
939
|
+
|
940
|
+
def msg_generator_internal(package, name, spec):
|
941
|
+
"""
|
942
|
+
Python code generator for .msg files. Takes in a package name,
|
943
|
+
message name, and message specification and generates a Python
|
944
|
+
message class.
|
945
|
+
|
946
|
+
@param package: name of package for message
|
947
|
+
@type package: str
|
948
|
+
@param name: base type name of message, e.g. 'Empty', 'String'
|
949
|
+
@type name: str
|
950
|
+
@param spec: parsed .msg specification
|
951
|
+
@type spec: L{MsgSpec}
|
952
|
+
"""
|
953
|
+
|
954
|
+
# #2990: have to compute md5sum before any calls to make_python_safe
|
955
|
+
|
956
|
+
# generate dependencies dictionary. omit files calculation as we
|
957
|
+
# rely on in-memory MsgSpecs instead so that we can generate code
|
958
|
+
# for older versions of msg files
|
959
|
+
try:
|
960
|
+
gendeps_dict = roslib.gentools.get_dependencies(spec, package, compute_files=False)
|
961
|
+
except roslib.msgs.MsgSpecException as e:
|
962
|
+
raise MsgGenerationException("Cannot generate .msg for %s/%s: %s"%(package, name, str(e)))
|
963
|
+
md5sum = roslib.gentools.compute_md5(gendeps_dict)
|
964
|
+
|
965
|
+
# remap spec names to be Python-safe
|
966
|
+
spec = make_ruby_safe(spec)
|
967
|
+
spec_names = spec.names
|
968
|
+
|
969
|
+
# for not capital class like tfMessages
|
970
|
+
capitalized_name = name[0].upper() + name[1:]
|
971
|
+
|
972
|
+
# #1807 : this will be much cleaner when msggenerator library is
|
973
|
+
# rewritten to not use globals
|
974
|
+
clear_patterns()
|
975
|
+
|
976
|
+
yield '# autogenerated by genmsg_ruby from %s.msg. Do not edit.'%name
|
977
|
+
yield "require 'ros/message'\n"
|
978
|
+
import_strs = []
|
979
|
+
for t in spec.types:
|
980
|
+
import_strs.extend(compute_import(package, t))
|
981
|
+
import_strs = set(import_strs)
|
982
|
+
for i in import_strs:
|
983
|
+
if i:
|
984
|
+
yield i
|
985
|
+
|
986
|
+
yield ''
|
987
|
+
|
988
|
+
yield "module %s\n"%package.capitalize()
|
989
|
+
|
990
|
+
fulltype = '%s%s%s'%(package, roslib.msgs.SEP, name)
|
991
|
+
|
992
|
+
#Yield data class first, e.g. Point2D
|
993
|
+
yield 'class %s <::ROS::Message'%capitalized_name
|
994
|
+
|
995
|
+
yield """ def self.md5sum
|
996
|
+
\"%s\"
|
997
|
+
end
|
998
|
+
"""%(md5sum)
|
999
|
+
yield """ def self.type
|
1000
|
+
\"%s\"
|
1001
|
+
end
|
1002
|
+
"""%(fulltype)
|
1003
|
+
if spec.has_header():
|
1004
|
+
bool_val = 'true'
|
1005
|
+
else:
|
1006
|
+
bool_val = 'false'
|
1007
|
+
yield """ def has_header?
|
1008
|
+
%s
|
1009
|
+
end
|
1010
|
+
"""%bool_val
|
1011
|
+
# note: we introduce an extra newline to protect the escaping from quotes in the message
|
1012
|
+
yield """ def message_definition
|
1013
|
+
\"%s\n\"
|
1014
|
+
end"""%compute_full_text_escaped(gendeps_dict)
|
1015
|
+
|
1016
|
+
if spec.constants:
|
1017
|
+
yield ' # Pseudo-constants'
|
1018
|
+
for c in spec.constants:
|
1019
|
+
if c.type == 'string':
|
1020
|
+
val = c.val
|
1021
|
+
if '"' in val and "'" in val:
|
1022
|
+
# crude escaping of \ and "
|
1023
|
+
escaped = c.val.replace('\\', '\\\\')
|
1024
|
+
escaped = escaped.replace('\"', '\\"')
|
1025
|
+
yield ' %s = "%s"'%(c.name, escaped)
|
1026
|
+
elif '"' in val: #use raw encoding for prettiness
|
1027
|
+
yield " %s = r'%s'"%(c.name, val)
|
1028
|
+
elif "'" in val: #use raw encoding for prettiness
|
1029
|
+
yield ' %s = r"%s"'%(c.name, val)
|
1030
|
+
else:
|
1031
|
+
yield " %s = '%s'"%(c.name, val)
|
1032
|
+
else:
|
1033
|
+
yield ' %s = %s'%(c.name, c.val)
|
1034
|
+
yield ''
|
1035
|
+
yield " attr_accessor "+", ".join([":"+x for x in spec_names])+"\n"
|
1036
|
+
|
1037
|
+
yield '_REPLACE_FOR_STRUCT_'
|
1038
|
+
if len(spec_names):
|
1039
|
+
yield " @@struct_L = ::ROS::Struct.new(\"L\")"
|
1040
|
+
yield " @@slot_types = ['"+"','".join(spec.types)+"']"
|
1041
|
+
else:
|
1042
|
+
yield " @@struct_L = ::ROS::Struct.new(\"L\")"
|
1043
|
+
yield " @@slot_types = []"
|
1044
|
+
|
1045
|
+
yield """
|
1046
|
+
def initialize
|
1047
|
+
# Constructor. Any message fields that are implicitly/explicitly
|
1048
|
+
# set to None will be assigned a default value. The recommend
|
1049
|
+
# use is keyword arguments as this is more robust to future message
|
1050
|
+
# changes. You cannot mix in-order arguments and keyword arguments.
|
1051
|
+
#
|
1052
|
+
# The available fields are:
|
1053
|
+
# %s
|
1054
|
+
#
|
1055
|
+
# @param args: complete set of field values, in .msg order
|
1056
|
+
# @param kwds: use keyword arguments corresponding to message field names
|
1057
|
+
# to set specific fields.
|
1058
|
+
#
|
1059
|
+
"""%','.join(spec_names)
|
1060
|
+
if len(spec_names):
|
1061
|
+
yield " # message fields cannot be None, assign default values for those that are"
|
1062
|
+
if len(spec_names) > 0:
|
1063
|
+
for (t, s) in zip(spec.types, spec_names):
|
1064
|
+
yield " @%s = %s"%(s, default_value(t, package))
|
1065
|
+
yield " end"
|
1066
|
+
yield """
|
1067
|
+
def _get_types
|
1068
|
+
# internal API method
|
1069
|
+
return @slot_types
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
def serialize(buff)
|
1073
|
+
# serialize message into buffer
|
1074
|
+
# @param buff: buffer
|
1075
|
+
# @type buff: StringIO"""
|
1076
|
+
for y in serialize_fn_generator(package, spec):
|
1077
|
+
yield " "+ y
|
1078
|
+
yield " end"
|
1079
|
+
yield """
|
1080
|
+
def deserialize(str)
|
1081
|
+
# unpack serialized message in str into this message instance
|
1082
|
+
# @param str: byte array of serialized message
|
1083
|
+
# @type str: str
|
1084
|
+
"""
|
1085
|
+
for y in deserialize_fn_generator(package, spec):
|
1086
|
+
yield " " + y
|
1087
|
+
yield " end"
|
1088
|
+
yield "end # end of class"
|
1089
|
+
yield "end # end of module"
|
1090
|
+
|
1091
|
+
import copy
|
1092
|
+
import re
|
1093
|
+
|
1094
|
+
def convert_to_ruby_pattern(python_pattern):
|
1095
|
+
ruby_pattern = copy.copy(python_pattern)
|
1096
|
+
# get python key
|
1097
|
+
for (py, ru) in PYTHON_RUBY_DICT.iteritems():
|
1098
|
+
ruby_pattern = re.sub(py, ru, ruby_pattern)
|
1099
|
+
# swap number and char
|
1100
|
+
return re.sub(r"([0-9]+)([A-Za-z])", r'\2\1', ruby_pattern)
|
1101
|
+
|
1102
|
+
def msg_generator(package, base_name, spec):
|
1103
|
+
generated = ''
|
1104
|
+
for l in msg_generator_internal(package, base_name, spec):
|
1105
|
+
generated += l + "\n"
|
1106
|
+
|
1107
|
+
patterns = get_patterns()
|
1108
|
+
structs = ''
|
1109
|
+
for p in set(patterns):
|
1110
|
+
if p == 'I':
|
1111
|
+
continue
|
1112
|
+
ruby_p = convert_to_ruby_pattern(p)
|
1113
|
+
var_name = ' @@struct_%s'%ruby_p
|
1114
|
+
structs += '%s = ::ROS::Struct.new("%s")\n'%(var_name, ruby_p)
|
1115
|
+
clear_patterns()
|
1116
|
+
import re
|
1117
|
+
return re.sub('_REPLACE_FOR_STRUCT_', structs, generated, 1)
|
1118
|
+
|
1119
|
+
def gen_msg(path, output_dir_prefix=None):
|
1120
|
+
f = os.path.abspath(path)
|
1121
|
+
(package_dir, package) = roslib.packages.get_dir_pkg(f)
|
1122
|
+
(name, spec) = roslib.msgs.load_from_file(f, package)
|
1123
|
+
base_name = roslib.names.resource_name_base(name)
|
1124
|
+
if not output_dir_prefix:
|
1125
|
+
output_dir_prefix = '%s/msg_gen/ruby'%package_dir
|
1126
|
+
output_dir = '%s/%s'%(output_dir_prefix, package)
|
1127
|
+
if not os.path.exists(output_dir):
|
1128
|
+
os.makedirs(output_dir)
|
1129
|
+
out = open('%s/%s.rb'%(output_dir, base_name), 'w')
|
1130
|
+
out.write(msg_generator(package, base_name, spec))
|
1131
|
+
out.close()
|
1132
|
+
|
1133
|
+
if __name__ == '__main__':
|
1134
|
+
for arg in sys.argv[1:]:
|
1135
|
+
gen_msg(arg)
|