Reusable ROS 2 infrastructure for controlling multiple CAN devices through one shared bus owner.
The project separates the stable device-protocol ABI from the ROS 2 runtime implementation. A robot driver can therefore combine motors, grippers, dexterous hands, and sensors on the same CAN bus without duplicating the transport or introducing a new device-specific ABI.
| Package | Purpose |
|---|---|
shared_can_interface |
Versioned, device-neutral transport and protocol plugin ABI |
shared_can_ros2 |
Generic ros2_control bus manager and Linux SocketCAN transport plugin |
shared_can_ros2/CanSystem is the single owner of a CAN interface. It loads one transport plugin
and one or more data-protocol plugins through pluginlib.
Each ros2_control joint, sensor, or GPIO declares a protocol_plugin and an optional
protocol_instance. Components with the same instance are configured as one protocol group.
Every received frame is offered to every protocol plugin, while transmission and periodic polling
are serialized by the bus owner.
The included runtime plugins are:
<plugin>shared_can_ros2/CanSystem</plugin>
<param name="transport_plugin">shared_can_ros2/SocketCanTransport</param>
<param name="can_interface">can0</param>The shared frame type supports standard 11-bit and extended 29-bit Classical CAN identifiers. CAN FD is not currently supported.
Protocol packages depend only on shared_can_interface and implement
shared_can_interface::v1::ProtocolInterface. The explicit v1 namespace allows a future
incompatible ABI to coexist with existing plugins.
Command and state channels are addressed by their ROS interface names instead of fixed motor
fields. A plugin may therefore expose interfaces such as position, velocity, effort,
current, temperature, or device-specific names without changing the shared ABI.
Example device configuration:
<joint name="joint1">
<param name="protocol_plugin">example_motor_protocol/ExampleProtocol</param>
<param name="protocol_instance">arm_motors</param>
<param name="device_id">1</param>
<command_interface name="position"/>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>A protocol plugin provides initialization requests, frame decoding, periodic polling, enable/disable sequences, command encoding, named state values, and device fault state. It does not open a CAN socket itself.
Protocols may additionally implement shared_can_interface::v1::EnableReadinessInterface.
This is an optional side interface; the existing ProtocolInterface v1 vtable is unchanged.
During activation the manager calls begin_enable_check() once, repeats these protocols'
pre_enable_sequence() approximately every 20 ms, and dispatches feedback to all protocol
groups until pending_enable_states() is empty, with a 3000 ms overall timeout. Probes must not
enable, disable or calibrate devices, and must preserve an existing hold. Legacy protocols still
receive their single pre-enable sequence. Participating protocols may provide a fresh stopped
position through get_initial_command() after this wait; a warm protocol must return its existing
hold command to preserve preload. Explicit command initial values are not overwritten.
Build/install both the updated runtime and any protocol using this optional interface. An older runtime does not perform the readiness loop merely because the new protocol is installed.
Command interfaces can declare min and max for standard ROS interfaces. For custom interface
names, use minimum and maximum parameters so the shared manager can enforce limits without
triggering warnings from ros2_control's standard joint-limit parser. An explicit initial_value
is preserved during activation; if it is omitted, the manager tries to initialize the command from
the protocol's state interface with the same name.
<command_interface name="custom_command">
<param name="minimum">0.0</param>
<param name="maximum">100.0</param>
<param name="initial_value">10.0</param>
</command_interface>Place this repository and the device-protocol packages in the same ROS 2 workspace:
source /opt/ros/jazzy/setup.bash
cd ~/ros2_ws
rosdep install --from-paths src --ignore-src --rosdistro jazzy -r -y
colcon build --symlink-install --packages-up-to shared_can_ros2
source install/setup.bashCanSystemruns enable sequences only after initial-state and pre-enable checks succeed.- Normal deactivation does not send disable frames; devices keep executing their final motor-side command. A failure before entering enable sequences also sends no disable frames, preserving previously running holds. The existing emergency-disable behavior still applies to a failure after an enable sequence has started; an interrupted partial activation is not a safe hold.
- Before normal deactivation stops writes,
CanSystemflushes the current command interfaces once through the normal limit/encoding/send path. Controllers must prepare their final hold before hardware deactivation: the generic bus manager does not invent a hold or clear feed-forward. This handles shutdowns with no subsequent periodic write. It does not retry or re-enable motors; a closed bus or send failure is reported as deactivation failure, with further writes stopped. A successful flush means the transport accepted the frames, not that every motor acknowledged or executed them. Position-step and absolute command limits still apply on the final write. - A cold arm must remain physically supported during the readiness wait or a failed activation. Readiness is a protocol-level observation, not proof of output torque or safe payload support.
- Only one control application should own a physical CAN bus at a time.
- A protocol plugin must not create a competing SocketCAN receive socket.
- Device-specific command limits and enable behavior remain the responsibility of the hardware description and protocol plugin.
- Validate a new protocol with a separate diagnostic tool before loading it into
CanSystem.
The ROS packages declare the Apache License 2.0.