ROS2 driver for the MSG gripper using the Spectral BLDC library and CAN bus.
Developed by UCLA Biomechatronics Lab and Ben Forbes
The node accepts sensor_msgs/JointState commands with position specified as encoder counts (0–255) and publishes feedback with position in encoder counts and effort in milliamps.
| Direction | Topic | Message Type |
|---|---|---|
| Command | /gripper/command_joint_states |
sensor_msgs/JointState |
| Feedback | /gripper/feedback_joint_states |
sensor_msgs/JointState |
A control_msgs/GripperCommand action server is also available at /gripper_command.
- Ubuntu 22.04 LTS with ROS2 Humble installed (installation guide)
- Python 3.10+
- A USB-to-CAN adapter that supports the
slcanprotocol (e.g., CANable, PCAN-USB)
macOS / Windows: Not officially tested, but the Python driver code is platform-agnostic. ROS2 and
python-canmust be available on your platform. The only OS-specific setting is thechanneldevice path — see Hardware & CAN Setup below.
The gripper communicates over CAN bus via a USB-to-CAN adapter using the slcan protocol.
After plugging in the USB-to-CAN adapter, find the device path:
Linux:
ls /dev/ttyACM* /dev/ttyUSB*
# Typical result: /dev/ttyACM0 or /dev/ttyUSB0macOS:
ls /dev/tty.usb*
# Typical result: /dev/tty.usbmodem1101 or /dev/tty.usbserial-1234Windows:
Open Device Manager → Ports (COM & LPT) → note the COMx number (e.g., COM3).
The channel parameter in config/default.yaml (or passed at launch) is the only OS-specific setting:
| Platform | Typical channel value |
|---|---|
| Linux | /dev/ttyACM0 or /dev/ttyUSB0 |
| macOS | /dev/tty.usbmodem* or /dev/tty.usbserial* |
| Windows | COM3 (adjust number as needed) |
On Linux you may need to add your user to the dialout group to access /dev/ttyACM* without sudo:
sudo usermod -aG dialout $USER
# Log out and back in for the change to take effectpip install Spectral_BLDC python-cancd ~/ros2_ws/src
git clone https://github.com/BiomechatronicsLab/source_robotics_msg_gripper_ros2.git
cd ~/ros2_ws
colcon build --packages-select msg_gripper_ros2
source install/setup.bashros2 launch msg_gripper_ros2 msg_gripper_launch.pyWith custom config or namespace:
ros2 launch msg_gripper_ros2 msg_gripper_launch.py \
config_file:=/path/to/my_config.yaml \
node_name:=my_gripper \
namespace:=robotOverride individual parameters by providing a custom config file:
# Copy and edit the default config
cp src/msg_gripper_ros2/config/default.yaml ~/my_gripper.yaml
# Edit channel, bustype, etc. in ~/my_gripper.yaml, then:
ros2 launch msg_gripper_ros2 msg_gripper_launch.py \
config_file:=~/my_gripper.yamlOr run the node directly with --ros-args for one-off overrides:
ros2 run msg_gripper_ros2 msg_gripper_node \
--ros-args -p channel:=/dev/ttyUSB0Publish a sensor_msgs/JointState to the command topic:
| Field | Description |
|---|---|
position[0] |
Target position — encoder counts (0–255) |
velocity[0] |
(optional) Speed 0–255, default: 20 |
effort[0] |
(optional) Force in mA, default: 500 |
Commands are filtered by a deadband: if the new position is within position_deadband counts (default: 5) of the last sent position, the command is silently dropped. This prevents redundant CAN traffic.
The feedback JointState contains:
| Field | Description |
|---|---|
position[0] |
Current position in encoder counts |
effort[0] |
Motor current in mA |
All tests are run with pytest from the package directory:
cd ~/src/source_robotics_msg_gripper_ros2/
source /opt/ros/humble/setup.bashpytest test/ -v -m "not hardware"The unit tests mock the Spectral_BLDC library so no CAN adapter or gripper needs to be connected.
With the gripper connected (the test suite launches the node automatically):
pytest test/ -vCustom device path:
pytest test/ -v --channel /dev/ttyUSB0If the node is already running externally:
pytest test/ -v --no-launch| Option | Default | Description |
|---|---|---|
--channel |
/dev/ttyACM0 |
CAN serial device path |
--bustype |
slcan |
CAN bus type |
--bitrate |
1000000 |
CAN bitrate in bps |
--no-launch |
False |
Skip launching the node (assumes it is already running) |
--startup-timeout |
10 |
Seconds to wait for node startup |
# Command gripper to position 128
ros2 topic pub --once /gripper/command_joint_states sensor_msgs/msg/JointState \
"{position: [128]}"
# Monitor feedback
ros2 topic echo /gripper/feedback_joint_statesThe node exposes a control_msgs/GripperCommand action at /gripper_command.
position maps to encoder counts (0–255); max_effort maps to force in mA (0 = use default).
Send a goal from the command line:
ros2 action send_goal /gripper_command control_msgs/action/GripperCommand \
"{command: {position: 200.0, max_effort: 500.0}}"Send a goal and stream feedback:
ros2 action send_goal --feedback /gripper_command control_msgs/action/GripperCommand \
"{command: {position: 100.0, max_effort: 0.0}}"
max_effort: 0.0uses the node'sdefault_forceparameter.
All parameters are configurable via config/default.yaml or at launch time.
| Parameter | Default | Description |
|---|---|---|
bustype |
slcan |
CAN bus type |
channel |
/dev/ttyACM0 |
CAN interface device (OS-specific — see above) |
bitrate |
1000000 |
CAN bitrate |
node_id |
0 |
Spectral BLDC node ID |
default_speed |
20 |
Default speed (0–255) |
default_force |
500 |
Default force in mA |
position_deadband |
5 |
Minimum change in counts to send a new command |
auto_calibrate |
true |
Run calibration on startup |
command_topic |
/gripper/command_joint_states |
Command subscription topic |
feedback_topic |
/gripper/feedback_joint_states |
Feedback publisher topic |
- Check that the USB-to-CAN adapter is plugged in and recognized by the OS.
- Verify the
channelparameter matches the actual device path (e.g.,/dev/ttyACM0). - Try a lower
bitrateif the adapter does not support 1 Mbit/s.
# Quick fix (requires sudo each session):
sudo chmod a+rw /dev/ttyACM0
# Permanent fix (requires logout):
sudo usermod -aG dialout $USER- The calibration command is sent once ~1 second after startup (
auto_calibrate: true). - If the gripper does not move, check CAN communication first (see above).
- You can disable auto-calibration with
auto_calibrate: falseand trigger it manually if needed. - Ensure the gripper has enough range of motion and is not mechanically obstructed.
- Confirm the node started without errors (
ros2 node listshould showmsg_gripper_node). - The motor only responds after being addressed — check that at least one command has been sent.
- Use
ros2 topic hz /gripper/feedback_joint_statesto confirm the publisher is running at ~20 Hz.
msg_gripper_ros2/
├── CMakeLists.txt
├── package.xml
├── setup.py
├── pytest.ini
├── LICENSE
├── README.md
├── CONTRIBUTING.md
├── CODEOWNERS.md
├── msg_gripper_ros2/
│ ├── __init__.py
│ └── msg_gripper_node.py
├── launch/
│ └── msg_gripper_launch.py
├── config/
│ └── default.yaml
├── resource/
│ └── msg_gripper_ros2
└── test/
├── conftest.py # shared fixtures and CLI options
├── test_unit.py # offline unit tests (no hardware)
└── test_hardware.py # hardware-in-the-loop tests
Bug reports and pull requests are welcome at: https://github.com/BiomechatronicsLab/source_robotics_msg_gripper_ros2/issues
Please include:
- ROS2 distribution and OS version
- USB-to-CAN adapter model
- Full error output from
ros2 launch msg_gripper_ros2 msg_gripper_launch.py