diff --git a/src/roswire/definitions/msg.py b/src/roswire/definitions/msg.py index 3bc571e2..cf2a6f15 100644 --- a/src/roswire/definitions/msg.py +++ b/src/roswire/definitions/msg.py @@ -5,6 +5,7 @@ from io import BytesIO import logging import functools +import hashlib import struct import re import os @@ -31,7 +32,7 @@ ConstantValue = Union[str, int, float] -@attr.s(frozen=True) +@attr.s(frozen=True, str=False) class Constant: typ = attr.ib(type=str) name = attr.ib(type=str) @@ -46,8 +47,11 @@ def to_dict(self) -> Dict[str, Any]: 'name': self.name, 'value': self.value} + def __str__(self) -> str: + return f"{self.typ} {self.name}={str(self.value)}" -@attr.s(frozen=True) + +@attr.s(frozen=True, str=False) class Field: typ: str = attr.ib() name: str = attr.ib() @@ -85,6 +89,13 @@ def to_dict(self) -> Dict[str, str]: return {'type': self.typ, 'name': self.name} + def without_package_name(self) -> 'Field': + typ = self.typ.partition('/')[2] if '/' in self.typ else self.typ + return Field(typ, self.name) + + def __str__(self) -> str: + return f"{self.typ} {self.name}" + @attr.s(frozen=True) class MsgFormat: @@ -209,6 +220,27 @@ def flatten(self, fmt = name_to_format[field.typ] yield from fmt.flatten(name_to_format, ctx + (field.name,)) + def md5text(self, name_to_msg: Mapping[str, 'MsgFormat']) -> str: + """Computes the MD5 text for this format.""" + lines: List[str] = [] + lines += [str(c) for c in self.constants] + for f in self.fields: + if is_builtin(f.base_type): + lines += [str(f.without_package_name())] + else: + f_md5 = name_to_msg[f.base_type].md5sum(name_to_msg) + lines += [f'{f_md5} {f.name}'] + return '\n'.join(lines) + + def md5sum(self, name_to_msg: Mapping[str, 'MsgFormat']) -> str: + """Computes the MD5 sum for this format.""" + logger.debug("generating md5sum: %s", self.fullname) + txt = self.md5text(name_to_msg) + logger.debug("generated md5 text [%s]:\n%s", self.fullname, txt) + md5sum = hashlib.md5(txt.encode('utf-8')).hexdigest() + logger.debug("generated md5sum [%s]: %s", self.fullname, md5sum) + return md5sum + class Message: """Base class used by all messages.""" @@ -241,6 +273,11 @@ def to_dict(self) -> Dict[str, Any]: d[name] = self._to_dict_value(val) return d + @classmethod + def md5sum(cls) -> str: + """Returns the md5sum for this message type.""" + raise NotImplementedError + @classmethod def read(cls, b: BinaryIO) -> 'Message': raise NotImplementedError diff --git a/src/roswire/definitions/type_db.py b/src/roswire/definitions/type_db.py index 1b5335b4..aa6af691 100644 --- a/src/roswire/definitions/type_db.py +++ b/src/roswire/definitions/type_db.py @@ -1,7 +1,7 @@ __all__ = ('TypeDatabase',) from typing import (Collection, Type, Mapping, Iterator, Dict, ClassVar, Any, - Sequence, Callable, BinaryIO) + Sequence, Callable, BinaryIO, List) from collections import OrderedDict import attr @@ -42,6 +42,8 @@ def build(cls, db_format: FormatDatabase) -> 'TypeDatabase': ns['format'] = fmt ns['read'] = classmethod(cls._build_read(name_to_type, fmt)) ns['write'] = cls._build_write(name_to_type, fmt) + md5 = fmt.md5sum(db_format.messages) + ns['md5sum'] = classmethod(lambda cls, md5=md5: md5) t: Type[Message] = type(fmt.name, (Message,), ns) t = attr.s(t, frozen=True, slots=True) name_to_type[fmt.fullname] = t diff --git a/test/test_type_database.py b/test/test_type_database.py index 362405a1..7d5c0df9 100644 --- a/test/test_type_database.py +++ b/test/test_type_database.py @@ -5,6 +5,7 @@ MsgFormat, Time) from test_file import build_file_proxy +from test_bag import load_mavros_type_db def test_build(): @@ -119,3 +120,23 @@ def test_to_and_from_dict(): 'frame_id': 'foo'} assert m.to_dict() == d assert db_type.from_dict(fmt, d) == m + + +def test_md5sum(): + db_type = load_mavros_type_db() + + def check(name: str, md5sum_expected: str) -> None: + assert db_type[name].md5sum() == md5sum_expected + + check('std_msgs/Header', '2176decaecbce78abc3b96ef049fabed') + check('std_msgs/Duration', '3e286caf4241d664e55f3ad380e2ae46') + check('mavros_msgs/CommandCode', 'f7e54ea3892a961cc44c9350fdb0855e') + check('geometry_msgs/Quaternion', 'a779879fadf0160734f906b8c19c7004') + check('geometry_msgs/Vector3', '4a842b65f413084dc2b10fb484ea7f17') + check('geometry_msgs/Wrench', '4f539cf138b23283b520fd271b567936') + check('geometry_msgs/Transform', 'ac9eff44abf714214112b05d54a3cf9b') + check('geometry_msgs/TransformStamped', 'b5764a33bfeb3588febc2682852579b0') + check('tf/tfMessage', '94810edda583a504dfda3829e70d7eec') + check('visualization_msgs/InteractiveMarkerPose', 'a6e6833209a196a38d798dadb02c81f8') + check('mavros_msgs/State', '9e3d873fae342c8f48a8bd64c53d991e') + check('mavros_msgs/Mavlink', '6dd71a38b8541fdc2de89a548c7dbc2f')