rosruby 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|