Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ jobs:
- name: Install Doxygen and Graphviz
run: sudo apt-get update && sudo apt-get install -y doxygen graphviz

# Pure logic, no build/network/submodule checkout needed — runs before
# Build so a failure here is fast and doesn't waste a Doxygen run.
# See test/ for what's covered: list-submodule-tags.js's tag parsing,
# sync-external-docs.js's stale-file pruning (scripts/lib/prune.js),
# the YouTube-embed remark plugin (both .md and .mdx compilation —
# regression coverage for the bug in PR #14's review), and
# site-nav-tree.mjs's versioned-sidebar drift guard (below).
- name: Unit tests
run: npm run test:unit

# Fails on broken internal links / anchors (onBrokenLinks: 'throw')
# and runs the sync + tools-table generators via the prebuild hook.
- name: Build
Expand All @@ -65,14 +75,24 @@ jobs:
# tool/guide page that's grown a self-installed sub-section list — all
# committed files, none of them gitignored (only the submodule-synced
# docs/drivers/**/*.md content is — see the comment above that rule in
# .gitignore). Nothing else regenerates and commits these after a
# merge, so a PR that changes something the generator depends on
# (e.g. a Dependabot submodule bump) without also re-running it
# locally would otherwise silently ship a deployed site that doesn't
# match what's committed. This catches that here, generically — no
# file list to keep in sync as the generator's own output grows.
# .gitignore). It does the exact same thing under versioned-tools/ for
# a per-tool-versioned tool's own wrapper page (see
# docs/contribute/versioning.mdx) — same gitignore split there
# (versioned-tools/**/*.md), so this check covers both directories.
# regenerate-versioned-sidebars.mjs (also run via the prebuild hook,
# as part of `npm run generate`) does the same for every
# `*_versioned_sidebars/*.json` snapshot — the unit-test drift guard
# above already covers this statically, but a real build regenerating
# something the static check couldn't see (e.g. a doc's frontmatter
# label actually changing) still needs this line to catch it.
# Nothing else regenerates and commits these after a merge, so a PR
# that changes something the generator depends on (e.g. a Dependabot
# submodule bump) without also re-running it locally would otherwise
# silently ship a deployed site that doesn't match what's committed.
# This catches that here, generically — no file list to keep in sync
# as the generator's own output grows.
- name: Verify generated content is committed
run: git diff --exit-code -- docs/
run: git diff --exit-code -- docs/ versioned-tools/ '*_versioned_sidebars/'

# Smoke-tests the generated output (key pages + tools table present).
- name: Test
Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ docs/drivers/**/*.md
# job's actual output before adding the blanket rule above.
!docs/drivers/Force Torque Sensor/Libraries/C/_readme.md

# Same rule, same reasoning, for a per-tool-versioned tool's live content —
# see docs/contribute/versioning.mdx. This is the *live*, always-regenerated
# 'Latest' content; it is NOT the same thing as a cut version's own frozen
# snapshot (<tool>_versioned_docs/, <tool>_versioned_sidebars/,
# <tool>_versions.json), which IS committed — a version cut is a deliberate,
# permanent record, not build output.
versioned-tools/**/*.md

# doxygen2docusaurus scratch output (scripts/sync-external-docs.js stages
# here, then copies the filtered result into docs/ — see external-jobs.js)
/.doxygen2docusaurus-staging
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@


C++ driver with functions to control `Robotiq` Adaptive Grippers: 2F85, 2F140 and Hand-E. It allows high communication frequency.

:::note
With the default baudrate of the gripper the maximum achievable communication
frequency is 250Hz. The communication frequency is set with the
`ConnectionConfig::connectionFrequency` parameter, which defaults to 100Hz.
:::

Cross-platform: Linux, Windows, macOS — and freestanding/RTOS targets such
as STM32 microcontrollers.


Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: C++
sidebar_label: C++
---

**Stable** currently tracks `2f85_cpp`'s only tagged release so far,
**v1.0.0** — there are no older releases archived here yet.

