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