From 66f63bf7ad7412f079e8ee5661ecae54f1ae7ec1 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 12:16:39 -0400 Subject: [PATCH 01/12] test CI --- mqtt_ros_bridge/encodings.py | 64 ++++++++++++++++++++++++++++++++++++ package.xml | 1 + test/test_encodings.py | 23 +++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 mqtt_ros_bridge/encodings.py create mode 100644 test/test_encodings.py diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py new file mode 100644 index 0000000..709f21e --- /dev/null +++ b/mqtt_ros_bridge/encodings.py @@ -0,0 +1,64 @@ +from typing import Protocol, Type, TypeVar, NoReturn + +import json +from rclpy.type_support import check_is_valid_msg_type + + +NestedDictionary = dict[str, 'NestedDictionary'] | dict[str, str] | dict[str, NoReturn] + + +class MsgLike(Protocol): + """Generic Message Type Alias.""" + + @classmethod + def get_fields_and_field_types(cls) -> dict[str, str]: + ... + + +MsgLikeT = TypeVar("MsgLikeT", bound=MsgLike) + + +RESERVED_FIELD_TYPE = '_msgs/' + + +def human_readable_encoding(msg: MsgLike) -> bytes: + check_is_valid_msg_type(type(msg)) + return json.dumps(human_readable_encoding_recursive(msg)).encode() + + +def human_readable_encoding_recursive(msg: MsgLike) -> NestedDictionary: + msg_dict: NestedDictionary = {} + for field, field_types in msg.get_fields_and_field_types().items(): + value = getattr(msg, field) + + if isinstance(value, bytes): + value = value.decode() + elif RESERVED_FIELD_TYPE in field_types: + value = human_readable_encoding_recursive(value) + msg_dict[field] = value + + return msg_dict + + +def human_readable_decoding(byte_msg: bytes, msg_type: Type[MsgLikeT]) -> MsgLikeT: + check_is_valid_msg_type(msg_type) + + str_msg = byte_msg.decode() + + msg_dict = json.loads(str_msg) + + return human_readable_decoding_recursive(msg_dict, msg_type) + + +def human_readable_decoding_recursive(msg_dict: NestedDictionary, + msg_type: Type[MsgLikeT]) -> MsgLikeT: + msg = msg_type() + + for field, value in msg_dict.items(): + if isinstance(getattr(msg, field), bytes): + value = value.encode() + elif isinstance(value, dict): + value = human_readable_decoding_recursive(value, type(getattr(msg, field))) + + setattr(msg, field, value) + return msg diff --git a/package.xml b/package.xml index 8a7b0d1..1186ca0 100644 --- a/package.xml +++ b/package.xml @@ -15,6 +15,7 @@ ament_flake8 ament_pep257 ament_mypy + test_msgs python3-pytest diff --git a/test/test_encodings.py b/test/test_encodings.py new file mode 100644 index 0000000..3f12013 --- /dev/null +++ b/test/test_encodings.py @@ -0,0 +1,23 @@ +from test_msgs.msg import (Arrays, BasicTypes, BoundedPlainSequences, BoundedSequences, Builtins, + Constants, Defaults, Empty, MultiNested, Strings, UnboundedSequences, + WStrings) +from mqtt_ros_bridge.encodings import human_readable_encoding, human_readable_decoding, MsgLikeT + + +def encodings_helper(msg: MsgLikeT) -> MsgLikeT: + assert human_readable_decoding(human_readable_encoding(msg), type(MsgLikeT)) == msg + + +def test_encodings() -> None: + encodings_helper(Arrays()) + encodings_helper(BasicTypes()) + encodings_helper(BoundedPlainSequences()) + encodings_helper(BoundedSequences()) + encodings_helper(Builtins()) + encodings_helper(Constants()) + encodings_helper(Defaults()) + encodings_helper(Empty()) + encodings_helper(MultiNested()) + encodings_helper(Strings()) + encodings_helper(UnboundedSequences()) + encodings_helper(WStrings()) From 13ceb73a9afa7d3d13d2502573b5dfa36844b1cb Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 12:20:09 -0400 Subject: [PATCH 02/12] clear up .items() --- mqtt_ros_bridge/encodings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index 709f21e..6d74c17 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -28,7 +28,7 @@ def human_readable_encoding(msg: MsgLike) -> bytes: def human_readable_encoding_recursive(msg: MsgLike) -> NestedDictionary: msg_dict: NestedDictionary = {} - for field, field_types in msg.get_fields_and_field_types().items(): + for field, field_types in (msg.get_fields_and_field_types()).items(): value = getattr(msg, field) if isinstance(value, bytes): @@ -56,7 +56,8 @@ def human_readable_decoding_recursive(msg_dict: NestedDictionary, for field, value in msg_dict.items(): if isinstance(getattr(msg, field), bytes): - value = value.encode() + if isinstance(value, str): + value = value.encode() elif isinstance(value, dict): value = human_readable_decoding_recursive(value, type(getattr(msg, field))) From 7de7a582e4b13261095748b39e6ea6398920824a Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 12:21:27 -0400 Subject: [PATCH 03/12] fix test_encodings --- test/test_encodings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_encodings.py b/test/test_encodings.py index 3f12013..95ad12d 100644 --- a/test/test_encodings.py +++ b/test/test_encodings.py @@ -5,7 +5,7 @@ def encodings_helper(msg: MsgLikeT) -> MsgLikeT: - assert human_readable_decoding(human_readable_encoding(msg), type(MsgLikeT)) == msg + assert human_readable_decoding(human_readable_encoding(msg), type(msg)) == msg def test_encodings() -> None: From e6008aa566d30fdeb2a0a9dd4f94f08cf08acbfd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 12:28:27 -0400 Subject: [PATCH 04/12] clean up --- mqtt_ros_bridge/encodings.py | 4 ++-- test/test_encodings.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index 6d74c17..d80e286 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -1,10 +1,10 @@ -from typing import Protocol, Type, TypeVar, NoReturn +from typing import Protocol, Type, TypeVar, NoReturn, TypeAlias import json from rclpy.type_support import check_is_valid_msg_type -NestedDictionary = dict[str, 'NestedDictionary'] | dict[str, str] | dict[str, NoReturn] +NestedDictionary: TypeAlias = dict[str, 'NestedDictionary'] | dict[str, str] | dict[str, NoReturn] class MsgLike(Protocol): diff --git a/test/test_encodings.py b/test/test_encodings.py index 95ad12d..89f7510 100644 --- a/test/test_encodings.py +++ b/test/test_encodings.py @@ -5,7 +5,9 @@ def encodings_helper(msg: MsgLikeT) -> MsgLikeT: - assert human_readable_decoding(human_readable_encoding(msg), type(msg)) == msg + encoded_and_decoded_msg = human_readable_decoding(human_readable_encoding(msg), type(msg)) + assert encoded_and_decoded_msg == msg + return encoded_and_decoded_msg def test_encodings() -> None: From 5f905b27e47326a9d7ec96967f1738c3bbc92ff6 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 12:31:16 -0400 Subject: [PATCH 05/12] mypy --- mqtt_ros_bridge/encodings.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index d80e286..8d63b62 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -1,10 +1,10 @@ -from typing import Protocol, Type, TypeVar, NoReturn, TypeAlias +from typing import Protocol, Type, TypeVar, TypeAlias import json from rclpy.type_support import check_is_valid_msg_type -NestedDictionary: TypeAlias = dict[str, 'NestedDictionary'] | dict[str, str] | dict[str, NoReturn] +NestedDictionary: TypeAlias = dict[str, 'NestedDictionary'] | dict[str, str] class MsgLike(Protocol): @@ -27,7 +27,7 @@ def human_readable_encoding(msg: MsgLike) -> bytes: def human_readable_encoding_recursive(msg: MsgLike) -> NestedDictionary: - msg_dict: NestedDictionary = {} + msg_dict = {} for field, field_types in (msg.get_fields_and_field_types()).items(): value = getattr(msg, field) @@ -53,13 +53,15 @@ def human_readable_decoding(byte_msg: bytes, msg_type: Type[MsgLikeT]) -> MsgLik def human_readable_decoding_recursive(msg_dict: NestedDictionary, msg_type: Type[MsgLikeT]) -> MsgLikeT: msg = msg_type() - + set_value: object for field, value in msg_dict.items(): if isinstance(getattr(msg, field), bytes): if isinstance(value, str): - value = value.encode() + set_value = value.encode() elif isinstance(value, dict): - value = human_readable_decoding_recursive(value, type(getattr(msg, field))) + set_value = human_readable_decoding_recursive(value, type(getattr(msg, field))) + else: + set_value = value - setattr(msg, field, value) + setattr(msg, field, set_value) return msg From f2687e7876169c2254e9fbe27460c71adf38df9c Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 13:59:28 -0400 Subject: [PATCH 06/12] Close Signed-off-by: Michael Carlstrom --- mqtt_ros_bridge/encodings.py | 45 ++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index 8d63b62..1e1da2c 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -1,10 +1,13 @@ -from typing import Protocol, Type, TypeVar, TypeAlias +from typing import Protocol, Type, TypeVar, TypeAlias, cast, Iterable + +from numpy import ndarray, array +from numpy.typing import NDArray import json from rclpy.type_support import check_is_valid_msg_type -NestedDictionary: TypeAlias = dict[str, 'NestedDictionary'] | dict[str, str] +NestedDictionary: TypeAlias = dict[str, 'NestedDictionary'] | dict[str, object] class MsgLike(Protocol): @@ -19,22 +22,42 @@ def get_fields_and_field_types(cls) -> dict[str, str]: RESERVED_FIELD_TYPE = '_msgs/' +ENCODING = 'latin-1' + +def numpy_encoding(array: NDArray) -> list: + return [int(x) for x in array] +def numpy_decoding(ls: list) -> NDArray: + return array(ls) def human_readable_encoding(msg: MsgLike) -> bytes: check_is_valid_msg_type(type(msg)) - return json.dumps(human_readable_encoding_recursive(msg)).encode() + + msg_dict = human_readable_encoding_recursive(msg) + return json.dumps(msg_dict).encode() def human_readable_encoding_recursive(msg: MsgLike) -> NestedDictionary: msg_dict = {} - for field, field_types in (msg.get_fields_and_field_types()).items(): + + msg_fields_and_field_types = type(msg).get_fields_and_field_types() + for field, field_types in msg_fields_and_field_types.items(): value = getattr(msg, field) if isinstance(value, bytes): - value = value.decode() + value = value.decode(ENCODING) + elif isinstance(value, list) and len(value) > 0: + if isinstance(value[0], bytes): + value = cast(list[bytes], value) + value = [byte.decode(ENCODING) for byte in value] + elif isinstance(value, ndarray): + value = numpy_encoding(value) elif RESERVED_FIELD_TYPE in field_types: - value = human_readable_encoding_recursive(value) + if isinstance(value, list): + print("hi") + value = [human_readable_encoding_recursive(msg_in_list) for msg_in_list in value] + else: + value = human_readable_encoding_recursive(value) msg_dict[field] = value return msg_dict @@ -57,7 +80,7 @@ def human_readable_decoding_recursive(msg_dict: NestedDictionary, for field, value in msg_dict.items(): if isinstance(getattr(msg, field), bytes): if isinstance(value, str): - set_value = value.encode() + set_value = value.encode(ENCODING) elif isinstance(value, dict): set_value = human_readable_decoding_recursive(value, type(getattr(msg, field))) else: @@ -65,3 +88,11 @@ def human_readable_decoding_recursive(msg_dict: NestedDictionary, setattr(msg, field, set_value) return msg + +from test_msgs.msg import Arrays + +# print(Arrays()) + + + +print(human_readable_decoding(human_readable_encoding(Arrays()), Arrays)) From 3e30aa5edf55ff605fc8face470217154bd9a0b3 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 19:57:37 -0400 Subject: [PATCH 07/12] Fix encoding Signed-off-by: Michael Carlstrom --- mqtt_ros_bridge/encodings.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index 1e1da2c..ca0f3a2 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -24,12 +24,15 @@ def get_fields_and_field_types(cls) -> dict[str, str]: RESERVED_FIELD_TYPE = '_msgs/' ENCODING = 'latin-1' + def numpy_encoding(array: NDArray) -> list: return [int(x) for x in array] + def numpy_decoding(ls: list) -> NDArray: return array(ls) + def human_readable_encoding(msg: MsgLike) -> bytes: check_is_valid_msg_type(type(msg)) @@ -50,14 +53,12 @@ def human_readable_encoding_recursive(msg: MsgLike) -> NestedDictionary: if isinstance(value[0], bytes): value = cast(list[bytes], value) value = [byte.decode(ENCODING) for byte in value] + elif RESERVED_FIELD_TYPE in field_types: + value = [human_readable_encoding_recursive(msg_in_list) for msg_in_list in value] elif isinstance(value, ndarray): value = numpy_encoding(value) elif RESERVED_FIELD_TYPE in field_types: - if isinstance(value, list): - print("hi") - value = [human_readable_encoding_recursive(msg_in_list) for msg_in_list in value] - else: - value = human_readable_encoding_recursive(value) + value = human_readable_encoding_recursive(value) msg_dict[field] = value return msg_dict @@ -67,9 +68,7 @@ def human_readable_decoding(byte_msg: bytes, msg_type: Type[MsgLikeT]) -> MsgLik check_is_valid_msg_type(msg_type) str_msg = byte_msg.decode() - msg_dict = json.loads(str_msg) - return human_readable_decoding_recursive(msg_dict, msg_type) @@ -93,6 +92,4 @@ def human_readable_decoding_recursive(msg_dict: NestedDictionary, # print(Arrays()) - - print(human_readable_decoding(human_readable_encoding(Arrays()), Arrays)) From 1b049b88f370381cb19516bf32c0561964ed21bb Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 21:41:36 -0400 Subject: [PATCH 08/12] Mypy fixes Signed-off-by: Michael Carlstrom --- mqtt_ros_bridge/encodings.py | 52 ++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index ca0f3a2..e70e2d7 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -1,12 +1,12 @@ -from typing import Protocol, Type, TypeVar, TypeAlias, cast, Iterable +import json +from array import array +from typing import (Any, Iterable, MutableSequence, Protocol, Type, TypeAlias, + TypeVar, cast) -from numpy import ndarray, array +from numpy import ndarray from numpy.typing import NDArray - -import json from rclpy.type_support import check_is_valid_msg_type - NestedDictionary: TypeAlias = dict[str, 'NestedDictionary'] | dict[str, object] @@ -19,18 +19,22 @@ def get_fields_and_field_types(cls) -> dict[str, str]: MsgLikeT = TypeVar("MsgLikeT", bound=MsgLike) +ArrayElementT = TypeVar('ArrayElementT', int, float, str) -RESERVED_FIELD_TYPE = '_msgs/' +RESERVED_FIELD_TYPE = '/' ENCODING = 'latin-1' -def numpy_encoding(array: NDArray) -> list: - return [int(x) for x in array] +def numpy_encoding(array_arg: NDArray[Any]) -> list[int]: + return [int(x) for x in array_arg] -def numpy_decoding(ls: list) -> NDArray: - return array(ls) +def array_encoding(array_arg: MutableSequence[ArrayElementT]) -> list[ArrayElementT]: + if len(array_arg) == 0: + return [] + element_type = type(array_arg[0]) + return [element_type(x) for x in array_arg] def human_readable_encoding(msg: MsgLike) -> bytes: @@ -55,8 +59,12 @@ def human_readable_encoding_recursive(msg: MsgLike) -> NestedDictionary: value = [byte.decode(ENCODING) for byte in value] elif RESERVED_FIELD_TYPE in field_types: value = [human_readable_encoding_recursive(msg_in_list) for msg_in_list in value] + elif isinstance(value, list) and len(value) == 0: + value = [] elif isinstance(value, ndarray): value = numpy_encoding(value) + elif isinstance(value, array): + value = array_encoding(value) elif RESERVED_FIELD_TYPE in field_types: value = human_readable_encoding_recursive(value) msg_dict[field] = value @@ -77,19 +85,29 @@ def human_readable_decoding_recursive(msg_dict: NestedDictionary, msg = msg_type() set_value: object for field, value in msg_dict.items(): - if isinstance(getattr(msg, field), bytes): + field_default = getattr(msg, field) + if isinstance(field_default, bytes): if isinstance(value, str): set_value = value.encode(ENCODING) elif isinstance(value, dict): set_value = human_readable_decoding_recursive(value, type(getattr(msg, field))) + elif isinstance(field_default, list): + if len(field_default) == 0: + set_value = [] + else: + field_default_element = field_default[0] + if isinstance(field_default_element, bytes): + value = cast(list[str], value) + set_value = [byte.encode(ENCODING) for byte in value] + elif RESERVED_FIELD_TYPE in msg_type.get_fields_and_field_types()[field]: + value = cast(Iterable[NestedDictionary], value) + set_value = [human_readable_decoding_recursive(msg_in_list, + type(getattr(msg, field)[0])) + for msg_in_list in value] + else: + set_value = value else: set_value = value setattr(msg, field, set_value) return msg - -from test_msgs.msg import Arrays - -# print(Arrays()) - -print(human_readable_decoding(human_readable_encoding(Arrays()), Arrays)) From 0c197cbb1e1066831ba2afdf8513ca9c924cb66b Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 21:45:40 -0400 Subject: [PATCH 09/12] Mypy fixes Signed-off-by: Michael Carlstrom --- mqtt_ros_bridge/encodings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index e70e2d7..835894c 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -7,7 +7,7 @@ from numpy.typing import NDArray from rclpy.type_support import check_is_valid_msg_type -NestedDictionary: TypeAlias = dict[str, 'NestedDictionary'] | dict[str, object] +NestedDictionary: TypeAlias = dict[str, object] class MsgLike(Protocol): From 9a75f48b42666ea69a9f3e0336574efa41671ad8 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 22:49:21 -0400 Subject: [PATCH 10/12] Fix bounds Signed-off-by: Michael Carlstrom --- mqtt_ros_bridge/encodings.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index 835894c..c582aab 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -1,9 +1,9 @@ import json from array import array -from typing import (Any, Iterable, MutableSequence, Protocol, Type, TypeAlias, +from typing import (Iterable, MutableSequence, Protocol, Type, TypeAlias, TypeVar, cast) -from numpy import ndarray +from numpy import ndarray, floating, integer from numpy.typing import NDArray from rclpy.type_support import check_is_valid_msg_type @@ -21,13 +21,15 @@ def get_fields_and_field_types(cls) -> dict[str, str]: MsgLikeT = TypeVar("MsgLikeT", bound=MsgLike) ArrayElementT = TypeVar('ArrayElementT', int, float, str) - RESERVED_FIELD_TYPE = '/' ENCODING = 'latin-1' -def numpy_encoding(array_arg: NDArray[Any]) -> list[int]: - return [int(x) for x in array_arg] +def numpy_encoding(array_arg: NDArray[integer] | NDArray[floating]) -> list[int] | list[float]: + if isinstance(array_arg[0], integer): + return [int(x) for x in array_arg] + else: + return [float(x) for x in array_arg] def array_encoding(array_arg: MutableSequence[ArrayElementT]) -> list[ArrayElementT]: From cffe90fa783cb3f1547a1767b4405bc0ebbee139 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 22:58:24 -0400 Subject: [PATCH 11/12] merge Signed-off-by: Michael Carlstrom --- mqtt_ros_bridge/bridge_node.py | 23 +++++++++-------------- mqtt_ros_bridge/encodings.py | 2 +- mqtt_ros_bridge/serializer.py | 27 +++++++++++++++++++++------ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/mqtt_ros_bridge/bridge_node.py b/mqtt_ros_bridge/bridge_node.py index 7586ba7..76b6fd5 100644 --- a/mqtt_ros_bridge/bridge_node.py +++ b/mqtt_ros_bridge/bridge_node.py @@ -1,28 +1,23 @@ -from typing import Any, TypeVar, Callable, Generic from dataclasses import dataclass +from typing import Any, Callable, Generic, Type +import paho.mqtt.client as MQTT import rclpy +from mqtt_ros_bridge.encodings import MsgLikeT +from mqtt_ros_bridge.serializer import ROSDefaultSerializer, Serializer +from rclpy._rclpy_pybind11 import RMWError from rclpy.node import Node from rclpy.publisher import Publisher from rclpy.subscription import Subscription -from rclpy._rclpy_pybind11 import RMWError - from std_msgs.msg import String -import paho.mqtt.client as MQTT - -from mqtt_ros_bridge.serializer import Serializer, ROSDefaultSerializer - - -T = TypeVar('T') - @dataclass -class TopicInfo(Generic[T]): +class TopicInfo(Generic[MsgLikeT]): """Metadata about a single topic.""" name: str - msg_type: T + msg_type: Type[MsgLikeT] serializer: type[Serializer] publish_on_ros: bool @@ -65,7 +60,7 @@ def __init__(self) -> None: self.mqtt_client.on_message = self.mqtt_callback - def make_ros_callback(self, topic_info: TopicInfo[T]) -> Callable[[T], None]: + def make_ros_callback(self, topic_info: TopicInfo[MsgLikeT]) -> Callable[[MsgLikeT], None]: """ Create a callback function which re-publishes messages on the same topic in MQTT. @@ -75,7 +70,7 @@ def make_ros_callback(self, topic_info: TopicInfo[T]) -> Callable[[T], None]: information about the topic that the callback will publish on """ - def callback(msg: T) -> None: + def callback(msg: MsgLikeT) -> None: self.get_logger().info(f'ROS RECEIVED: Topic: "{topic_info.name}" Payload: "{msg}"') self.mqtt_client.publish(topic_info.name, topic_info.serializer.serialize(msg)) diff --git a/mqtt_ros_bridge/encodings.py b/mqtt_ros_bridge/encodings.py index c582aab..5feef49 100644 --- a/mqtt_ros_bridge/encodings.py +++ b/mqtt_ros_bridge/encodings.py @@ -3,7 +3,7 @@ from typing import (Iterable, MutableSequence, Protocol, Type, TypeAlias, TypeVar, cast) -from numpy import ndarray, floating, integer +from numpy import floating, integer, ndarray from numpy.typing import NDArray from rclpy.type_support import check_is_valid_msg_type diff --git a/mqtt_ros_bridge/serializer.py b/mqtt_ros_bridge/serializer.py index 67a9a4d..658decb 100644 --- a/mqtt_ros_bridge/serializer.py +++ b/mqtt_ros_bridge/serializer.py @@ -1,7 +1,10 @@ -from typing import Any from abc import ABC, abstractmethod +from typing import Type -from rclpy.serialization import serialize_message, deserialize_message +from mqtt_ros_bridge.encodings import (MsgLike, MsgLikeT, + human_readable_decoding, + human_readable_encoding) +from rclpy.serialization import deserialize_message, serialize_message class Serializer(ABC): @@ -9,7 +12,7 @@ class Serializer(ABC): @staticmethod @abstractmethod - def serialize(message: Any) -> bytes: + def serialize(message: MsgLike) -> bytes: """ Serialize the provided ROS message to a bytes for MQTT. @@ -27,7 +30,7 @@ def serialize(message: Any) -> bytes: @staticmethod @abstractmethod - def deserialize(serialized_message: bytes, message_type: Any) -> Any: + def deserialize(serialized_message: bytes, message_type: Type[MsgLikeT]) -> MsgLikeT: """ Deserialize the provided bytes into a ROS message of the provided type. @@ -50,9 +53,21 @@ class ROSDefaultSerializer(Serializer): """Serialize and deserialize messages using the default ROS message serializer.""" @staticmethod - def serialize(message: Any) -> bytes: + def serialize(message: MsgLike) -> bytes: return serialize_message(message) @staticmethod - def deserialize(serialized_message: bytes, message_type: Any) -> Any: + def deserialize(serialized_message: bytes, message_type: Type[MsgLikeT]) -> MsgLikeT: return deserialize_message(serialized_message, message_type) + + +class HumanReadableSerializer(Serializer): + """Serialize and deserialize messages using the default ROS message serializer.""" + + @staticmethod + def serialize(message: MsgLike) -> bytes: + return human_readable_encoding(message) + + @staticmethod + def deserialize(serialized_message: bytes, message_type: Type[MsgLikeT]) -> MsgLikeT: + return human_readable_decoding(serialized_message, message_type) From 23432636e499c992b3f2e71e8331e2aca2dbc9fb Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 28 Mar 2024 23:07:43 -0400 Subject: [PATCH 12/12] Add comment Signed-off-by: Michael Carlstrom --- mqtt_ros_bridge/bridge_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mqtt_ros_bridge/bridge_node.py b/mqtt_ros_bridge/bridge_node.py index 76b6fd5..0f8978c 100644 --- a/mqtt_ros_bridge/bridge_node.py +++ b/mqtt_ros_bridge/bridge_node.py @@ -18,6 +18,7 @@ class TopicInfo(Generic[MsgLikeT]): name: str msg_type: Type[MsgLikeT] + # Should Serializer also be generic across MsgLikeT? serializer: type[Serializer] publish_on_ros: bool