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 from __future__ import print_function
37
38 """
39 ROS msg library for Python
40
41 Implements: U{http://ros.org/wiki/msg}
42 """
43
44 try:
45 from cStringIO import StringIO
46 except ImportError:
47 from io import StringIO
48
49 import os
50 import itertools
51 import sys
52 import re
53 import string
54
55 import roslib_electric.exceptions
56 import roslib_electric.manifest
57 import roslib_electric.packages
58 import roslib_electric.names
59 import roslib_electric.resources
60 import roslib_electric.rospack
61
62 VERBOSE = False
63
64
67
68
72
73 EXT = roslib_electric.names.MSG_EXT
74 SEP = roslib_electric.names.PRN_SEPARATOR
75
76 CONSTCHAR = '='
77 COMMENTCHAR = '#'
78
80
81
83 """
84 Compute the base data type, e.g. for arrays, get the underlying array item type
85 @param type_: ROS msg type (e.g. 'std_msgs/String')
86 @type type_: str
87 @return: base type
88 @rtype: str
89 """
90 if type_ is None:
91 return None
92 if '[' in type_:
93 return type_[:type_.find('[')]
94 return type_
95
97 """
98 Resolve type name based on current package context.
99
100 NOTE: in ROS Diamondback, 'Header' resolves to
101 'std_msgs/Header'. In previous releases, it resolves to
102 'roslib/Header' (REP 100).
103
104 e.g.::
105 resolve_type('String', 'std_msgs') -> 'std_msgs/String'
106 resolve_type('String[]', 'std_msgs') -> 'std_msgs/String[]'
107 resolve_type('std_msgs/String', 'foo') -> 'std_msgs/String'
108 resolve_type('uint16', 'std_msgs') -> 'uint16'
109 resolve_type('uint16[]', 'std_msgs') -> 'uint16[]'
110 """
111 bt = base_msg_type(type_)
112 if bt in BUILTIN_TYPES:
113 return type_
114 elif bt == 'Header':
115 return 'std_msgs/Header'
116 elif SEP in type_:
117 return type_
118 else:
119 return "%s%s%s"%(package_context, SEP, type_)
120
121
122
124 """
125 Parse ROS message field type
126 @param type_: ROS field type
127 @type type_: str
128 @return: base_type, is_array, array_length
129 @rtype: str, bool, int
130 @raise MsgSpecException: if type_ cannot be parsed
131 """
132 if not type_:
133 raise MsgSpecException("Invalid empty type")
134 if '[' in type_:
135 var_length = type_.endswith('[]')
136 splits = type_.split('[')
137 if len(splits) > 2:
138 raise MsgSpecException("Currently only support 1-dimensional array types: %s"%type_)
139 if var_length:
140 return type_[:-2], True, None
141 else:
142 try:
143 length = int(splits[1][:-1])
144 return splits[0], True, length
145 except ValueError:
146 raise MsgSpecException("Invalid array dimension: [%s]"%splits[1][:-1])
147 else:
148 return type_, False, None
149
150
151
152
154 """
155 @return: True if the name is a syntatically legal message type name
156 @rtype: bool
157 """
158 if not x or len(x) != len(x.strip()):
159 return False
160 base = base_msg_type(x)
161 if not roslib_electric.names.is_legal_resource_name(base):
162 return False
163
164 x = x[len(base):]
165 state = 0
166 i = 0
167 for c in x:
168 if state == 0:
169 if c != '[':
170 return False
171 state = 1
172 elif state == 1:
173 if c == ']':
174 state = 0
175 else:
176 try:
177 string.atoi(c)
178 except:
179 return False
180 return state == 0
181
183 """
184 @return: True if the name is a legal constant type. Only simple types are allowed.
185 @rtype: bool
186 """
187 return x in PRIMITIVE_TYPES
188
195
196
197
199 """
200 Container class for holding a Constant declaration
201 """
202 __slots__ = ['type', 'name', 'val', 'val_text']
203
204 - def __init__(self, type_, name, val, val_text):
205 """
206 @param type_: constant type
207 @type type_: str
208 @param name: constant name
209 @type name: str
210 @param val: constant value
211 @type val: str
212 @param val_text: Original text definition of \a val
213 @type val_text: str
214 """
215 if type is None or name is None or val is None or val_text is None:
216 raise ValueError('Constant must have non-None parameters')
217 self.type = type_
218 self.name = name.strip()
219 self.val = val
220 self.val_text = val_text
221
226
229
232
234 """
235 Convert spec into a string representation. Helper routine for MsgSpec.
236 @param indent: internal use only
237 @type indent: str
238 @param buff: internal use only
239 @type buff: StringIO
240 @return: string representation of spec
241 @rtype: str
242 """
243 if buff is None:
244 buff = StringIO()
245 for c in spec.constants:
246 buff.write("%s%s %s=%s\n"%(indent, c.type, c.name, c.val_text))
247 for type_, name in zip(spec.types, spec.names):
248 buff.write("%s%s %s\n"%(indent, type_, name))
249 base_type = base_msg_type(type_)
250 if not base_type in BUILTIN_TYPES:
251 subspec = get_registered(base_type)
252 _strify_spec(subspec, buff, indent + ' ')
253 return buff.getvalue()
254
256 """
257 Container class for storing information about a single field in a MsgSpec
258
259 Contains:
260 name
261 type
262 base_type
263 is_array
264 array_len
265 is_builtin
266 is_header
267 """
268
275
277 return "[%s, %s, %s, %s, %s]"%(self.name, self.type, self.base_type, self.is_array, self.array_len)
278
280 """
281 Container class for storing loaded msg description files. Field
282 types and names are stored in separate lists with 1-to-1
283 correspondence. MsgSpec can also return an md5 of the source text.
284 """
285
286 - def __init__(self, types, names, constants, text, full_name = '', short_name = '', package = ''):
287 """
288 @param types: list of field types, in order of declaration
289 @type types: [str]
290 @param names: list of field names, in order of declaration
291 @type names: [str]
292 @param constants: Constant declarations
293 @type constants: [L{Constant}]
294 @param text: text of declaration
295 @type text: str
296 @raise MsgSpecException: if spec is invalid (e.g. fields with the same name)
297 """
298 self.types = types
299 if len(set(names)) != len(names):
300 raise MsgSpecException("Duplicate field names in message: %s"%names)
301 self.names = names
302 self.constants = constants
303 assert len(self.types) == len(self.names), "len(%s) != len(%s)"%(self.types, self.names)
304
305 if (len(self.types)):
306 self.header_present = self.types[0] == HEADER and self.names[0] == 'header'
307 else:
308 self.header_present = False
309 self.text = text
310 self.full_name = full_name
311 self.short_name = short_name
312 self.package = package
313 self._parsed_fields = [Field(name, type) for (name, type) in zip(self.names, self.types)]
314
316 """
317 @return: zip list of types and names (e.g. [('int32', 'x'), ('int32', 'y')]
318 @rtype: [(str,str),]
319 """
320 return list(zip(self.types, self.names))
321
323 """
324 @return: list of Field classes
325 @rtype: [Field,]
326 """
327 return self._parsed_fields
328
330 """
331 @return: True if msg decription contains a 'Header header'
332 declaration at the beginning
333 @rtype: bool
334 """
335 return self.header_present
337 if not other or not isinstance(other, MsgSpec):
338 return False
339 return self.types == other.types and self.names == other.names and \
340 self.constants == other.constants and self.text == other.text
342 if not other or not isinstance(other, MsgSpec):
343 return True
344 return not self.__eq__(other)
345
347 if self.constants:
348 return "MsgSpec[%s, %s, %s]"%(repr(self.constants), repr(self.types), repr(self.names))
349 else:
350 return "MsgSpec[%s, %s]"%(repr(self.types), repr(self.names))
351
353 return _strify_spec(self)
354
355
356
368
369 _initialized = False
371
372 global _initialized
373 if _initialized:
374 return
375
376 fname = '%s%s'%(HEADER, EXT)
377 std_msgs_dir = roslib_electric.packages.get_pkg_dir('std_msgs')
378 if std_msgs_dir is None:
379 raise MsgSpecException("Unable to locate roslib: %s files cannot be loaded"%EXT)
380
381 header = os.path.join(std_msgs_dir, roslib_electric.packages.MSG_DIR, fname)
382 if not os.path.isfile(header):
383 sys.stderr.write("ERROR: cannot locate %s. Expected to find it at '%s'\n"%(fname, header))
384 return False
385
386
387 _, spec = load_from_file(header, '')
388 register(HEADER, spec)
389 register('std_msgs/'+HEADER, spec)
390
391 register('roslib/'+HEADER, spec)
392 for k, spec in EXTENDED_BUILTINS.items():
393 register(k, spec)
394
395 _initialized = True
396
397
398
400 """
401 Predicate for filtering directory list. matches message files
402 @param f: filename
403 @type f: str
404 """
405 return os.path.isfile(f) and f.endswith(EXT)
406
407
417
419 """
420 Determine the file system path for the specified .msg
421 resource. .msg resource does not have to exist.
422
423 @param package: name of package .msg file is in
424 @type package: str
425 @param type_: type name of message, e.g. 'Point2DFloat32'
426 @type type_: str
427 @return: file path of .msg file in specified package
428 @rtype: str
429 """
430 return roslib_electric.packages.resource_file(package, roslib_electric.packages.MSG_DIR, type_+EXT)
431
433 """
434 List all messages that a package contains.
435
436 @param package: package to load messages from
437 @type package: str
438 @return: list of message type names and specs for package, as well as a list
439 of message names that could not be processed.
440 @rtype: [(str, L{MsgSpec}), [str]]
441 """
442 _init()
443 types = list_msg_types(package, False)
444 specs = []
445 failures = []
446 for t in types:
447 try:
448 typespec = load_from_file(msg_file(package, t), package)
449 specs.append(typespec)
450 except Exception as e:
451 failures.append(t)
452 print("ERROR: unable to load %s"%t)
453 return specs, failures
454
491
522
524 """
525 Convert constant value declaration to python value. Does not do
526 type-checking, so ValueError or other exceptions may be raised.
527
528 @param type_: ROS field type
529 @type type_: str
530 @param val: string representation of constant
531 @type val: str:
532 @raise ValueError: if unable to convert to python representation
533 @raise MsgSpecException: if value exceeds specified integer width
534 """
535 if type_ in ['float32','float64']:
536 return float(val)
537 elif type_ in ['string']:
538 return val.strip()
539 elif type_ in ['int8', 'uint8', 'int16','uint16','int32','uint32','int64','uint64', 'char', 'byte']:
540
541 bits = [('int8', 8), ('uint8', 8), ('int16', 16),('uint16', 16),\
542 ('int32', 32),('uint32', 32), ('int64', 64),('uint64', 64),\
543 ('byte', 8), ('char', 8)]
544 b = [b for t, b in bits if t == type_][0]
545 import math
546 if type_[0] == 'u' or type_ == 'char':
547 lower = 0
548 upper = int(math.pow(2, b)-1)
549 else:
550 upper = int(math.pow(2, b-1)-1)
551 lower = -upper - 1
552 val = int(val)
553 if val > upper or val < lower:
554 raise MsgSpecException("cannot coerce [%s] to %s (out of bounds)"%(val, type_))
555 return val
556 elif type_ == 'bool':
557
558 return True if eval(val) else False
559 raise MsgSpecException("invalid constant type: [%s]"%type_)
560
562 """
563 Load message specification for specified type
564
565 @param package_context: package name to use for the type name or
566 '' to use the local (relative) naming convention.
567 @type package_context: str
568 @return: Message type name and message specification
569 @rtype: (str, L{MsgSpec})
570 """
571 pkg, basetype = roslib_electric.names.package_resource_name(msgtype)
572 pkg = pkg or package_context
573 try:
574 m_f = msg_file(pkg, basetype)
575 except roslib_electric.packages.InvalidROSPkgException:
576 raise MsgSpecException("Cannot locate message type [%s], package [%s] does not exist"%(msgtype, pkg))
577 return load_from_file(m_f, pkg)
578
580 """
581 Load message specification from a string.
582 @param text: .msg text
583 @type text: str
584 @param package_context: package name to use for the type name or
585 '' to use the local (relative) naming convention.
586 @type package_context: str
587 @return: Message specification
588 @rtype: L{MsgSpec}
589 @raise MsgSpecException: if syntax errors or other problems are detected in file
590 """
591 types = []
592 names = []
593 constants = []
594 for orig_line in text.split('\n'):
595 l = orig_line.split(COMMENTCHAR)[0].strip()
596 if not l:
597 continue
598 splits = [s for s in [x.strip() for x in l.split(" ")] if s]
599 type_ = splits[0]
600 if not is_valid_msg_type(type_):
601 raise MsgSpecException("%s is not a legal message type"%type_)
602 if CONSTCHAR in l:
603 if not is_valid_constant_type(type_):
604 raise MsgSpecException("%s is not a legal constant type"%type_)
605 if type_ == 'string':
606
607 idx = orig_line.find(CONSTCHAR)
608 name = orig_line[orig_line.find(' ')+1:idx]
609 val = orig_line[idx+1:]
610 else:
611 splits = [x.strip() for x in ' '.join(splits[1:]).split(CONSTCHAR)]
612 if len(splits) != 2:
613 raise MsgSpecException("Invalid declaration: %s"%l)
614 name = splits[0]
615 val = splits[1]
616 try:
617 val_converted = _convert_val(type_, val)
618 except Exception as e:
619 raise MsgSpecException("Invalid declaration: %s"%e)
620 constants.append(Constant(type_, name, val_converted, val.strip()))
621 else:
622 if len(splits) != 2:
623 raise MsgSpecException("Invalid declaration: %s"%l)
624 name = splits[1]
625 if not is_valid_msg_field_name(name):
626 raise MsgSpecException("%s is not a legal message field name"%name)
627 if package_context and not SEP in type_:
628 if not base_msg_type(type_) in RESERVED_TYPES:
629
630 type_ = "%s/%s"%(package_context, type_)
631 types.append(type_)
632 names.append(name)
633 return MsgSpec(types, names, constants, text, full_name, short_name, package_context)
634
636 """
637 Convert the .msg representation in the file to a MsgSpec instance.
638 This does *not* register the object.
639 @param file_path: path of file to load from
640 @type file_path: str:
641 @param package_context: package name to prepend to type name or
642 '' to use local (relative) naming convention.
643 @type package_context: str
644 @return: Message type name and message specification
645 @rtype: (str, L{MsgSpec})
646 @raise MsgSpecException: if syntax errors or other problems are detected in file
647 """
648 if VERBOSE:
649 if package_context:
650 print("Load spec from", file_path, "into package [%s]"%package_context)
651 else:
652 print("Load spec from", file_path)
653
654 file_name = os.path.basename(file_path)
655 type_ = file_name[:-len(EXT)]
656 base_type_ = type_
657
658 if package_context:
659 while package_context.endswith(SEP):
660 package_context = package_context[:-1]
661 type_ = "%s%s%s"%(package_context, SEP, type_)
662 if not roslib_electric.names.is_legal_resource_name(type_):
663 raise MsgSpecException("%s: [%s] is not a legal type name"%(file_path, type_))
664
665 f = open(file_path, 'r')
666 try:
667 try:
668 text = f.read()
669 return (type_, load_from_string(text, package_context, type_, base_type_))
670 except MsgSpecException as e:
671 raise MsgSpecException('%s: %s'%(file_name, e))
672 finally:
673 f.close()
674
675
676
677
678 HEADER = 'Header'
679 TIME = 'time'
680 DURATION = 'duration'
681
683 """
684 @param type_: message type name
685 @type type_: str
686 @return: True if \a type_ refers to the ROS Header type
687 @rtype: bool
688 """
689
690 return type_ in [HEADER, 'std_msgs/Header', 'roslib/Header']
691
692
693
694
695
696
697
698 TIME_MSG = "uint32 secs\nuint32 nsecs"
699
700 DURATION_MSG = "int32 secs\nint32 nsecs"
701
702
703 PRIMITIVE_TYPES = ['int8','uint8','int16','uint16','int32','uint32','int64','uint64','float32','float64',
704 'string',
705 'bool',
706
707 'char','byte']
708 BUILTIN_TYPES = PRIMITIVE_TYPES + [TIME, DURATION]
709
711 """
712 @param msg_type_name: name of message type
713 @type msg_type_name: str
714 @return: True if msg_type_name is a builtin/primitive type
715 @rtype: bool
716 """
717 return msg_type_name in BUILTIN_TYPES
718
719
720 EXTENDED_BUILTINS = { TIME : load_from_string(TIME_MSG), DURATION: load_from_string(DURATION_MSG) }
721
722 RESERVED_TYPES = BUILTIN_TYPES + [HEADER]
723
724 REGISTERED_TYPES = { }
725 _loaded_packages = []
726
728 """
729 @param msg_type_name: name of message type
730 @type msg_type_name: str
731 @return: True if msg spec for specified msg type name is
732 registered. NOTE: builtin types are not registered.
733 @rtype: bool
734 """
735 return msg_type_name in REGISTERED_TYPES
736
752
754 """
755 Load MsgSpec into the type dictionary
756
757 @param msg_type_name: name of message type
758 @type msg_type_name: str
759 @param msg_spec: spec to load
760 @type msg_spec: L{MsgSpec}
761 """
762 if VERBOSE:
763 print("Register msg %s"%msg_type_name)
764 REGISTERED_TYPES[msg_type_name] = msg_spec
765