- ROS2 interface for virtual CAN (vcan0), for developing and testing CAN logic without vehicle hardware
- Type a signal name and a value in the terminal, and it is encoded into a CAN frame from the
.dbcdefinition - The received frame is decoded back into physical values and published as one topic per signal, so the round trip is visible end to end
terminal input vcan0 ROS2 topic
Vehicle_speed 80 ──► 711 [8] 00 50 00 ... ──► /can/Vehicle_speed { value: 80.0, unit: kph }
- Ubuntu 22.04
- ROS2 Humble
- CAN : SocketCAN virtual interface (vcan0)
| Package | Description |
|---|---|
can_interface | Bus (raw SocketCAN socket), DbcLoader (.dbc parser), DBC (signal encode/decode) |
can_msgs | Frame.msg (raw frame), Signal.msg (one decoded signal), ParsedMessage.msg (one message) |
socketcan_interface | Receives frames from vcan0, publishes raw frames and decoded signals |
virtual_can | Encodes values typed in the terminal and writes them to vcan0 |
sudo modprobe vcan # Load the vcan kernel module
sudo ip link add dev vcan0 type vcan # Create the interface
sudo ip link set up vcan0 # Bring it up- The setting is lost on reboot, so it must be re-applied
ip link show vcan0 # Check the interface
candump vcan0 # Check receptionDBC files live in a separate private repository (IVSP-DBC), registered as a submodule at dbc/.
git clone --recursive git@github.com:dev-muuu/VirtualCAN-ROS2-Interface.git
git submodule update --init --recursive # If dbc/ is empty
git submodule update --remote dbc # Pull the latest DBC- Access to the private repository is required — without it the clone succeeds but
dbc/stays empty
DbcLoader parses BO_ (message) and SG_ (signal) lines of a .dbc file directly. Bit layout follows the DBC convention.
| Byte order | start_bit | Extraction |
|---|---|---|
Little endian (@1) | LSB position | Shift the 8-byte little-endian value |
Big endian (@0) | MSB position | Collect bits in Motorola order |
Resolution order for the DBC path:
| Priority | Source | Example |
|---|---|---|
| 1 | Command line argument | ros2 run virtual_can virtual_can_sender dbc/Sportage_CAN_ver01.dbc |
| 2 | CAN_DBC_PATH environment variable | export CAN_DBC_PATH=$PWD/dbc/Sportage_CAN_ver01.dbc |
| 3 | Default | dbc/Sportage_CAN_ver01.dbc (relative to the working directory) |
Inject arbitrary values into the bus without vehicle hardware, to verify the receive → parse → log path end to end.
- Values are given in physical units (km/h, deg) — bit packing (start bit, length, byte order, sign, factor/offset) is resolved from the signal database
- Symmetric with
extract_signal, so encode → send → receive → decode can be verified in a single loop - Reproduces specific conditions (e.g. speed 80 km/h) on demand, which is hard to trigger in an actual vehicle
| Method | Input | Output |
|---|---|---|
create_frame_by_signal_name | One signal name and value | One frame, message ID resolved automatically |
create_frame | Message ID and multiple signal values | One frame |
create_frames_by_signal_names | Multiple signal values across messages | Multiple frames, grouped by message ID |
#include"can_interface/dbc.h"
can_interface::DBCdbc("dbc/Sportage_CAN_ver01.dbc");
// 1. One signal — message ID is resolved from the signal nameauto frame = dbc.create_frame_by_signal_name("EPS_command", 90.0);
if (frame) bus.send(*frame);
// 2. Multiple signals within one messageauto frame = dbc.create_frame(343, {
{"EPS_command", 90.0},
{"SCC_command", 1.5},
});
// 3. Multiple signals across messages — grouped by message ID automaticallyauto frames = dbc.create_frames_by_signal_names({
{"EPS_command", 90.0},
{"Alive_count", 7.0},
});
for (constauto& f : frames) bus.send(f);- Signal names must match the
SG_names in the DBC exactly — check withprint_signal_map() create_framereturnsstd::nulloptif the message ID is unknown, or if a signal does not belong to that messagecreate_frames_by_signal_namessilently skips unknown signal names, so verify the returned count
For values entered at runtime, use the sender directly — see Interactive Command. Define a generator in can_frame_generator.h only for fixed patterns that are repeated often.
namespacecan_frame_generator
{
can_frame generate_velocity()
{
can_interface::DBC dbc;
auto frame_opt = dbc.create_frame_by_signal_name("EPS_command", 90.0);
return frame_opt ? *frame_opt : can_frame{};
}
}colcon build --symlink-install
source install/setup.bashRun from the workspace root — both executables resolve the DBC through a relative path.
- ROS2 Interface — reads vcan0, publishes raw frames and decoded signals
source install/setup.bash
ros2 run socketcan_interface socketcan_interface- Frame Sender — encodes the values typed in the terminal
source install/setup.bash
ros2 run virtual_can virtual_can_sender- Monitor — see Monitoring
ros2 topic echo /can/Vehicle_speedParameters of the ROS2 interface:
| Parameter | Default | Description |
|---|---|---|
interface | vcan0 | SocketCAN interface name — set to can0 for a real bus |
dbc_path | (empty) | Falls back to CAN_DBC_PATH, then dbc/Sportage_CAN_ver01.dbc |
parsed_prefix | /can | Namespace of the decoded topics |
publish_grouped | false | Also publish one topic per message, carrying all of its signals |
ros2 run socketcan_interface socketcan_interface --ros-args \
-p interface:=vcan0 \
-p dbc_path:=$PWD/dbc/Sportage_CAN_ver01.dbc \
-p publish_grouped:=trueType a signal name and a value in the sender terminal, and the frame is encoded and sent at that moment. No rebuild is needed to change a value.
| Command | Description |
|---|---|
<signal> <value> [...] | Send once — the default |
hold <signal> <value> [...] | Repeat at 10 Hz until changed, like a real bus |
show | Print the values currently being repeated |
clear | Stop repeating |
list | Print every message and signal defined in the DBC |
test | Send the fixed test frame (0x123) once |
quit | Exit |
EPS_command 90 # One frame, right now
EPS_command 90 SCC_command 1.5 # Two signals of one message -> one frame
Alive_count 7 # Different message -> separate frame
hold EPS_command 90 # Start repeating at 10 Hz
hold EPS_command 120 # Change the value while repeating
clear # Stop repeating- Signals are grouped by message ID automatically, so signals from different messages can be given on one line
- A signal name that is not in the DBC is rejected before sending, with the name printed
- Nothing is sent until an input arrives —
holdis the only command that keeps sending
| Topic | Type | Description |
|---|---|---|
/frame_can0 | can_msgs/Frame | Raw CAN frame, every ID including those absent from the DBC |
/can/<signal> | can_msgs/Signal | One topic per signal — header, name, value, unit |
/can/<message>/<signal> | can_msgs/Signal | Used only when the same signal name appears in more than one message |
/can/<message> | can_msgs/ParsedMessage | All signals of one message at once — only with publish_grouped:=true |
- Every topic in the DBC is created at startup, so
ros2 topic echoresolves the type before the first frame arrives valueis a single scalar, so it can be plotted directly inrqt_plotor Foxglove
ros2 topic list | grep ^/can # Every decoded signal
ros2 topic echo /can/Vehicle_speed # One signal
ros2 topic echo /frame_can0 # Raw frame
candump vcan0 # Check at the SocketCAN layer- Start
echobefore typing into the sender — a single-shot send is gone by the time it starts - Use
holdto keep a value on the bus while inspecting it