1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 """
36 Library for Python message generation.
37
38 The structure of the serialization descends several levels of serializers:
39 - msg_generator: generator for an individual msg file
40 - serialize_fn_generator: generator for msg.serialize()
41 - serializer_generator
42 - field-type-specific serializers
43 - deserialize_fn_generator: generator for msg.deserialize()
44 - serializer_generator
45 - field-type-specific serializers
46 """
47
48
49
50
51 import os
52 import keyword
53 import shutil
54 import atexit
55 import itertools
56 import sys
57 import tempfile
58 import traceback
59 import struct
60
61 from cStringIO import StringIO
62
63 import roslib.exceptions
64 import roslib.gentools
65 import roslib.msgs
66 import roslib.packages
67
68
69 INDENT = ' '
70
72 """
73 Exception type for errors in roslib.genpy
74 """
75 pass
76
78 """
79 wrapper for roslib.msgs.get_registered that wraps unknown types with a MsgGenerationException
80 @param type_: ROS message type
81 @type type_: str
82 """
83 try:
84 return roslib.msgs.get_registered(type_)
85 except KeyError:
86 raise MsgGenerationException("Unknown type [%s]. Please check that the manifest.xml correctly declares dependencies."%type_)
87
88
89
90
91 SIMPLE_TYPES_DICT = {
92 'int8': 'b',
93 'uint8': 'B',
94
95
96 'bool': 'B',
97 'int16' : 'h',
98 'uint16' : 'H',
99 'int32' : 'i',
100 'uint32' : 'I',
101 'int64' : 'q',
102 'uint64' : 'Q',
103 'float32': 'f',
104 'float64': 'd',
105
106 'char' : 'B',
107 'byte' : 'b',
108 }
109
110
111 SIMPLE_TYPES = SIMPLE_TYPES_DICT.keys()
112
114 """
115 @return bool: True if type is a 'simple' type, i.e. is of
116 fixed/known serialization length. This is effectively all primitive
117 types except for string
118 @rtype: bool
119 """
120 return type_ in SIMPLE_TYPES
121
123 """
124 @return True if type_ is a special type (i.e. builtin represented as a class instead of a primitive)
125 @rtype: bool
126 """
127 return type_ in _SPECIAL_TYPES
128
130 """
131 @return: special type handler for type_ or None
132 @rtype: L{Special}
133 """
134 return _SPECIAL_TYPES.get(type_, None)
135
136
137
138
140
141 - def __init__(self, constructor, post_deserialize, import_str):
142 """
143 @param constructor: expression to instantiate new type instance for deserialization
144 @type constructor: str
145 @param post_Deserialize: format string for expression to evaluate on type instance after deserialization is complete.
146 @type post_Deserialize: str
147 variable name will be passed in as the single argument to format string.
148 @param import_str: import to include if type is present
149 @type import_str: str
150 """
151 self.constructor = constructor
152 self.post_deserialize = post_deserialize
153 self.import_str = import_str
154
155 - def get_post_deserialize(self, varname):
156 """
157 @return: Post-deserialization code to executed (unindented) or
158 None if no post-deserialization is required
159 @rtype: str
160 """
161 if self.post_deserialize:
162 return self.post_deserialize%varname
163 else:
164 return None
165
166 _SPECIAL_TYPES = {
167 roslib.msgs.HEADER: Special('std_msgs.msg._Header.Header()', None, 'import std_msgs.msg'),
168 roslib.msgs.TIME: Special('roslib.rostime.Time()', '%s.canon()', 'import roslib.rostime'),
169 roslib.msgs.DURATION: Special('roslib.rostime.Duration()', '%s.canon()', 'import roslib.rostime'),
170 }
171
172
173
174
175
177 """
178 Compute default value for field_type
179 @param default_package: default package
180 @type default_package: str
181 @param field_type str: ROS .msg field type
182 @type field_type: ROS .msg field type
183 @return: default value encoded in Python string representation
184 @rtype: str
185 """
186 if field_type in ['byte', 'int8', 'int16', 'int32', 'int64',\
187 'char', 'uint8', 'uint16', 'uint32', 'uint64']:
188 return '0'
189 elif field_type in ['float32', 'float64']:
190 return '0.'
191 elif field_type == 'string':
192
193 return "''"
194 elif field_type == 'bool':
195 return 'False'
196 elif field_type.endswith(']'):
197 base_type, is_array, array_len = roslib.msgs.parse_type(field_type)
198 if base_type in ['byte', 'uint8']:
199
200 if array_len is not None:
201 return "chr(0)*%s"%array_len
202 else:
203 return "''"
204 elif array_len is None:
205 return '[]'
206 else:
207 def_val = default_value(base_type, default_package)
208 return '[' + ','.join(itertools.repeat(def_val, array_len)) + ']'
209 else:
210 return compute_constructor(default_package, field_type)
211
213 """
214 Flattens the msg spec so that embedded message fields become
215 direct references. The resulting MsgSpec isn't a true/legal
216 L{MsgSpec} and should only be used for serializer generation
217 @param msg: msg to flatten
218 @type msg: L{MsgSpec}
219 @return: flatten message
220 @rtype: L{MsgSpec}
221 """
222 new_types = []
223 new_names = []
224 for t, n in zip(msg.types, msg.names):
225
226 if roslib.msgs.is_registered(t):
227 msg_spec = flatten(roslib.msgs.get_registered(t))
228 new_types.extend(msg_spec.types)
229 for n2 in msg_spec.names:
230 new_names.append(n+'.'+n2)
231 else:
232
233
234 new_types.append(t)
235 new_names.append(n)
236 return roslib.msgs.MsgSpec(new_types, new_names, msg.constants, msg.text)
237
239 """
240 Remap field/constant names in spec to avoid collision with Python reserved words.
241 @param spec: msg spec to map to new, python-safe field names
242 @type spec: L{MsgSpec}
243 @return: python-safe message specification
244 @rtype: L{MsgSpec}
245 """
246 new_c = [roslib.msgs.Constant(c.type, _remap_reserved(c.name), c.val, c.val_text) for c in spec.constants]
247 return roslib.msgs.MsgSpec(spec.types, [_remap_reserved(n) for n in spec.names], new_c, spec.text)
248
250 """
251 Map field_name to a python-safe representation, if necessary
252 @param field_name: msg field name
253 @type field_name: str
254 @return: remapped name
255 @rtype: str
256 """
257
258 if field_name in keyword.kwlist + ['self']:
259 return field_name + "_"
260 return field_name
261
262
263
264
266 """
267 @param types: type names
268 @type types: [str]
269 @return: format string for struct if types are all simple. Otherwise, return None
270 @rtype: str
271 """
272 if not types:
273 return None
274 try:
275 return ''.join([SIMPLE_TYPES_DICT[t] for t in types])
276 except:
277 return None
278
279 -def compute_post_deserialize(type_, varname):
280 """
281 Compute post-deserialization code for type_, if necessary
282 @return: code to execute post-deserialization (unindented), or None if not necessary.
283 @rtype: str
284 """
285 s = get_special(type_)
286 if s is not None:
287 return s.get_post_deserialize(varname)
288
290 """
291 Compute python constructor expression for specified message type implementation
292 @param package str: package that type is being imported into. Used
293 to resolve type_ if package is not specified.
294 @type package: str
295 @param type_: message type
296 @type type_: str
297 """
298 if is_special(type_):
299 return get_special(type_).constructor
300 else:
301 base_pkg, base_type_ = compute_pkg_type(package, type_)
302 if not roslib.msgs.is_registered("%s/%s"%(base_pkg,base_type_)):
303 return None
304 else:
305 return '%s.msg.%s()'%(base_pkg, base_type_)
306
308 """
309 @param package: package that type is being imported into
310 @type package: str
311 @param type: message type (package resource name)
312 @type type: str
313 @return (str, str): python package and type name
314 """
315 splits = type_.split(roslib.msgs.SEP)
316 if len(splits) == 1:
317 return package, splits[0]
318 elif len(splits) == 2:
319 return tuple(splits)
320 else:
321 raise MsgGenerationException("illegal message type: %s"%type_)
322
324 """
325 Compute python import statement for specified message type implementation
326 @param package: package that type is being imported into
327 @type package: str
328 @param type_: message type (package resource name)
329 @type type_: str
330 @return: list of import statements (no newline) required to use type_ from package
331 @rtype: [str]
332 """
333
334 orig_base_type = roslib.msgs.base_msg_type(type_)
335
336
337
338 pkg, base_type = compute_pkg_type(package, orig_base_type)
339 type_str = "%s/%s"%(pkg, base_type)
340
341
342
343
344 if roslib.msgs.is_builtin(orig_base_type) or \
345 roslib.msgs.is_header_type(orig_base_type):
346
347
348
349 if is_special(base_type):
350 retval = [get_special(base_type).import_str]
351 else:
352 retval = []
353 elif not roslib.msgs.is_registered(type_str):
354 retval = []
355 else:
356 retval = ['import %s.msg'%pkg]
357 for t in get_registered_ex(type_str).types:
358 sub = compute_import(package, t)
359 retval.extend([x for x in sub if not x in retval])
360 return retval
361
363 """
364 Same as roslib.gentools.compute_full_text, except that the
365 resulting text is escaped to be safe for Python's triple-quote string
366 quoting
367
368 @param get_deps_dict: dictionary returned by get_dependencies call
369 @type get_deps_dict: dict
370 @return: concatenated text for msg/srv file and embedded msg/srv types. Text will be escaped for triple-quote
371 @rtype: str
372 """
373 msg_definition = roslib.gentools.compute_full_text(gen_deps_dict)
374 msg_definition.replace('"""', r'\"\"\"')
375 return msg_definition
376
378 """
379 Optimize the struct format pattern.
380 @param pattern: struct pattern
381 @type pattern: str
382 @return: optimized struct pattern
383 @rtype: str
384 """
385 if not pattern or len(pattern) == 1 or '%' in pattern:
386 return pattern
387 prev = pattern[0]
388 count = 1
389 new_pattern = ''
390 nums = [str(i) for i in range(0, 9)]
391 for c in pattern[1:]:
392 if c == prev and not c in nums:
393 count += 1
394 else:
395 if count > 1:
396 new_pattern = new_pattern + str(count) + prev
397 else:
398 new_pattern = new_pattern + prev
399 prev = c
400 count = 1
401 if count > 1:
402 new_pattern = new_pattern + str(count) + c
403 else:
404 new_pattern = new_pattern + prev
405 return new_pattern
406
407
408
410 return "buff.write(%s)"%expr
411
412
414 """
415 @param var: variable name
416 @type var: str
417 @return: struct packing code for an int32
418 """
419 return serialize('_struct_I.pack(%s)'%var)
420
421
423 """
424 @param var: variable name
425 @type var: str
426 @return: struct unpacking code for an int32
427 """
428 return '(%s,) = _struct_I.unpack(%s)'%(var, buff)
429
430
431 -def pack(pattern, vars):
432 """
433 create struct.pack call for when pattern is a string pattern
434 @param pattern: pattern for pack
435 @type pattern: str
436 @param vars: name of variables to pack
437 @type vars: str
438 """
439
440 pattern = reduce_pattern(pattern)
441 add_pattern(pattern)
442 return serialize("_struct_%s.pack(%s)"%(pattern, vars))
443 -def pack2(pattern, vars):
444 """
445 create struct.pack call for when pattern is the name of a variable
446 @param pattern: name of variable storing string pattern
447 @type pattern: struct
448 @param vars: name of variables to pack
449 @type vars: str
450 """
451 return serialize("struct.pack(%s, %s)"%(pattern, vars))
452 -def unpack(var, pattern, buff):
453 """
454 create struct.unpack call for when pattern is a string pattern
455 @param var: name of variable to unpack
456 @type var: str
457 @param pattern: pattern for pack
458 @type pattern: str
459 @param buff: buffer to unpack from
460 @type buff: str
461 """
462
463 pattern = reduce_pattern(pattern)
464 add_pattern(pattern)
465 return var + " = _struct_%s.unpack(%s)"%(pattern, buff)
467 """
468 Create struct.unpack call for when pattern refers to variable
469 @param var: variable the stores the result of unpack call
470 @type var: str
471 @param pattern: name of variable that unpack will read from
472 @type pattern: str
473 @param buff: buffer that the unpack reads from
474 @type buff: StringIO
475 """
476 return "%s = struct.unpack(%s, %s)"%(var, pattern, buff)
477
478
479
480
481
482
483
484 _NUMPY_DTYPE = {
485 'float32': 'numpy.float32',
486 'float64': 'numpy.float64',
487 'bool': 'numpy.bool',
488 'int8': 'numpy.int8',
489 'int16': 'numpy.int16',
490 'int32': 'numpy.int32',
491 'int64': 'numpy.int64',
492 'uint8': 'numpy.uint8',
493 'uint16': 'numpy.uint16',
494 'uint32': 'numpy.uint32',
495 'uint64': 'numpy.uint64',
496
497 'char' : 'numpy.uint8',
498 'byte' : 'numpy.int8',
499 }
500
502 """
503 create numpy deserialization code
504 """
505 return var + " = numpy.frombuffer(%s, dtype=%s, count=%s)"%(buff, dtype, count)
506
508 """
509 create numpy serialization code
510 @param vars: name of variables to pack
511 """
512 return serialize("%s.tostring()"%var)
513
514
515
516
517 _serial_context = ''
518 _context_stack = []
519
520 _counter = 0
526
527 -def push_context(context):
528 """
529 Push new variable context onto context stack. The context stack
530 manages field-reference context for serialization, e.g. 'self.foo'
531 vs. 'self.bar.foo' vs. 'var.foo'
532 """
533 global _serial_context, _context_stack
534 _context_stack.append(_serial_context)
535 _serial_context = context
536
538 """
539 Pop variable context from context stack. The context stack manages
540 field-reference context for serialization, e.g. 'self.foo'
541 vs. 'self.bar.foo' vs. 'var.foo'
542 """
543 global _serial_context
544 _serial_context = _context_stack.pop()
545
546 _context_patterns = []
548 """
549 Record struct pattern that's been used for (de)serialization
550 """
551 _context_patterns.append(p)
553 """
554 Clear record of struct pattern that have been used for (de)serialization
555 """
556 del _context_patterns[:]
558 """
559 @return: record of struct pattern that have been used for (de)serialization
560 """
561 return _context_patterns[:]
562
563
564
565
566
567
568
570 """
571 Generator for array-length serialization (32-bit, little-endian unsigned integer)
572 @param var: variable name
573 @type var: str
574 @param is_string: if True, variable is a string type
575 @type is_string: bool
576 @param serialize bool: if True, generate code for
577 serialization. Other, generate code for deserialization
578 @type serialize: bool
579 """
580 if serialize:
581 yield "length = len(%s)"%var
582
583
584
585
586
587 if not is_string:
588 yield int32_pack("length")
589 else:
590 yield "start = end"
591 yield "end += 4"
592 yield int32_unpack('length', 'str[start:end]')
593
595 """
596 Generator for string types. similar to arrays, but with more
597 efficient call to struct.pack.
598 @param name: spec field name
599 @type name: str
600 @param serialize: if True, generate code for
601 serialization. Other, generate code for deserialization
602 @type serialize: bool
603 """
604
605
606 if _serial_context and serialize:
607
608 yield "_x = %s%s"%(_serial_context, name)
609 var = "_x"
610 else:
611 var = _serial_context+name
612
613
614
615 base_type, is_array, array_len = roslib.msgs.parse_type(type_)
616
617 if base_type not in ['uint8', 'byte'] or array_len is None:
618 for y in len_serializer_generator(var, True, serialize):
619 yield y
620
621 if serialize:
622
623
624
625 base_type, is_array, array_len = roslib.msgs.parse_type(type_)
626 if base_type in ['uint8', 'byte']:
627 yield "# - if encoded as a list instead, serialize as bytes instead of string"
628 if array_len is None:
629 yield "if type(%s) in [list, tuple]:"%var
630 yield INDENT+pack2("'<I%sB'%length", "length, *%s"%var)
631 yield "else:"
632 yield INDENT+pack2("'<I%ss'%length", "length, %s"%var)
633 else:
634 yield "if type(%s) in [list, tuple]:"%var
635 yield INDENT+pack('%sB'%array_len, "*%s"%var)
636 yield "else:"
637 yield INDENT+pack('%ss'%array_len, var)
638 else:
639 yield pack2("'<I%ss'%length", "length, %s"%var)
640 else:
641 yield "start = end"
642 if array_len is not None:
643 yield "end += %s" % array_len
644 else:
645 yield "end += length"
646 yield "%s = str[start:end]" % var
647
649 """
650 Generator for array types
651 @raise MsgGenerationException: if array spec is invalid
652 """
653 base_type, is_array, array_len = roslib.msgs.parse_type(type_)
654 if not is_array:
655 raise MsgGenerationException("Invalid array spec: %s"%type_)
656 var_length = array_len is None
657
658
659
660 if base_type in ['byte', 'uint8']:
661 for y in string_serializer_generator(package, type_, name, serialize):
662 yield y
663 return
664
665 var = _serial_context+name
666 try:
667
668 if var_length:
669 for y in len_serializer_generator(var, False, serialize):
670 yield y
671 length = None
672 else:
673 length = array_len
674
675
676 if is_simple(base_type):
677 if var_length:
678 pattern = compute_struct_pattern([base_type])
679 yield "pattern = '<%%s%s'%%length"%pattern
680 if serialize:
681 if is_numpy:
682 yield pack_numpy(var)
683 else:
684 yield pack2('pattern', "*"+var)
685 else:
686 yield "start = end"
687 yield "end += struct.calcsize(pattern)"
688 if is_numpy:
689 dtype = _NUMPY_DTYPE[base_type]
690 yield unpack_numpy(var, 'length', dtype, 'str[start:end]')
691 else:
692 yield unpack2(var, 'pattern', 'str[start:end]')
693 else:
694 pattern = "%s%s"%(length, compute_struct_pattern([base_type]))
695 if serialize:
696 if is_numpy:
697 yield pack_numpy(var)
698 else:
699 yield pack(pattern, "*"+var)
700 else:
701 yield "start = end"
702 yield "end += %s"%struct.calcsize('<%s'%pattern)
703 if is_numpy:
704 dtype = _NUMPY_DTYPE[base_type]
705 yield unpack_numpy(var, length, dtype, 'str[start:end]')
706 else:
707 yield unpack(var, pattern, 'str[start:end]')
708 if not serialize and base_type == 'bool':
709
710 if base_type == 'bool':
711 yield "%s = map(bool, %s)"%(var, var)
712
713 else:
714
715
716
717
718 loop_var = 'val%s'%len(_context_stack)
719
720
721 if base_type == 'string':
722 push_context('')
723 factory = string_serializer_generator(package, base_type, loop_var, serialize)
724 else:
725 push_context('%s.'%loop_var)
726 factory = serializer_generator(package, get_registered_ex(base_type), serialize, is_numpy)
727
728 if serialize:
729 yield 'for %s in %s:'%(loop_var, var)
730 else:
731 yield '%s = []'%var
732 if var_length:
733 yield 'for i in xrange(0, length):'
734 else:
735 yield 'for i in xrange(0, %s):'%length
736 if base_type != 'string':
737 yield INDENT + '%s = %s'%(loop_var, compute_constructor(package, base_type))
738 for y in factory:
739 yield INDENT + y
740 if not serialize:
741 yield INDENT + '%s.append(%s)'%(var, loop_var)
742 pop_context()
743
744 except MsgGenerationException:
745 raise
746 except Exception, e:
747 raise MsgGenerationException(e)
748
750 """
751 Generator for serializing complex type
752 @param serialize: if True, generate serialization
753 code. Otherwise, deserialization code.
754 @type serialize: bool
755 @param is_numpy: if True, generate serializer code for numpy
756 datatypes instead of Python lists
757 @type is_numpy: bool
758 @raise MsgGenerationException: if type is not a valid
759 """
760
761
762
763
764
765 _, is_array, _ = roslib.msgs.parse_type(type_)
766
767
768 if is_array:
769 for y in array_serializer_generator(package, type_, name, serialize, is_numpy):
770 yield y
771
772 elif type_ == 'string':
773 for y in string_serializer_generator(package, type_, name, serialize):
774 yield y
775 else:
776 if not is_special(type_):
777
778 pkg, base_type = compute_pkg_type(package, type_)
779 type_ = "%s/%s"%(pkg, base_type)
780 if roslib.msgs.is_registered(type_):
781
782 ctx_var = next_var()
783 yield "%s = %s"%(ctx_var, _serial_context+name)
784 push_context(ctx_var+'.')
785
786
787 for y in serializer_generator(package, get_registered_ex(type_), serialize, is_numpy):
788 yield y
789 pop_context()
790 else:
791
792 raise MsgGenerationException("Unknown type: %s. Package context is %s"%(type_, package))
793
795 """
796 Generator (de)serialization code for multiple fields from spec
797 @param spec: MsgSpec
798 @type spec: MsgSpec
799 @param start: first field to serialize
800 @type start: int
801 @param end: last field to serialize
802 @type end: int
803 """
804
805 if end - start > 1 and _serial_context.endswith('.'):
806 yield '_x = '+_serial_context[:-1]
807 vars_ = '_x.' + (', _x.').join(spec.names[start:end])
808 else:
809 vars_ = _serial_context + (', '+_serial_context).join(spec.names[start:end])
810
811 pattern = compute_struct_pattern(spec.types[start:end])
812 if serialize:
813 yield pack(pattern, vars_)
814 else:
815 yield "start = end"
816 yield "end += %s"%struct.calcsize('<%s'%reduce_pattern(pattern))
817 yield unpack('(%s,)'%vars_, pattern, 'str[start:end]')
818
819
820
821
822 bool_vars = [(f, t) for f, t in zip(spec.names[start:end], spec.types[start:end]) if t == 'bool']
823 for f, t in bool_vars:
824
825 var = _serial_context+f
826 yield "%s = bool(%s)"%(var, var)
827
829 """
830 Python generator that yields un-indented python code for
831 (de)serializing MsgSpec. The code this yields is meant to be
832 included in a class method and cannot be used
833 standalone. serialize_fn_generator and deserialize_fn_generator
834 wrap method to provide appropriate class field initializations.
835 @param package: name of package the spec is being used in
836 @type package: str
837 @param serialize: if True, yield serialization
838 code. Otherwise, yield deserialization code.
839 @type serialize: bool
840 @param is_numpy: if True, generate serializer code for numpy datatypes instead of Python lists
841 @type is_numpy: bool
842 """
843
844
845
846 if spec is None:
847 raise MsgGenerationException("spec is none")
848 names, types = spec.names, spec.types
849 if serialize and not len(names):
850 yield "pass"
851 return
852
853
854
855
856 curr = 0
857 for (i, type_) in enumerate(types):
858 if not is_simple(type_):
859 if i != curr:
860 for y in simple_serializer_generator(spec, curr, i, serialize):
861 yield y
862 curr = i+1
863 for y in complex_serializer_generator(package, type_, names[i], serialize, is_numpy):
864 yield y
865 if curr < len(types):
866 for y in simple_serializer_generator(spec, curr, len(types), serialize):
867 yield y
868
870 """
871 generator for body of serialize() function
872 @param is_numpy: if True, generate serializer code for numpy
873 datatypes instead of Python lists
874 @type is_numpy: bool
875 """
876
877 yield "try:"
878 push_context('self.')
879
880 for y in serializer_generator(package, flatten(spec), True, is_numpy):
881 yield " "+y
882 pop_context()
883 yield "except struct.error, se: self._check_types(se)"
884 yield "except TypeError, te: self._check_types(te)"
885
886
888 """
889 generator for body of deserialize() function
890 @param is_numpy: if True, generate serializer code for numpy
891 datatypes instead of Python lists
892 @type is_numpy: bool
893 """
894 yield "try:"
895
896
897 for type_, name in spec.fields():
898 if roslib.msgs.is_registered(type_):
899 yield " if self.%s is None:"%name
900 yield " self.%s = %s"%(name, compute_constructor(package, type_))
901 yield " end = 0"
902
903
904 push_context('self.')
905
906 for y in serializer_generator(package, flatten(spec), False, is_numpy):
907 yield " "+y
908 pop_context()
909
910
911
912 for type_, name in spec.fields():
913 code = compute_post_deserialize(type_, "self.%s"%name)
914 if code:
915 yield " %s"%code
916
917 yield " return self"
918 yield "except struct.error, e:"
919 yield " raise roslib.message.DeserializationError(e) #most likely buffer underfill"
920
922 """
923 Python code generator for .msg files. Takes in a package name,
924 message name, and message specification and generates a Python
925 message class.
926
927 @param package: name of package for message
928 @type package: str
929 @param name: base type name of message, e.g. 'Empty', 'String'
930 @type name: str
931 @param spec: parsed .msg specification
932 @type spec: L{MsgSpec}
933 """
934
935
936
937
938
939
940 gendeps_dict = roslib.gentools.get_dependencies(spec, package, compute_files=False)
941 md5sum = roslib.gentools.compute_md5(gendeps_dict)
942
943
944 spec = make_python_safe(spec)
945 spec_names = spec.names
946
947
948
949 clear_patterns()
950
951 yield '"""autogenerated by genmsg_py from %s.msg. Do not edit."""'%name
952 yield 'import roslib.message\nimport struct\n'
953 import_strs = []
954 for t in spec.types:
955 import_strs.extend(compute_import(package, t))
956 import_strs = set(import_strs)
957 for i in import_strs:
958 if i:
959 yield i
960
961 yield ''
962
963 fulltype = '%s%s%s'%(package, roslib.msgs.SEP, name)
964
965
966 yield 'class %s(roslib.message.Message):'%name
967 yield ' _md5sum = "%s"'%(md5sum)
968 yield ' _type = "%s"'%(fulltype)
969 yield ' _has_header = %s #flag to mark the presence of a Header object'%spec.has_header()
970
971 yield ' _full_text = """%s\n"""'%compute_full_text_escaped(gendeps_dict)
972
973 if spec.constants:
974 yield ' # Pseudo-constants'
975 for c in spec.constants:
976 if c.type == 'string':
977 val = c.val
978 if '"' in val and "'" in val:
979
980 escaped = c.val.replace('\\', '\\\\')
981 escaped = escaped.replace('\"', '\\"')
982 yield ' %s = "%s"'%(c.name, escaped)
983 elif '"' in val:
984 yield " %s = r'%s'"%(c.name, val)
985 elif "'" in val:
986 yield ' %s = r"%s"'%(c.name, val)
987 else:
988 yield " %s = '%s'"%(c.name, val)
989 else:
990 yield ' %s = %s'%(c.name, c.val)
991 yield ''
992
993 if len(spec_names):
994 yield " __slots__ = ['"+"','".join(spec_names)+"']"
995 yield " _slot_types = ['"+"','".join(spec.types)+"']"
996 else:
997 yield " __slots__ = []"
998 yield " _slot_types = []"
999
1000 yield """
1001 def __init__(self, *args, **kwds):
1002 \"\"\"
1003 Constructor. Any message fields that are implicitly/explicitly
1004 set to None will be assigned a default value. The recommend
1005 use is keyword arguments as this is more robust to future message
1006 changes. You cannot mix in-order arguments and keyword arguments.
1007
1008 The available fields are:
1009 %s
1010
1011 @param args: complete set of field values, in .msg order
1012 @param kwds: use keyword arguments corresponding to message field names
1013 to set specific fields.
1014 \"\"\"
1015 if args or kwds:
1016 super(%s, self).__init__(*args, **kwds)"""%(','.join(spec_names), name)
1017
1018 if len(spec_names):
1019 yield " #message fields cannot be None, assign default values for those that are"
1020 for (t, s) in zip(spec.types, spec_names):
1021 yield " if self.%s is None:"%s
1022 yield " self.%s = %s"%(s, default_value(t, package))
1023 if len(spec_names) > 0:
1024 yield " else:"
1025 for (t, s) in zip(spec.types, spec_names):
1026 yield " self.%s = %s"%(s, default_value(t, package))
1027
1028 yield """
1029 def _get_types(self):
1030 \"\"\"
1031 internal API method
1032 \"\"\"
1033 return self._slot_types
1034
1035 def serialize(self, buff):
1036 \"\"\"
1037 serialize message into buffer
1038 @param buff: buffer
1039 @type buff: StringIO
1040 \"\"\""""
1041 for y in serialize_fn_generator(package, spec):
1042 yield " "+ y
1043 yield """
1044 def deserialize(self, str):
1045 \"\"\"
1046 unpack serialized message in str into this message instance
1047 @param str: byte array of serialized message
1048 @type str: str
1049 \"\"\""""
1050 for y in deserialize_fn_generator(package, spec):
1051 yield " " + y
1052 yield ""
1053
1054 yield """
1055 def serialize_numpy(self, buff, numpy):
1056 \"\"\"
1057 serialize message with numpy array types into buffer
1058 @param buff: buffer
1059 @type buff: StringIO
1060 @param numpy: numpy python module
1061 @type numpy module
1062 \"\"\""""
1063 for y in serialize_fn_generator(package, spec, is_numpy=True):
1064 yield " "+ y
1065 yield """
1066 def deserialize_numpy(self, str, numpy):
1067 \"\"\"
1068 unpack serialized message in str into this message instance using numpy for array types
1069 @param str: byte array of serialized message
1070 @type str: str
1071 @param numpy: numpy python module
1072 @type numpy: module
1073 \"\"\""""
1074 for y in deserialize_fn_generator(package, spec, is_numpy=True):
1075 yield " " + y
1076 yield ""
1077
1078
1079
1080
1081 yield '_struct_I = roslib.message.struct_I'
1082 patterns = get_patterns()
1083 for p in set(patterns):
1084
1085 if p == 'I':
1086 continue
1087 var_name = '_struct_%s'%(p.replace('<',''))
1088 yield '%s = struct.Struct("<%s")'%(var_name, p)
1089 clear_patterns()
1090
1091
1092
1093
1095 """
1096 @param dep_msg: text of dependent .msg definition
1097 @type dep_msg: str
1098 @return: type name, message spec
1099 @rtype: str, MsgSpec
1100 @raise MsgGenerationException: if dep_msg is improperly formatted
1101 """
1102 line1 = dep_msg.find('\n')
1103 msg_line = dep_msg[:line1]
1104 if not msg_line.startswith("MSG: "):
1105 raise MsgGenerationException("invalid input to generate_dynamic: dependent type is missing 'MSG:' type declaration header")
1106 dep_type = msg_line[5:].strip()
1107 dep_pkg, dep_base_type = roslib.names.package_resource_name(dep_type)
1108 dep_spec = roslib.msgs.load_from_string(dep_msg[line1+1:], dep_pkg)
1109 return dep_type, dep_spec
1110
1112 """
1113 Modify pkg/base_type name so that it can safely co-exist with
1114 statically generated files.
1115
1116 @return: name to use for pkg/base_type for dynamically generated message class.
1117 @rtype: str
1118 """
1119 return "_%s__%s"%(pkg, base_type)
1120
1122 """
1123 Modify the generated code to rewrite names such that the code can
1124 safely co-exist with messages of the same name.
1125
1126 @param py_text: genmsg_py-generated Python source code
1127 @type py_text: str
1128 @return: updated text
1129 @rtype: str
1130 """
1131 for t in types:
1132 pkg, base_type = roslib.names.package_resource_name(t)
1133 gen_name = _gen_dyn_name(pkg, base_type)
1134
1135
1136
1137 py_text = py_text.replace("import %s.msg"%pkg, '')
1138
1139 py_text = py_text.replace("%s.msg.%s"%(pkg, base_type), gen_name)
1140
1141 py_text = py_text.replace('class %s('%base_type, 'class %s('%gen_name)
1142
1143 py_text = py_text.replace('super(%s,'%base_type, 'super(%s,'%gen_name)
1144
1145 py_text = py_text.replace('std_msgs.msg._Header.Header', _gen_dyn_name('std_msgs', 'Header'))
1146 return py_text
1147
1149 """
1150 Dymamically generate message classes from msg_cat .msg text
1151 gendeps dump. This method modifies sys.path to include a temp file
1152 directory.
1153 @param core_type str: top-level ROS message type of concatenanted .msg text
1154 @param msg_cat str: concatenation of full message text (output of gendeps --cat)
1155 @raise MsgGenerationException: if dep_msg is improperly formatted
1156 """
1157 core_pkg, core_base_type = roslib.names.package_resource_name(core_type)
1158
1159
1160
1161
1162
1163 msg_cat = msg_cat.replace('roslib/Header', 'std_msgs/Header')
1164
1165
1166 splits = msg_cat.split('\n'+'='*80+'\n')
1167 core_msg = splits[0]
1168 deps_msgs = splits[1:]
1169
1170
1171 specs = { core_type: roslib.msgs.load_from_string(core_msg, core_pkg) }
1172
1173 for dep_msg in deps_msgs:
1174
1175 dep_type, dep_spec = _generate_dynamic_specs(specs, dep_msg)
1176 specs[dep_type] = dep_spec
1177
1178
1179
1180
1181 roslib.msgs.reinit()
1182 for t, spec in specs.iteritems():
1183 roslib.msgs.register(t, spec)
1184
1185
1186
1187 buff = StringIO()
1188 for t, spec in specs.iteritems():
1189 pkg, s_type = roslib.names.package_resource_name(t)
1190
1191 for l in msg_generator(pkg, s_type, spec):
1192 l = _gen_dyn_modify_references(l, specs.keys())
1193 buff.write(l + '\n')
1194 full_text = buff.getvalue()
1195
1196
1197 tmp_dir = tempfile.mkdtemp(prefix='genpy_')
1198
1199
1200 atexit.register(shutil.rmtree, tmp_dir)
1201
1202
1203 tmp_file = tempfile.NamedTemporaryFile(suffix=".py",dir=tmp_dir)
1204 tmp_file.file.write(full_text)
1205 tmp_file.file.close()
1206
1207
1208 sys.path.append(os.path.dirname(tmp_file.name))
1209
1210
1211 mod = __import__(os.path.basename(tmp_file.name)[:-3])
1212
1213
1214 messages = {}
1215 for t in specs.iterkeys():
1216 pkg, s_type = roslib.names.package_resource_name(t)
1217 try:
1218 messages[t] = getattr(mod, _gen_dyn_name(pkg, s_type))
1219 except AttributeError:
1220 raise MsgGenerationException("cannot retrieve message class for %s/%s"%(pkg, s_type))
1221
1222
1223 roslib.msgs.reinit()
1224
1225 return messages
1226