Once a newer tag is cut, older releases will be listed here with a link
to their own tag in the source repository.
191 changes: 191 additions & 0 deletions adaptive-grippers-cpp_versioned_docs/version-stable/_readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
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](https://github.com/robotiq/ros) will consume this SDK.

## Design

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](https://github.com/debevv/nanoMODBUS);
serial transport is [libserialport](https://sigrok.org/wiki/Libserialport).

## Building

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)](#windows-msys2) below |

```sh
cmake -S sdk_cpp -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build # run unit tests, no hardware needed
```

### Windows (MSYS2)

Neither 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.

1. Install MSYS2 from [msys2.org](https://www.msys2.org)
(or `winget install MSYS2.MSYS2`).
2. Open the **MSYS2 UCRT64** shell from the Start menu.
3. Install the toolchain and dependencies:

```sh
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
```

4. Build and test as usual, from the same shell:

```sh
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.

## Getting started

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`](https://github.com/robotiq/grippers/blob/v1.0.0/sdk_cpp/examples/move_gripper.cpp)

Run it by executing:
```sh
./build/examples/move_gripper /dev/ttyUSB0 # Linux (macOS: /dev/tty.usbserial-XXXX)
./build/examples/move_gripper.exe COM3 # Windows: find the port in Device Manager
```

The example activates the gripper (calibration sweep), opens, and
closes — keep the jaws clear.

### Without a gripper

`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:

```cpp
#include <Robotiq/gripper/fake/gripper_factory.hpp>

auto gripper = Robotiq::makeFakeGripper(); // no port opened
```

Everything 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.

## Consuming from CMake

```cmake
find_package(grippers REQUIRED) # installed
# or: add_subdirectory(path/to/grippers/sdk_cpp)
target_link_libraries(your_target PRIVATE Robotiq::grippers)
```

## Serial port notes

- **Linux**: add yourself to the `dialout` group for `/dev/ttyUSB*` access.
The SDK sets the FTDI `latency_timer` to 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](https://ftdichip.com/drivers/vcp-drivers/)
and set its `LatencyTimer` to `1` (in the driver's `Info.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/ttyUSB0` on Linux, `COM3` on Windows,
`/dev/tty.usbserial-XXXX` on 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.

## Embedded / bare-metal builds

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: the
`std::thread`-backed `Platform` (`makeDefaultPlatform()`), and
the stderr default logger. Construct `Gripper` with its platform-taking
constructor and a `Platform` implemented over your RTOS.
`ports/threadx/threadx_platform.hpp` is 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 follows `GRIPPERS_HOSTED`)
drops the libserialport-backed `DefaultSerial` and its dependency. Inject
your own `Serial` (e.g. a UART transport) via the
`unique_ptr<Serial>` constructors of `detail::GripperModbusClient` /
`Gripper`.
- **`detail::GripperModbusClient`** is 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 no `Platform`.

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.

## Versioning

[Semantic versioning](https://semver.org) 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.

## License

BSD-3-Clause. Portions derived from PickNik Robotics'
[ros2_robotiq_gripper](https://github.com/PickNikRobotics/ros2_robotiq_gripper)
driver (BSD-3-Clause); original copyright notices are preserved in the
affected files and full history is preserved in git.
21 changes: 21 additions & 0 deletions adaptive-grippers-cpp_versioned_docs/version-stable/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: C++
sidebar_label: C++
---

<div style={{display: 'flex', gap: '1rem', justifyContent: 'left', flexWrap: 'wrap'}}>
<img src="/img/C++-Logo.wine.png" alt="C++ logo" style={{height: '10vh', width: 'auto', maxWidth: '100%'}} />
</div>

![Libraries](https://img.shields.io/badge/Category-Libraries-lightgrey)

![Supported by Robotiq](https://img.shields.io/badge/Supported_by-Robotiq-blue)

import Readme from './_readme.md';

<Readme />

## Source Code

<a className="cta-button" href="https://github.com/robotiq/grippers/tree/v1.0.0"><img src="/img/GitHub_Invertocat_blue.png" alt="" style={{height: '1.9em', width: 'auto', verticalAlign: 'middle'}} /> GitHub Repository</a>

Loading
Loading