Extending the Type System¶
Sugarcoat uses a type system built on SupportedType to bridge ROS 2 message types with Python-native data. This document explains how the system works and how to extend it with custom types.
SupportedType Base Class¶
Every supported message type is a subclass of ros_sugar.io.supported_types.SupportedType. The base class defines three extension points:
class SupportedType:
# The ROS 2 message class (e.g., std_msgs.msg.String)
_ros_type: type
# Callback class for deserializing incoming messages
callback = callbacks.GenericCallback
@classmethod
def convert(cls, output, **_) -> Any:
"""Convert Python data into a ROS message instance."""
return output
@classmethod
def get_ros_type(cls) -> type:
"""Return the underlying ROS 2 message class."""
return cls._ros_type
_ros_type¶
Class attribute that holds the ROS 2 message class. This is used to create subscriptions, validate topic compatibility, and generate UI schemas.
callback¶
A callback class (typically a subclass of GenericCallback) that handles deserialization of incoming ROS messages. Different types use specialized callbacks – for example, ImageCallback for sensor images, OdomCallback for odometry, StdMsgCallback for simple std_msgs types.
convert¶
A classmethod that takes Python-native data and returns a ROS message instance. The first positional argument should be named output. This is called by Publisher when a component publishes output. For example, Image.convert() accepts either a numpy.ndarray or an existing ROSImage and returns a ROSImage.
Built-in Types¶
Sugarcoat ships with the following built-in types in ros_sugar.io.supported_types:
Type |
ROS Message |
Callback |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(default) |
Registering Additional Types¶
Use add_additional_datatypes() to register custom types at runtime:
from ros_sugar.io.supported_types import add_additional_datatypes
add_additional_datatypes([MyCustomType, AnotherType])
This function maintains a global _additional_types dictionary. When a type with the same __name__ already exists, the function merges callbacks and conversion functions rather than replacing the existing entry. This allows multiple packages to augment the same type with additional callbacks.
Step-by-Step: Adding a Custom Type¶
Suppose you want to add support for sensor_msgs/Range:
1. Define the type class¶
from sensor_msgs.msg import Range as ROSRange
from ros_sugar.io.supported_types import SupportedType
from ros_sugar.io.callbacks import GenericCallback
class Range(SupportedType):
"""Range sensor message support."""
_ros_type = ROSRange
callback = GenericCallback
@classmethod
def convert(cls, output: float, **_) -> ROSRange:
msg = ROSRange()
msg.range = output
return msg
2. Register the type¶
from ros_sugar.io.supported_types import add_additional_datatypes
add_additional_datatypes([Range])
3. Use it in a Topic¶
from ros_sugar.io import Topic
range_topic = Topic(name="/front_sonar", msg_type=Range)
The topic can now be used as an input or output on any BaseComponent.
Custom Callbacks¶
If the default GenericCallback is insufficient (for example, you need to extract specific fields or perform numpy conversions), create a custom callback class inheriting from GenericCallback:
from ros_sugar.io.callbacks import GenericCallback
class RangeCallback(GenericCallback):
def __call__(self, msg):
# Extract and store just the range value
self.output = msg.range
Then assign it to your type:
class Range(SupportedType):
_ros_type = ROSRange
callback = RangeCallback
# ...
How Derived Packages Register Types¶
Packages built on Sugarcoat (such as Kompass or EmbodiedAgents) register their own types by calling add_additional_datatypes() at import time. For example, a navigation package might add:
# In my_nav_package/__init__.py
from ros_sugar.io.supported_types import add_additional_datatypes
from .types import CostMap, Waypoint, TrajectoryArray
add_additional_datatypes([CostMap, Waypoint, TrajectoryArray])
This ensures that when any component from my_nav_package is imported, the types are immediately available for topic wiring and event conditions.
Merging Behavior¶
If two packages register a type with the same class name, add_additional_datatypes() merges them:
callback: If the existing type has no callback, the new one is used. If both have callbacks, they are combined into a list.
_ros_type: Only overwritten if the existing type has no
_ros_typeset.convert: Merged using the same list-accumulation logic as callbacks.
This allows, for example, one package to define the _ros_type and another to supply a specialized convert function for the same message type.