BaseNode: Dual-Mode Node
BaseNode is the common base class for communication modules in ros_base. Its most important feature is not "inheriting from a ROS node", but making the same business code work in two runtime modes:
- Attached mode: mounted onto a
BaseManager - Standalone mode: runs as an independent ROS2 node
1. The actual structure in the current implementation
The core logic of BaseNode.__init__() is:
def __init__(self, manager=None, *args, **kwargs):
self._manager = manager if manager is not None else Node(*args, **kwargs)
In practice:
- When
managerexists,self._manageris thatBaseManager - When
manageris absent,self._manageris a temporaryrclpy.node.Node
The following interfaces are then forwarded to self._manager:
create_publishercreate_subscriptioncreate_timerdestroy_publisherdestroy_subscriptionget_clock
2. Attached mode
This is the most common production setup. In this mode, a node only needs to:
- Subscribe to ROS messages
- Cache results into its own attributes
- Provide publish methods when needed
For example:
class SensorNode(BaseNode):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.latest_msg = None
self.sub = self.create_subscription(
Image, "/camera/color/image_raw", self.callback, 10
)
def callback(self, msg):
self.latest_msg = msg
Then a handler or agent can read data directly:
There is no need to create another internal topic just to pass images between modules in the same system.
3. Standalone mode
The current implementation supports calling:
One detail is easy to miss: standalone mode still requires rclpy.init() first.
Standard template:
import rclpy
rclpy.init()
node = MyNode(node_name="my_node")
try:
node.start_spin_standalone()
finally:
node.release_resources()
if rclpy.ok():
node.destroy_node()
rclpy.shutdown()
CamSubNode and Robot2VLMBridge both provide this kind of standalone entry point in the source tree.
4. Context exposed by default
When attached to a manager, BaseNode can access:
self.nodesself.agentsself.loggerself.timestampself.node_freq_hz
This makes it possible for a node to read system context, but it is still better to keep boundaries clear:
- Nodes are good at caching and bridging
- Complex business scheduling should stay out of nodes
- Nodes should not actively call agents or handlers in reverse, because that introduces coupling in the wrong direction
5. Resource cleanup
BaseNode.release_resources() is an empty method in the base class and is meant to be overridden by subclasses.
Typical cases include:
- Closing a video writer
- Stopping a background thread
- Closing a window
- Releasing a serial port or hardware handle
For example, CamSubNode.release_resources() stops the recording thread and closes the output video file.
6. The typical bridge role
In the two example projects, nodes often act as protocol boundary layers:
SigLoMa-VLM/sigloma_vlm/nodes/robot2vlm.pypublishes high-level perception results to the lower-level system and subscribes torl_ready,turn_done, andgrasp_donequad_deploy/quad_deploy/nodes/sigloma/vlm2robot.pyreceives upper-level control signals and sends backrl_ready,turn_done, andgrasp_done
The ideal shape of this kind of node is:
- Complex outside-facing protocol
- Simple inside-facing interface
- Business logic still kept inside handlers and agents