I need (we?) to create something similar for ROS2 and the DDS serialization protocol, but I really don’t know where to start.
Is there any ROS2/DDS guru out there willing to contribute and work together with me to build this?
What is needed:
Subscribe to a generic topic similarly to topic_tools::ShapeShifter (is this available in ROS2?)
We need to know the IDL at run-time and from rosbags.
Given the raw blob and the IDL, the deserialization must be done without any code generated header (before I start from scratch, maybe some library might help me do that?)
I’ll do my best to answer questions and give guidance, though I don’t have lots of time to dedicate to this at the moment.
You can get the raw data buffer for a received message (untyped) already, see:
This is the interface that rosbag2 uses (sort of), the wrinkle being that you still need to create a typed subscription so that doesn’t completely satisfy your needs. There is a “generic” subscription in the rosbag2 sources which accomplishes this, but has some drawbacks:
The drawback is (and @karsten please correct me if I’m wrong) that the type support for the type you’re subscribing to is available on the local system. Which means you still need the message package for the message locally on your machine. This isn’t how the other things you linked to from ROS 1 work (AFAIK), as they instead do runtime deserialization if needed. Whereas this solution from rosbag2 dynamically loads the appropriate type support at runtime, but the code that does the deserialization is still compiled at some point. You can see how that works here:
We do want a completely runtime based (de)serialization API, but we don’t have that exactly yet. We do have something like it with our “type support introspection” which is a special type support used by some of the rmw implementations to do runtime (de)serialization rather than generating ROS specific code to do it. This type support exposes to you member information like so (sensor_msgs/msg/Image as an example):
static const ::rosidl_typesupport_introspection_cpp::MessageMember Image_message_member_array[7] = {
{
"header", // name
::rosidl_typesupport_introspection_cpp::ROS_TYPE_MESSAGE, // type
0, // upper bound of string
::rosidl_typesupport_introspection_cpp::get_message_type_support_handle<std_msgs::msg::Header>(), // members of sub message
false, // is array
0, // array size
false, // is upper bound
offsetof(sensor_msgs::msg::Image, header), // bytes offset in struct
nullptr, // default value
nullptr, // size() function pointer
nullptr, // get_const(index) function pointer
nullptr, // get(index) function pointer
NULL // resize(index) function pointer
},
{
"height", // name
::rosidl_typesupport_introspection_cpp::ROS_TYPE_UINT32, // type
0, // upper bound of string
NULL, // members of sub message
false, // is array
0, // array size
false, // is upper bound
offsetof(sensor_msgs::msg::Image, height), // bytes offset in struct
nullptr, // default value
nullptr, // size() function pointer
nullptr, // get_const(index) function pointer
nullptr, // get(index) function pointer
NULL // resize(index) function pointer
},
{
"width", // name
::rosidl_typesupport_introspection_cpp::ROS_TYPE_UINT32, // type
0, // upper bound of string
NULL, // members of sub message
false, // is array
0, // array size
false, // is upper bound
offsetof(sensor_msgs::msg::Image, width), // bytes offset in struct
nullptr, // default value
nullptr, // size() function pointer
nullptr, // get_const(index) function pointer
nullptr, // get(index) function pointer
NULL // resize(index) function pointer
},
{
"encoding", // name
::rosidl_typesupport_introspection_cpp::ROS_TYPE_STRING, // type
0, // upper bound of string
NULL, // members of sub message
false, // is array
0, // array size
false, // is upper bound
offsetof(sensor_msgs::msg::Image, encoding), // bytes offset in struct
nullptr, // default value
nullptr, // size() function pointer
nullptr, // get_const(index) function pointer
nullptr, // get(index) function pointer
NULL // resize(index) function pointer
},
{
"is_bigendian", // name
::rosidl_typesupport_introspection_cpp::ROS_TYPE_UINT8, // type
0, // upper bound of string
NULL, // members of sub message
false, // is array
0, // array size
false, // is upper bound
offsetof(sensor_msgs::msg::Image, is_bigendian), // bytes offset in struct
nullptr, // default value
nullptr, // size() function pointer
nullptr, // get_const(index) function pointer
nullptr, // get(index) function pointer
NULL // resize(index) function pointer
},
{
"step", // name
::rosidl_typesupport_introspection_cpp::ROS_TYPE_UINT32, // type
0, // upper bound of string
NULL, // members of sub message
false, // is array
0, // array size
false, // is upper bound
offsetof(sensor_msgs::msg::Image, step), // bytes offset in struct
nullptr, // default value
nullptr, // size() function pointer
nullptr, // get_const(index) function pointer
nullptr, // get(index) function pointer
NULL // resize(index) function pointer
},
{
"data", // name
::rosidl_typesupport_introspection_cpp::ROS_TYPE_UINT8, // type
0, // upper bound of string
NULL, // members of sub message
true, // is array
0, // array size
false, // is upper bound
offsetof(sensor_msgs::msg::Image, data), // bytes offset in struct
nullptr, // default value
size_function__Image__data, // size() function pointer
get_const_function__Image__data, // get_const(index) function pointer
get_function__Image__data, // get(index) function pointer
resize_function__Image__data // resize(index) function pointer
}
};
With this information a specific rmw implementation can do serialization for a specific format (e.g. CDR which is the binary format used by DDS), see rmw fastrtps “dynamic” using it:
What’s missing for true runtime deserialization is being able to generate this information using only a runtime string as input. That currently doesn’t exist, even in Python (though the parts are there since the msg/idl parsing is written in Python), but we do want that in the future too.
Hopefully that gives you a bit to think about and some starting places for discussion if not implementation.
Last summer I created a plugin to read ros2 bags in PlotJuggler.
I never got around to finish it completely, but it worked good enough for us to analyze our ros2 bags.
It is still missing some important features, like :
a cmakelists.txt and package.xml that works for both ROS1 and ROS2
arrays / strings support
support for other serialization formats (for the moment sqlite3 & CDR is hardcoded)
plot real-time data (only bags are supported at the moment)
Also because in ros2-bags don’t contain the .msg file data (unlike ROS1), you have to source the workspace containing the compiled .msg files
I’ve done some work on getting a ROS2 DataStreamer plugin implemented for PlotJuggler based on the great information above. It’s far from complete, but I’ve got the basic functionality there: creating a ROS2 node in a separate thread, subscribing to topics, pulling data out via introspection, and plotting. I’d be interested in some feedback on what you would like to see in order to accept a pull request, assuming you haven’t already been working on this yourself.
As stated by others above, a workspace containing the compiled .msg files for messages you want to view must be sourced in order to have introspection. These are not needed at compile time though (the libraries are dynamically loaded), and as far as I know the same restriction applies for all the other ROS2 tools (e.g. the standard rqt plugins).
You can see what I’ve done so far here:
Main things that still need to be done from my point of view: