A standalone, ROS-independent C++ SDK for controlling Robotiq 2F adaptive grippers (2F-85 / 2F-140 / Hand-E class) over their Modbus RTU serial link. Cross-platform: Linux, Windows, macOS.
The ROS 2 driver will consume this SDK.
A layered API around a shared process image:
Gripper— the API for applications. Construction opens the link, reads the gripper status (it fails when no gripper answers), and starts exchanging. All Modbus traffic happens in the background exchange cycle (one FC 0x17 transaction per period, up to ~200 Hz at 115200 baud). The command image is seeded from the gripper's own state echoes before anything is written — connecting never disturbs a running gripper.
setCommand()/getStatus() exchange whole GripperCommand/GripperStatus
blocks. Each block has named fields (command.positionRequest,
command.speed, ...) and small accessors for its packed action/status
byte, plus the raw bytes through data(). Reads stay whole-snapshot, so
consecutive fields never come from different exchange cycles. The block byte layout and status
bit masks are published in Robotiq/gripper/register_map.hpp, and the
Modbus register addresses in Robotiq/detail/modbus_constants.hpp, mirroring
the instruction manual.
The Modbus protocol layer is nanoMODBUS; serial transport is libserialport.
Requirements: CMake ≥ 3.16, a C++17 compiler, libserialport.
| Platform | libserialport |
|---|---|
| Ubuntu/Debian | sudo apt install libserialport-dev |
| macOS | brew install libserialport |
| Windows | MSYS2 — see Windows (MSYS2) below |
cmake -S sdk_cpp -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build # run unit tests, no hardware neededNeither vcpkg nor Conan Center packages libserialport, so the supported
Windows toolchain is MSYS2/GCC — the same environment this repo's CI
uses. MSYS2 is a Windows distribution of Unix tooling with pacman
(the Arch Linux package manager) and a large repository of prebuilt
native libraries.
-
Install MSYS2 from msys2.org (or
winget install MSYS2.MSYS2). -
Open the MSYS2 UCRT64 shell from the Start menu.
-
Install the toolchain and dependencies:
pacman -Syu pacman -S --needed mingw-w64-ucrt-x86_64-gcc \ mingw-w64-ucrt-x86_64-cmake \ mingw-w64-ucrt-x86_64-ninja \ mingw-w64-ucrt-x86_64-libserialport -
Build and test as usual, from the same shell:
cmake -S sdk_cpp -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j ctest --test-dir build
This produces native Windows binaries (GCC, no emulation layer). MSVC is not currently supported: libserialport ships no MSVC package, so a Visual Studio build would have to compile libserialport itself.
A complete example — waiting for motion to settle, reading the
position back, injecting a log sink — is built as described in the Building section
and can be found here:
sdk_cpp/examples/move_gripper.cpp
Run it by executing:
./build/examples/move_gripper /dev/ttyUSB0 # Linux (macOS: /dev/tty.usbserial-XXXX)
./build/examples/move_gripper.exe COM3 # Windows: find the port in Device ManagerThe example activates the gripper (calibration sweep), opens, and closes — keep the jaws clear.
makeFakeGripper() returns a Gripper driving a fake device instead of a
serial port, for bring-up, demos and CI on machines with no hardware attached:
#include <Robotiq/gripper/fake/gripper_factory.hpp>
auto gripper = Robotiq::makeFakeGripper(); // no port openedEverything above the wire is the real thing — the typed blocks, the exchange
cycle, the process image, activate() / recoverFromFault(). The device below
it is deliberately minimal: activation completes instantly and the fingers are
wherever they were last commanded to be. There is no motion profile, no travel
time, no object detection and no fault injection.
find_package(grippers REQUIRED) # installed
# or: add_subdirectory(path/to/grippers/sdk_cpp)
target_link_libraries(your_target PRIVATE Robotiq::grippers)- Linux: add yourself to the
dialoutgroup for/dev/ttyUSB*access. The SDK sets the FTDIlatency_timerto 1 ms automatically when it has permission (the kernel default of 16 ms triples Modbus latency); for unprivileged use, ship a udev rule that sets it at plug time. - Windows: the FTDI latency timer is a driver setting (Device Manager → COM port → Port Settings → Advanced → Latency Timer); set it to 1 ms for high-rate control.
- macOS: the FTDI latency timer defaults to 16 ms — capping the exchange
rate near ~60 Hz — and macOS offers no way to lower it from the SDK. To run
faster, install FTDI's VCP driver
and set its
LatencyTimerto1(in the driver'sInfo.plist); it then applies to every open, including this SDK's. On macOS 11+ also approve the driver in System Settings → Privacy & Security and make sure it — not Apple's built-in FTDI driver — binds your adapter (kextstat | grep -i ftdi). Otherwise ~60 Hz is the ceiling on the default driver. - Factory-default link settings: 115200 baud, 8N1, Modbus slave 0x09.
- Port naming:
/dev/ttyUSB0on Linux,COM3on Windows,/dev/tty.usbserial-XXXXon macOS. - Windows: thread pacing is quantized by the OS timer (default tick ~15.6 ms), so exchange periods shorter than ~16 ms will run slower than configured. High-rate control on Windows is currently untuned — open an issue if your application needs it.
The SDK core — Gripper and its threaded exchange loop — compiles for
freestanding targets (e.g. STM32 microcontrollers, arm-none-eabi). Everything
OS-flavored is injectable; two CMake options select what ships with it:
GRIPPERS_HOSTED=OFF(default ON) drops the hosted conveniences: thestd::thread-backedPlatform(makeDefaultPlatform()), and the stderr default logger. ConstructGripperwith its platform-taking constructor and aPlatformimplemented over your RTOS.ports/threadx/threadx_platform.hppis the working reference (Azure RTOS ThreadX, with the exchange task's stack size and priority as constructor arguments); porting to another RTOS means implementing its four members over the native primitives.GRIPPERS_BUILD_DEFAULT_SERIAL=OFF(default followsGRIPPERS_HOSTED) drops the libserialport-backedDefaultSerialand its dependency. Inject your ownSerial(e.g. a UART transport) via theunique_ptr<Serial>constructors ofdetail::GripperModbusClient/Gripper.detail::GripperModbusClientis the no-thread layer: one Modbus transaction per call, so a single-threaded superloop schedules the exchange itself. This is the simplest path for small MCUs and needs no RTOS — and noPlatform.
Two integration caveats, detailed in ports/threadx/threadx_platform.hpp
because each presents as an unexplained hang: the injected Serial::read must
yield the CPU while awaiting bytes (interrupt/DMA + RTOS semaphore, never a
polled busy-wait), and std::chrono::steady_clock must be backed by a real
monotonic clock on the target.
Semantic versioning from 1.0.0 on: patch releases fix
bugs, minor releases add API, and a breaking change to the documented API takes
a major release. The documented API is what this README and the public headers
describe — Gripper, the command/status blocks and the register map,
ConnectionConfig, Serial, Platform, Logger, and
detail::GripperModbusClient for the no-thread path. Anything under
Robotiq/detail/ that is not described here is internal and may change in any
release.
BSD-3-Clause. Portions derived from PickNik Robotics' ros2_robotiq_gripper driver (BSD-3-Clause); original copyright notices are preserved in the affected files and full history is preserved in git.