diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf3048e..6125090 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -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 diff --git a/.gitignore b/.gitignore index cfcd3cb..46d89ac 100644 --- a/.gitignore +++ b/.gitignore @@ -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 (_versioned_docs/, _versioned_sidebars/, +# _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 diff --git a/adaptive-grippers-cpp_versioned_docs/version-previous-versions/_readme.md b/adaptive-grippers-cpp_versioned_docs/version-previous-versions/_readme.md new file mode 100644 index 0000000..a9c3674 --- /dev/null +++ b/adaptive-grippers-cpp_versioned_docs/version-previous-versions/_readme.md @@ -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. + + diff --git a/adaptive-grippers-cpp_versioned_docs/version-previous-versions/index.mdx b/adaptive-grippers-cpp_versioned_docs/version-previous-versions/index.mdx new file mode 100644 index 0000000..eff45df --- /dev/null +++ b/adaptive-grippers-cpp_versioned_docs/version-previous-versions/index.mdx @@ -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. diff --git a/adaptive-grippers-cpp_versioned_docs/version-stable/_readme.md b/adaptive-grippers-cpp_versioned_docs/version-stable/_readme.md new file mode 100644 index 0000000..f946884 --- /dev/null +++ b/adaptive-grippers-cpp_versioned_docs/version-stable/_readme.md @@ -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 + +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` 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. diff --git a/adaptive-grippers-cpp_versioned_docs/version-stable/index.mdx b/adaptive-grippers-cpp_versioned_docs/version-stable/index.mdx new file mode 100644 index 0000000..929d4eb --- /dev/null +++ b/adaptive-grippers-cpp_versioned_docs/version-stable/index.mdx @@ -0,0 +1,21 @@ +--- +title: C++ +sidebar_label: C++ +--- + +
+ C++ logo +
+ +![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'; + + + +## Source Code + + GitHub Repository + diff --git a/adaptive-grippers-cpp_versioned_sidebars/version-previous-versions-sidebars.json b/adaptive-grippers-cpp_versioned_sidebars/version-previous-versions-sidebars.json new file mode 100644 index 0000000..8505830 --- /dev/null +++ b/adaptive-grippers-cpp_versioned_sidebars/version-previous-versions-sidebars.json @@ -0,0 +1,208 @@ +{ + "adaptiveGrippersCppSidebar": [ + { + "type": "link", + "label": "Overview", + "href": "/docs/intro" + }, + { + "type": "category", + "label": "Adaptive grippers", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "doc", + "id": "index", + "label": "C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Melodic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Melodic" + }, + { + "type": "link", + "label": "ROS1 · Kinetic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Kinetic" + }, + { + "type": "link", + "label": "ROS1 · Indigo", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Indigo" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/Isaac%20Sim" + }, + { + "type": "link", + "label": "PyBullet", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/PyBullet" + }, + { + "type": "link", + "label": "MuJoCo", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/MuJoCo" + } + ] + }, + { + "type": "category", + "label": "Other", + "items": [ + { + "type": "link", + "label": "GraspGen", + "href": "/docs/drivers/Adaptive%20grippers/Other/GraspGen" + } + ] + } + ] + }, + { + "type": "category", + "label": "Tactile Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C++", + "href": "/docs/drivers/Tactile%20Sensor/Libraries/C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Tactile%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Noetic", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS1-Noetic" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Tactile%20Sensor/Simulation/Isaac%20Sim" + } + ] + } + ] + }, + { + "type": "category", + "label": "Force Torque Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/C" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Force%20Torque%20Sensor/ROS/ROS2-Humble" + } + ] + } + ] + }, + { + "type": "category", + "label": "EPick", + "items": [ + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/EPick/ROS/ROS2-Humble" + } + ] + } + ] + } + ] +} diff --git a/adaptive-grippers-cpp_versioned_sidebars/version-stable-sidebars.json b/adaptive-grippers-cpp_versioned_sidebars/version-stable-sidebars.json new file mode 100644 index 0000000..8505830 --- /dev/null +++ b/adaptive-grippers-cpp_versioned_sidebars/version-stable-sidebars.json @@ -0,0 +1,208 @@ +{ + "adaptiveGrippersCppSidebar": [ + { + "type": "link", + "label": "Overview", + "href": "/docs/intro" + }, + { + "type": "category", + "label": "Adaptive grippers", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "doc", + "id": "index", + "label": "C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Melodic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Melodic" + }, + { + "type": "link", + "label": "ROS1 · Kinetic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Kinetic" + }, + { + "type": "link", + "label": "ROS1 · Indigo", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Indigo" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/Isaac%20Sim" + }, + { + "type": "link", + "label": "PyBullet", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/PyBullet" + }, + { + "type": "link", + "label": "MuJoCo", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/MuJoCo" + } + ] + }, + { + "type": "category", + "label": "Other", + "items": [ + { + "type": "link", + "label": "GraspGen", + "href": "/docs/drivers/Adaptive%20grippers/Other/GraspGen" + } + ] + } + ] + }, + { + "type": "category", + "label": "Tactile Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C++", + "href": "/docs/drivers/Tactile%20Sensor/Libraries/C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Tactile%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Noetic", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS1-Noetic" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Tactile%20Sensor/Simulation/Isaac%20Sim" + } + ] + } + ] + }, + { + "type": "category", + "label": "Force Torque Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/C" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Force%20Torque%20Sensor/ROS/ROS2-Humble" + } + ] + } + ] + }, + { + "type": "category", + "label": "EPick", + "items": [ + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/EPick/ROS/ROS2-Humble" + } + ] + } + ] + } + ] +} diff --git a/adaptive-grippers-cpp_versions.json b/adaptive-grippers-cpp_versions.json new file mode 100644 index 0000000..402035e --- /dev/null +++ b/adaptive-grippers-cpp_versions.json @@ -0,0 +1,4 @@ +[ + "stable", + "previous-versions" +] diff --git a/docs/api-stability.mdx b/docs/api-stability.mdx new file mode 100644 index 0000000..946d7f1 --- /dev/null +++ b/docs/api-stability.mdx @@ -0,0 +1,43 @@ +--- +title: API Stability Policy +sidebar_label: API stability policy +--- + +# API stability policy + +This page applies to every Robotiq-maintained software tool on this site +that has its own **Stable / Development (main) / Previous versions** +switcher (see +[Per-tool documentation versioning](/docs/contribute/versioning) for how +that switcher works). + +## Only a tagged release is a commitment + +**Stable** always tracks a tool's newest tagged release. That's the only +version this site makes any compatibility commitment about — once a tag +is cut, the API it documents doesn't change under you. + +**Development (main)** documents whatever is currently on the source +repository's default branch, which can include work in progress: renamed +methods, changed signatures, new required parameters, or symbols removed +entirely, all without notice. Nothing on a Development page is a +commitment, even if it looks finished. If you're integrating against one +of these tools, build against **Stable**, not Development. + +## Versioning scheme + +Each tool tags its own releases independently — there's no single +site-wide version number, since this site aggregates several +independently-released repositories. A tool's tags follow `vMAJOR.MINOR.PATCH` +(semantic versioning): a `MAJOR` bump can break compatibility, `MINOR` +adds functionality without breaking existing callers, and `PATCH` is a +fix with no API change. + +## Previous versions + +Older tagged releases aren't archived on this site — only **Stable** and +**Development (main)** are real, rebuilt versions. A tool's **Previous +versions** page +lists its older tags with a link to that tag's own source, so you can +still read matching documentation (and check out matching code), just not +in place on this site. diff --git a/docs/contribute/adding-a-tool.mdx b/docs/contribute/adding-a-tool.mdx index 7f54c8b..c680bfe 100644 --- a/docs/contribute/adding-a-tool.mdx +++ b/docs/contribute/adding-a-tool.mdx @@ -7,7 +7,18 @@ displayed_sidebar: contributeSidebar # Step-by-step: adding a new software tool The steps below add a Python tool for the **Adaptive grippers** family hosted -at `https://github.com/robotiq/2f85-python-driver`. +at `https://github.com/robotiq/2f85-python-driver`, living under `docs/` +like every non-versioned tool. + +**If the new tool is Robotiq-maintained and submodule-synced** (as opposed +to a third-party or not-yet-available tool), it should usually also get its +own **Stable / Development (main) / Previous versions** switcher, matching every other +Robotiq-maintained tool on this site — see +[Per-tool documentation versioning](./versioning) once the steps below are +done. That changes *where* the tool's files live (`versioned-tools/` +instead of `docs/`) and adds a few extra pieces (its own Docusaurus plugin +instance, its own `sidebars..js`); everything below — the sync job, +the wrapper page, the badges — stays exactly the same either way. ## 1. Add the submodule @@ -284,3 +295,9 @@ sub-category matching where they live on disk: Only include the sub-categories that actually have pages — e.g. Force Torque Sensor has no Simulation or Other tools yet, so its sidebar entry skips those two groups entirely. + +**Adding versioning to this tool afterward** replaces its doc-id string +above (e.g. `'drivers/Adaptive grippers/Libraries/Python/index'`) with a +plain link into its own new plugin instance — see +[Per-tool documentation versioning](./versioning#checklist-adding-versioning-to-a-new-tool) +for the full checklist. diff --git a/docs/contribute/api-reference-cpp.mdx b/docs/contribute/api-reference-cpp.mdx index 6c0396f..6ee2a3a 100644 --- a/docs/contribute/api-reference-cpp.mdx +++ b/docs/contribute/api-reference-cpp.mdx @@ -461,12 +461,19 @@ publish the reference automatically. Doxybook2-based pipeline). Add a job with a `doxygen2docusaurus` field (instead of `from`) to that -submodule's list in `scripts/external-jobs.js`: +submodule's list in `scripts/external-jobs.js`. If the tool is under +[per-tool documentation versioning](./versioning) (every current +doxygen2docusaurus consumer on this site is — see the real `2f85_cpp` job), +it also needs `destRoot: 'versioned-tools'` and `routeBasePath` — see the +comment on both, and on `apiFolderPath`/`docsBaseUrl` below, for why a +`destRoot` job needs `routeBasePath` spelled out explicitly: ```js { doxygen2docusaurus: { doxyfileDir: 'sdk_cpp' }, - to: 'drivers/Adaptive grippers/Libraries/C++/API', + to: 'Adaptive grippers/Libraries/C++/API', + destRoot: 'versioned-tools', + routeBasePath: '/docs/drivers/Adaptive grippers/Libraries/C++', exclude: [ 'files', 'folders', 'indices/files', 'namespaces', 'indices/namespaces', @@ -475,6 +482,10 @@ submodule's list in `scripts/external-jobs.js`: }, ``` +A non-versioned tool's job (no `destRoot`) skips `routeBasePath` entirely +and uses `to: 'drivers/Adaptive grippers/Libraries/C++/API'` instead — +site-relative from `docs/`, the same as any other job's `to`. + `sync-external-docs.js`'s handling of a `doxygen2docusaurus` job first deletes that submodule's `doxygen-xml/` output folder, then runs `doxygen`. **Doxygen never cleans its own `OUTPUT_DIRECTORY` between runs** — an XML @@ -489,18 +500,31 @@ exclusion just sat there, untouched, until something finally deleted the folder. It then writes a `doxygen2docusaurus.json` config next to the repo root -(`apiFolderPath`/`apiBaseUrl` both set to the job's own `to`, so every -generated slug and cross-reference is already correct for where the content -ends up — no post-hoc link rewriting needed) and runs the tool against it, -staged into a gitignored folder rather than straight into `docs/` (doxygen2docusaurus -also wipes its own *output* folder on every run — staging keeps that -separate from the post-processing pipeline below, and lets `pruneStale` -compare "what should exist now" against what's actually in `docs/` before -touching anything there). It then applies the transforms described in +and runs the tool against it, staged into a gitignored folder rather than +straight into `docs/`/`versioned-tools/` (doxygen2docusaurus also wipes its +own *output* folder on every run — staging keeps that separate from the +post-processing pipeline below, and lets `pruneStale` compare "what should +exist now" against what's actually there before touching anything). For a +**non-versioned** job, `apiFolderPath`/`apiBaseUrl` are both just the job's +own `to` (this site's real mount point), so every generated slug and +cross-reference is already correct for where the content ends up — no +post-hoc link rewriting needed. For a **versioned** (`destRoot`) job, +`apiFolderPath` is reduced to just the instance-relative tail (e.g. `'API'`, +via `path.basename(job.to)`) since that's also what drives the sidebar +JSON's own doc ids, which must be instance-relative to match +`sidebars..js`; `docsBaseUrl` is set from the job's own +`routeBasePath` instead of the default `'docs'`, so the absolute +`` backlinks doxygen2docusaurus bakes directly into the +generated HTML still resolve to the versioned instance's real +`routeBasePath`, not the default instance's `/docs`. See the big comment on +`destRoot`/`currentDocsRoot`/`currentRoutePrefix` near the top of +`sync-external-docs.js` for the full mechanics. Either way, it then applies +the transforms described in [Matching Doxygen's own reference look](#matching-doxygens-own-reference-look) -above, copies the result into `docs/${to}/`, builds the [Global -Index](#global-index), and writes the pruned/merged sidebar JSON — all -before `sidebars.js` ever reads it back. +above, copies the result into place, builds the [Global +Index](#global-index), and writes the pruned/merged sidebar JSON to +`scripts/generated/doxygen-sidebar--.json` — all before +`sidebars.js`/`sidebars..js` ever reads it back. **Every page under this job comes from the submodule alone — nothing here is hand-authored on this site**, including the section's own landing page: @@ -515,12 +539,22 @@ to update it (this exact page went stale within days, listing a group that had already been renamed upstream), where the generated page is correct by construction on every run. -Add `'drivers/Adaptive grippers/Libraries/C++/API/index'` (Docusaurus resolves a doc's `id` -from its path, regardless of whether the underlying file is `.md` or `.mdx`) -to the sidebar; everything under it comes from -`loadDoxygenSidebarItems('drivers/Adaptive grippers/Libraries/C++/API')` as described in +Add the API reference's `index` doc (Docusaurus resolves a doc's `id` from +its path, regardless of whether the underlying file is `.md` or `.mdx`) to +the sidebar; everything under it comes from `loadDoxygenSidebarItems(...)` +as described in [The sidebar mirrors the Doxygen group hierarchy automatically](#the-sidebar-mirrors-the-doxygen-group-hierarchy-automatically) -above. +above. For a **non-versioned** tool that's a site-relative doc id in +`sidebars.js`, e.g. `'drivers/Adaptive grippers/Libraries/C++/API/index'`, +passed to `loadDoxygenSidebarItems('drivers/Adaptive grippers/Libraries/C++/API')` +(the same site-relative path as the job's own `to`). For a **versioned** +tool it's instance-relative instead, in that tool's own +`sidebars..js` — e.g. `'API/index'` passed to +`loadDoxygenSidebarItems('API')` (the job's own `to` with `destRoot` +stripped) — see `sidebars.adaptive-grippers-cpp.js`'s `doxygenApiCategory` +call for the real, current example, and +[Per-tool documentation versioning](./versioning) for the rest of what's +different about a versioned tool's sidebar. ## Optional: full-fidelity preview on this site @@ -620,6 +654,11 @@ the **tool repo**, then adding one job entry **here** — nothing in plain `{ from, to }` jobs in the same `submoduleJobs(...)` call — see [Splitting a tool page into overview, API reference, and guides](./how-it-works#splitting-a-tool-page-into-overview-api-reference-and-guides) in "How it works". + If the tool is also getting [per-tool documentation versioning](./versioning) + (the common case — see that page's checklist for the rest of the setup), + `destRoot`/`routeBasePath` go on this job too, per + [Wiring it into this site's build](#wiring-it-into-this-sites-build) + above. 9. Add the new section's sidebar entries — the tool's `index.mdx`, the generated API reference's doc id, and the `docs/` guides' doc id, if present — per the same "Splitting a tool page..." section. diff --git a/docs/contribute/how-it-works.mdx b/docs/contribute/how-it-works.mdx index a884be9..49fdce4 100644 --- a/docs/contribute/how-it-works.mdx +++ b/docs/contribute/how-it-works.mdx @@ -35,23 +35,17 @@ robotiq.github.io/ │ └── drivers/ │ ├── Tactile Sensor/ │ │ ├── index.mdx ← product landing page (in git) -│ │ ├── Libraries/ -│ │ │ ├── C++/ -│ │ │ │ ├── index.mdx ← wrapper page (in git) -│ │ │ │ ├── _readme.md ← synced from submodule -│ │ │ │ ├── API/ ← generated API reference (Doxygen + -│ │ │ │ │ └── index.mdx doxygen2docusaurus), see below -│ │ │ │ └── docs/ ← synced copy of the submodule's own -│ │ │ │ └── index.mdx docs/ folder (guides, design notes) -│ │ │ └── Python/ -│ │ │ ├── index.mdx -│ │ │ └── _readme.md +│ │ ├── Libraries/ ← C++ and Python are VERSIONED tools +│ │ │ (see versioned-tools/ below) — this +│ │ │ folder holds no tool pages, only +│ │ │ the sidebar link to each │ │ ├── ROS/ │ │ │ ├── index.mdx ← ROS landing page (in git), holds │ │ │ │ the per-distro compatibility table │ │ │ └── ROS2-Jazzy/index.mdx │ │ └── Simulation/ -│ │ └── Isaac Sim/index.mdx +│ │ └── Isaac Sim/index.mdx ← NOT versioned (own Isaac Sim page, +│ │ distinct from Adaptive grippers') │ ├── Force Torque Sensor/ │ │ ├── index.mdx │ │ ├── Libraries/C/ … @@ -61,8 +55,9 @@ robotiq.github.io/ │ │ └── ROS2-Humble/ … │ └── Adaptive grippers/ │ ├── index.mdx -│ ├── Libraries/C++/ … -│ ├── Libraries/Python/ … +│ ├── Libraries/ ← C++ is a VERSIONED tool (below); +│ │ Python isn't yet +│ │ └── Python/ … │ ├── ROS/ ← newest to oldest, per generation │ │ ├── index.mdx │ │ ├── ROS2-Lyrical/ … @@ -71,20 +66,55 @@ robotiq.github.io/ │ │ ├── ROS1-Melodic/ … │ │ ├── ROS1-Kinetic/ … │ │ └── ROS1-Indigo/ … -│ ├── Simulation/Isaac Sim/ … │ ├── Simulation/PyBullet/ … │ └── Other/GraspGen/ … ← "Other" category +├── versioned-tools/ ← content for VERSIONED tools (own +│ │ Docusaurus plugin instance each, +│ │ Stable/Development/Previous versions — +│ │ see contribute/versioning.mdx), +│ │ mirrors docs/drivers/'s own shape +│ │ one level in from +│ ├── Tactile Sensor/ +│ │ └── Libraries/ +│ │ ├── C++/ +│ │ │ ├── index.mdx ← wrapper page (in git) +│ │ │ ├── _readme.md ← synced from submodule +│ │ │ ├── API/ ← generated API reference (Doxygen + +│ │ │ │ └── index.mdx doxygen2docusaurus), see below +│ │ │ └── docs/ ← synced copy of the submodule's own +│ │ │ └── index.mdx docs/ folder (guides, design notes) +│ │ └── Python/ +│ │ ├── index.mdx +│ │ └── _readme.md +│ └── Adaptive grippers/ +│ ├── Libraries/C++/ … ← same guides+API shape as above +│ └── Simulation/Isaac Sim/ … ← single page, Development (main) only so far ├── draft/ ← content with no nav link yet (never │ built into a page — outside docs/) ├── scripts/ │ ├── sync-external-docs.js -│ └── generate-tools-table.js -└── sidebars.js +│ ├── generate-tools-table.js +│ └── site-nav-tree.mjs ← shared nav tree, see +│ contribute/versioning.mdx +├── sidebars.js ← main instance's sidebar +└── sidebars..js ← one per versioned tool ``` Files prefixed with `_` are Docusaurus **partials** — imported by a wrapper `.mdx` but not standalone pages themselves. +**A tool listed above as "VERSIONED" lives under `versioned-tools/`, not +`docs/`, and has its own Docusaurus plugin instance with its own +Stable/Development/Previous versions switcher** — see +[Per-tool documentation versioning](./versioning) for the whole mechanism, +why it needs a separate top-level folder rather than living inside +`docs/`, and how to add it to a new tool. Everything else on this page +(the sync script, the exclude-content markers, splitting a tool into +guides/API/overview, refreshing submodule pins) applies identically to a +versioned or non-versioned tool — versioning only changes *where* the +files live and *how many builds* exist of them, not how they're +authored or synced. + The `Libraries/`, `ROS/`, `Simulation/`, and `Other/` folders are just organizational containers — `generate-tools-table.js` doesn't care about folder names, only about which `index.mdx` files carry a `Category` badge @@ -151,7 +181,13 @@ A tool's `_readme.md` partial is meant to stay a short overview. Once a repository also has a generated API reference and its own prose documentation (design notes, guides — more than fits in a README), don't cram all three into one page. Split them into sibling folders next to the -tool's `index.mdx`, as with `docs/drivers/Adaptive grippers/Libraries/C++/`: +tool's `index.mdx`. The example below is written against a plain, +non-versioned tool under `docs/`; the real worked example on this site, +`versioned-tools/Adaptive grippers/Libraries/C++/`, is a *versioned* tool +(see [Per-tool documentation versioning](./versioning)) — same folder +shape, but under `versioned-tools/` instead of `docs/`, and with a +different sidebar entry (`sidebars.adaptive-grippers-cpp.js`'s `cppItem`, +not a `sidebars.js` entry): - **`/index.mdx`** — the manually maintained overview (unchanged); still imports `_readme.md`. No need to link to the two folders below by @@ -168,7 +204,7 @@ tool's `index.mdx`, as with `docs/drivers/Adaptive grippers/Libraries/C++/`: `scripts/external-jobs.js`: ```js - { from: 'docs', to: 'drivers/Adaptive grippers/Libraries/C++/docs' }, + { from: 'docs', to: 'drivers////docs' }, ``` **Name the source repo's guide files `NN-kebab-case.md`** (e.g. @@ -193,11 +229,11 @@ becomes a category instead of a single page: ```js { type: 'category', - label: 'C++', - link: { type: 'doc', id: 'drivers/Adaptive grippers/Libraries/C++/index' }, + label: '', + link: { type: 'doc', id: 'drivers////index' }, items: [ - 'drivers/Adaptive grippers/Libraries/C++/API/index', - 'drivers/Adaptive grippers/Libraries/C++/docs/index', + 'drivers////API/index', + 'drivers////docs/index', ], }, ``` @@ -264,10 +300,11 @@ already covers each half: from the PR head with that same elevated token; this workflow never does — no checkout step at all, just two `gh` API calls — and should stay that way. -3. Both can also be triggered on demand: `dependabot.yml`'s check via - "Dependabot" → "Check for updates" on the repo's Insights → - Dependency graph page, or the auto-merge workflow re-runs automatically - on its own `pull_request_target` trigger for any Dependabot PR. +3. Both the pin check and the merge can also be triggered on demand: + `dependabot.yml`'s check via "Dependabot" → "Check for updates" on the + repo's Insights → Dependency graph page, or the auto-merge workflow + re-runs automatically on its own `pull_request_target` trigger for any + Dependabot PR. ## Manually refreshing the deployed site diff --git a/docs/contribute/index.mdx b/docs/contribute/index.mdx index 4a804e6..1509063 100644 --- a/docs/contribute/index.mdx +++ b/docs/contribute/index.mdx @@ -11,6 +11,7 @@ a pull request, and embedding a software tool repository as a Git submodule with optional auto-generated API docs. It's split by topic — see the sidebar for the rest ([How it works](./how-it-works), [Adding a new software tool](./adding-a-tool), +[Per-tool documentation versioning](./versioning), [Auto-generated software tools tables](./tools-tables), [Auto-generated API reference](./api-reference-cpp), and a [Quick reference](./quick-reference) table). @@ -55,7 +56,9 @@ npm start ``` Opens `http://localhost:3000` in your browser. The page hot-reloads -when you edit files under `docs/`, `src/`, or `sidebars.js`. +when you edit files under `docs/`, `src/`, `sidebars.js`, or, for a +versioned tool (see [Per-tool documentation versioning](./versioning)), +`versioned-tools/`, `sidebars..js`, or `scripts/site-nav-tree.mjs`. ### Previewing local edits to a submodule @@ -107,9 +110,9 @@ pick up new commits — terminal 1's dev server hot-reloads the result, no restart needed. Skip to step 3. ⚠️ **Exception: adding or removing a Doxygen group (`\defgroup`).** The C++ -API sidebar is built dynamically from whatever files currently exist under -`API/Modules/` (see -[`scripts/doxygen-groups-sidebar.mjs`](https://github.com/robotiq/robotiq.github.io/blob/main/scripts/doxygen-groups-sidebar.mjs)), +API sidebar is read once from `scripts/generated/doxygen-sidebar-.json` +(written by `sync-external-docs.js`'s `doxygen2docusaurus` handling — see +[The sidebar mirrors the Doxygen group hierarchy automatically](./api-reference-cpp#the-sidebar-mirrors-the-doxygen-group-hierarchy-automatically)), but that scan only runs once, when the dev server starts — it isn't re-run on every file change the way doc content is. Editing an existing group's content hot-reloads fine, but a *new* or *deleted* group won't show up in diff --git a/docs/contribute/quick-reference.mdx b/docs/contribute/quick-reference.mdx index 32861bb..bf493ab 100644 --- a/docs/contribute/quick-reference.mdx +++ b/docs/contribute/quick-reference.mdx @@ -10,15 +10,17 @@ displayed_sidebar: contributeSidebar |---|---| | Submodules (pinned repo imports) | `external//` | | Sync + generation config | `scripts/external-jobs.js` → `JOBS`, authored via `submoduleJobs(submodule, { repoUrl, branch }, [...])` per submodule | -| C++ API sidebar (auto-built from Doxygen groups) | `scripts/doxygen-groups-sidebar.mjs`, called from `sidebars.js` — mirrors the `\defgroup`/`\ingroup` hierarchy with no manual sidebar entries; see [The sidebar mirrors the Doxygen group hierarchy automatically](./api-reference-cpp#the-sidebar-mirrors-the-doxygen-group-hierarchy-automatically) | +| C++ API sidebar (auto-built from Doxygen groups) | `scripts/generated/doxygen-sidebar--.json`, written by `sync-external-docs.js` and read back by `sidebars.js`/`sidebars..js`'s `loadDoxygenSidebarItems` — mirrors the `\defgroup`/`\ingroup` hierarchy with no manual sidebar entries; see [The sidebar mirrors the Doxygen group hierarchy automatically](./api-reference-cpp#the-sidebar-mirrors-the-doxygen-group-hierarchy-automatically) | +| Per-tool Stable/Development/Previous versions | one `@docusaurus/plugin-content-docs` instance per versioned tool in `docusaurus.config.js`'s `plugins` array, content under `versioned-tools////` (not `docs/`) — see [Per-tool documentation versioning](./versioning) | +| Full site nav tree, shared by the main sidebar and every versioned tool's own sidebar | `scripts/site-nav-tree.mjs` — see [Per-tool documentation versioning](./versioning) | | One-command preview of pushed WIP submodule commits | `npm run preview` in a **second terminal**, run **before** starting terminal 1 with `SKIP_SUBMODULE_RESET=1 npm start` (plain `npm start` resets the submodule via its own `prestart` and undoes the preview) — **not** `npm start preview`, which mis-parses as a `docusaurus start` site-directory argument — see [Previewing local edits to a submodule](./#previewing-local-edits-to-a-submodule) | | Exclude README content from the synced page | `` ... `` in the tool repo's README | -| Synced partials | `docs/drivers////_readme.md` | -| Wrapper pages | `docs/drivers////index.mdx` | -| Generated API reference folder | `docs/drivers////API/` | +| Synced partials | `docs/drivers////_readme.md` — or `versioned-tools////_readme.md` for a versioned tool, see [Per-tool documentation versioning](./versioning) | +| Wrapper pages | `docs/drivers////index.mdx` (`versioned-tools/...` for a versioned tool) | +| Generated API reference folder | `docs/drivers////API/` (`versioned-tools/...` for a versioned tool) | | Compile-checked C++ examples | `\snippet ` in a doc comment, tagged in a real file under `sdk_cpp/examples/` — not a hand-written `\code` block. C++ only; Python has no equivalent. See [Compile-checked examples with `\snippet`](./api-reference-cpp#compile-checked-examples-with-snippet) | | Verified code examples in guide pages (outside Doxygen's reach) | `` above a fenced code block; checked by `check_doc_snippets.py` in both the tool repo's own CI and here via `scripts/check-doc-snippets.js` + `docSnippetsCheck` in `external-jobs.js`. Template at `templates/check_doc_snippets.py`. See [Verifying markdown code examples against real source](./api-reference-cpp#verifying-markdown-code-examples-against-real-source) | -| Synced guide docs folder (verbatim copy of the repo's own `docs/`) | `docs/drivers////docs/` | +| Synced guide docs folder (verbatim copy of the repo's own `docs/`) | `docs/drivers////docs/` (`versioned-tools/...` for a versioned tool) | | Category badge (Libraries / ROS1 / ROS2 / Simulation / Other) | `![...](.../Category--lightgrey)` at the top of each wrapper page | | Support badge | `![Supported by ` — `src/remark/externalLinksNewTab.mjs`; no `target="_blank"` needed by hand | | A YouTube video embed | write a thumbnail image wrapped in a link to the same video (`[![Title](https://img.youtube.com/vi//...)](https://youtu.be/)`) — GitHub-safe, and auto-upgraded to a real player by `src/remark/youtubeEmbed.mjs`; no raw ``, - }; -} - function mdxAttr(name, value) { return {type: 'mdxJsxAttribute', name, value}; } -function buildMdxNode(thumbnailId, title) { +export function buildEmbedNode(thumbnailId, title) { return { type: 'mdxJsxFlowElement', name: 'div', @@ -83,27 +71,32 @@ function buildMdxNode(thumbnailId, title) { }; } -export default function remarkYoutubeEmbed() { - return (tree, file) => { - const isMdx = file?.extname === '.mdx'; +// Extracts { thumbnailId } if `node` is a paragraph matching the pattern, +// else undefined. Exported as a pure function so the matching logic can be +// unit tested without going through a full remark/unified pipeline. +export function matchYoutubeParagraph(node) { + if (!node || node.type !== 'paragraph' || node.children.length !== 1) return undefined; - visit(tree, 'paragraph', (node, index, parent) => { - if (!parent || index === null || node.children.length !== 1) return; + const link = node.children[0]; + if (link.type !== 'link' || link.children.length !== 1) return undefined; - const link = node.children[0]; - if (link.type !== 'link' || link.children.length !== 1) return; + const image = link.children[0]; + if (image.type !== 'image') return undefined; - const image = link.children[0]; - if (image.type !== 'image') return; + const thumbnailId = THUMBNAIL_RE.exec(image.url)?.[1]; + const linkId = VIDEO_LINK_RE.exec(link.url)?.[1]; + if (!thumbnailId || !linkId || thumbnailId !== linkId) return undefined; - const thumbnailId = THUMBNAIL_RE.exec(image.url)?.[1]; - const linkId = VIDEO_LINK_RE.exec(link.url)?.[1]; - if (!thumbnailId || !linkId || thumbnailId !== linkId) return; + return {thumbnailId, title: image.alt || 'Video'}; +} - const title = image.alt || 'Video'; - parent.children[index] = isMdx - ? buildMdxNode(thumbnailId, title) - : buildHtmlNode(thumbnailId, title); +export default function remarkYoutubeEmbed() { + return (tree) => { + visit(tree, 'paragraph', (node, index, parent) => { + if (!parent || index === null) return; + const match = matchYoutubeParagraph(node); + if (!match) return; + parent.children[index] = buildEmbedNode(match.thumbnailId, match.title); }); }; } diff --git a/src/theme/DocVersionBanner/index.jsx b/src/theme/DocVersionBanner/index.jsx new file mode 100644 index 0000000..5a8f930 --- /dev/null +++ b/src/theme/DocVersionBanner/index.jsx @@ -0,0 +1,64 @@ +import React from 'react'; +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import { + useActivePlugin, + useDocVersionSuggestions, + useDocsPreferredVersion, + useDocsVersion, +} from '@docusaurus/plugin-content-docs/client'; +import {ThemeClassNames} from '@docusaurus/theme-common'; + +// Swizzled from @docusaurus/theme-classic's own DocVersionBanner, which +// hardcodes two stacked paragraphs ("This is unreleased documentation for +// ... version." then, on its own line, "For up-to-date documentation, see +// the latest version (...).") — condensed to one line/one sentence here. +// +// The 'unreleased' text also states this site's actual API-stability +// policy, not just "unreleased" — Development (main) documents +// in-progress work, and a reader landing here (e.g. from a search result, +// before `noIndex` on that version took effect everywhere it's indexed) +// needs to know its APIs aren't a commitment, not just that it's "not the +// latest" — see docs/api-stability.mdx for the full policy this links to. +function BannerText({banner}) { + if (banner === 'unreleased') { + return ( + <> + Experimental: documents unreleased main. APIs may + change or be removed without notice and aren't covered by our{' '} + compatibility commitment. Use + + ); + } + return <>No longer maintained — for the latest release, see; +} + +function DocVersionBannerEnabled({className, versionMetadata}) { + const {pluginId} = useActivePlugin({failfast: true}); + const {savePreferredVersionName} = useDocsPreferredVersion(pluginId); + const {latestDocSuggestion, latestVersionSuggestion} = useDocVersionSuggestions(pluginId); + const getVersionMainDoc = (version) => version.docs.find((doc) => doc.id === version.mainDocId); + const latestVersionSuggestedDoc = latestDocSuggestion ?? getVersionMainDoc(latestVersionSuggestion); + + return ( +
+ {' '} + + savePreferredVersionName(latestVersionSuggestion.name)}> + {latestVersionSuggestion.label} + + + . +
+ ); +} + +export default function DocVersionBanner({className}) { + const versionMetadata = useDocsVersion(); + if (!versionMetadata.banner) return null; + return ; +} diff --git a/src/theme/NavbarItem/ComponentTypes.js b/src/theme/NavbarItem/ComponentTypes.js new file mode 100644 index 0000000..2644f8a --- /dev/null +++ b/src/theme/NavbarItem/ComponentTypes.js @@ -0,0 +1,10 @@ +import ComponentTypes from '@theme-original/NavbarItem/ComponentTypes'; +import ScopedDocsVersionDropdown from './ScopedDocsVersionDropdown'; + +// Adds one custom navbar item type on top of the full stock set (re-used +// via @theme-original, not hand-copied) — see ScopedDocsVersionDropdown for +// why the stock 'docsVersionDropdown' type can't be used directly here. +export default { + ...ComponentTypes, + 'custom-scopedVersionDropdown': ScopedDocsVersionDropdown, +}; diff --git a/src/theme/NavbarItem/ScopedDocsVersionDropdown.jsx b/src/theme/NavbarItem/ScopedDocsVersionDropdown.jsx new file mode 100644 index 0000000..c5a56e6 --- /dev/null +++ b/src/theme/NavbarItem/ScopedDocsVersionDropdown.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import {useActivePlugin} from '@docusaurus/plugin-content-docs/client'; +import DocsVersionDropdownNavbarItem from '@theme/NavbarItem/DocsVersionDropdownNavbarItem'; + +// The stock `docsVersionDropdown` navbar item (see +// DocsVersionDropdownNavbarItem.tsx in @docusaurus/theme-classic) always +// renders, everywhere on the site — when the current page isn't part of its +// own docsPluginId, it just falls back to a link at that instance's +// `lastVersion`, rather than disappearing. That's the opposite of what's +// needed here: per docs/contribute/versioning.mdx, the Stable/Development +// (main)/Previous versions switcher must only appear on the pages that actually +// belong to a Robotiq-maintained, submodule-synced tool's own versioned +// instance (e.g. 'tactile-python'), not site-wide on every other page +// (product pages, third-party tools, docs/contribute/...). +// +// This wraps the stock component and only renders it while +// useActivePlugin — which resolves the *current* URL to whichever docs +// plugin instance actually owns it — matches this item's own +// `docsPluginId`. `failfast: false` so a page that isn't a docs page at +// all (or belongs to some other docs instance) resolves to `undefined` +// instead of throwing. +export default function ScopedDocsVersionDropdown(props) { + const activePlugin = useActivePlugin({failfast: false}); + if (activePlugin?.pluginId !== props.docsPluginId) { + return null; + } + // Registering a custom navbar item type skips the prop-schema + // normalization Docusaurus applies to its own built-in types — without + // these, DocsVersionDropdownNavbarItem throws ("dropdownItemsBefore is + // not iterable") since it assumes they're always at least []. + return ( + + ); +} diff --git a/tactile-cpp_versioned_docs/version-previous-versions/_readme.md b/tactile-cpp_versioned_docs/version-previous-versions/_readme.md new file mode 100644 index 0000000..00d5965 --- /dev/null +++ b/tactile-cpp_versioned_docs/version-previous-versions/_readme.md @@ -0,0 +1,304 @@ +A lightweight, cross-platform C++ SDK for interfacing with Robotiq tactile sensors via USB. This SDK provides a simple, threaded API for collecting high-frequency (1KHz) sensor data from tactile arrays, IMU sensors. + +Contains +- Quick_start.cpp script for displaying sensor output in terminal +- setup_and_run.sh script for building sdk, setting permissions, applying udev rules (needs utils folder) +- test_data_flow.cpp script for checking data rate +- test_packat_analysis.cpp checks that logic to validate data is working + + +## Requirements + +### Build Dependencies + +- **C++11 Compiler**: GCC 4.8+, Clang 3.4+, or MSVC 2015+ +- **CMake**: 3.10 or higher +- **libserialport**: Cross-platform serial port library + +### Runtime Dependencies + +- **libserialport**: Must be installed on the system +- **USB Permissions** (Linux only): User must have access to serial ports + +## Installation + +run +```bash +bash setup_and_run.sh +``` + +this script: +- builds the SDK, +- finds and installs the correct dependancies +- sets the udev rules and sensor permissions +- launches Quick_start.cpp which connects to the sensors and visulizes the ouput in the terminal . + +launch just quick start with + +``` + ./build/Quick_start /dev/rq_tsf85_0 +``` + + +The `Quick_start` example displays real-time sensor data with ASCII visualization of the tactile pressure arrays. There is a logic in the script that waits for a +DISPLAY_UPDATE_INTERVAL_MS (currently set to 16ms) to ensure a nice smooth visulization. THis can be tuned. The packets are still sent and recieved at 1000hz. + +### Example Output + +``` +======================================== + Robotiq Tactile Sensor Quick Start +======================================== +Timestamp: 15234 ms + +--- Finger 0 --- + + +Static Tactile (7x4 pressure grid, baseline-subtracted): + -24 4 2 1 + -9 0 4 0 + -3 -8 -6 -1 + 4 8 3 5 + -12 1 16 18 + -2 14 2 -5 + 15 13 -3 6 + +Dynamic Tactile: 4 + +Accelerometer: X=44, Y=16244, Z=728 +Gyroscope: X=-127, Y=111, Z=-98 +Timestamp: 27678 +``` + + +## API Reference + +### RobotiqTactileSensor Class + +```cpp +class RobotiqTactileSensor +{ +public: + // Constructor: Opens port and starts data collection + RobotiqTactileSensor(const char* portName, + void (*dataCallback)(const Fingers&), + unsigned int period_ms = 1); + + // Destructor: Stops thread and closes port + ~RobotiqTactileSensor(); + + // Check if port opened successfully + bool isConnected() const; + + // Get last error message + const char* getLastError() const; + + // Get current data rate (bytes/sec) + uint64_t getDataRate() const; + + // Manually stop/start data collection + void stop(); + void start(); +}; +``` + +### Data Structures + +```cpp +struct FingerData +{ + uint16_t staticTactile[28]; // 4x7 pressure array (row-major) + int16_t dynamicTactile[1]; // Change in pressure + int16_t accelerometer[3]; // X, Y, Z (raw ADC values) + int16_t gyroscope[3]; // X, Y, Z (raw ADC values) + int16_t magnetometer[3]; // X, Y, Z (raw ADC values) + int16_t freebyte; // unassigned byte (raw ADC value) + uint16_t baseline[FINGER_STATIC_TACTILE_COUNT]; //place to save a baseline value to bias the sensors + +}; + +struct Fingers +{ + int64_t timestamp; // Milliseconds since start + FingerData finger[2]; // 2 fingers +}; +``` + + +### Static Tactile Array Access + +The `staticTactile` array is stored in row-major order: + +```cpp +// Access element at row r, column c (0-indexed) +int index = row * FINGER_STATIC_TACTILE_COL + col; +uint16_t pressure = data.finger[f].staticTactile[index]; + +// Example: Access top-left corner +uint16_t topLeft = data.finger[0].staticTactile[0]; + +// Example: Access bottom-right corner +uint16_t bottomRight = data.finger[0].staticTactile[27]; +``` + + + +## Troubleshooting + +### refresh rate + +the signal should be sent and recieved at 1000hz. run +```bash +./build/test_data_flow +``` +to see if samples are being read at 1ms + +### Example Output + +``` +======================================== + Data Flow Test +======================================== +Connecting to: /dev/rq_tsf85_0 + +Connected! Monitoring data flow... +Press Ctrl+C to exit + +Received 100 samples. Last timestamp: 100 ms +Received 200 samples. Last timestamp: 200 ms +Received 300 samples. Last timestamp: 300 ms +Received 400 samples. Last timestamp: 400 ms +``` + +Next, you can check that the dynamic data is being read correctly. A packt is considered "valid" if the dynamic data byte has a valudvalue in it. if you are losing packets it could be because this value is not being read correctly. check + +```bash +./build/test_packet_analysis +``` + +### Example Output + +``` +======================================== + Packet Analysis Test +======================================== +This will show how many packets contain dynamic tactile data + +Connected! Analyzing packets... +Let this run for 5-10 seconds... + +Packets: 100, With dynamic: 100 (100%) +Packets: 200, With dynamic: 200 (100%) +Packets: 300, With dynamic: 300 (100%) +``` + +if you are losing reresh rate, but all packets recieved are valid, there could be issues with your sensors, cables, or usb permissions. Try running the TactileSensor UI or the SensorQuickstart python scripts and see if the problem persists. + +### Linux: Permission Denied + +Add user to `dialout` group for serial port access: + +```bash +sudo usermod -a -G dialout $USER +# Log out and log back in for changes to take effect +``` + +Aplly `udev` rules in the utils folder: + +```bash +# Create udev rule +./apply_udev_rule.sh + +#find sensor +./find_sensor_devices.sh + +#give permissions +./set_sesnor_permissions.sh +``` + +### Connection Fails + +1. **Verify port name**: Use `ls /dev/ttyACM*` (Linux), or after udev rule look for `/dev/rq_tsf85_*` +2. **Check cable**: Ensure USB cable is properly connected +3. **Test with other software**: Verify sensor works with original UI or the SensorQuickstart python script +4. **Check baud rate**: SDK uses 115200 baud (hardware default) + +### No Data Received + +1. **Increase timeout**: Modify `sp_wait()` timeout in `threadLoop()` +2. **Check CRC errors**: CRC mismatches indicate communication issues +3. **Verify power**: Ensure sensor has adequate USB power +4. **Test with lower rate**: Try `period_ms = 10` instead of `1` + +### Build Errors + +**"libserialport not found"**: +```bash +# Ubuntu/Debian +sudo apt-get install libserialport-dev + +``` + +**"undefined reference to pthread"**: +```bash +# Add -lpthread to linker flags +g++ ... -lpthread +``` + +## Performance Notes + +- **Default Rate**: 1ms period = 1000 samples/second +- **Typical Data Rate**: 9000-10000 bytes/second at 1ms period +- **CPU Usage**: ~2-5% on modern systems (background thread) +- **Latency**: Sub-millisecond from sensor to callback + +## Architecture + +The SDK uses a non blocking threaded architecture: + +``` +User Application Thread Background USB Thread +------------------- --------------------- + +RobotiqTactileSensor() ------> Start thread + Configure serial port + Send autosend command + + Loop: + Wait for USB data + Parse packets +Your callback() <------- Call callback() + +~RobotiqTactileSensor() ------> Stop autosend + Join thread + Close port +``` + + + +**BSD 3-Clause License** + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the BSD 3-Clause conditions are met. + +See [LICENSE](https://github.com/robotiq/tactile_sensors/tree/main/LICENSE) for details. + +## Credits + +- **Original UI Implementation**: Shahbaz Youssefi (2016) +- **C++ SDK Adaptation**: 2026 +- **USB Protocol**: Based on Robotiq tactile sensor hardware specification + +## Support + +For issues, questions, or contributions: + +1. Check this README and troubleshooting section +2. Review the `Quick_start.cpp` example +3. Examine the original tactile_sensor_ui implementation +4. Open an issue in the project repository + +## References + +- [libserialport Documentation](https://sigrok.org/wiki/Libserialport) +- [Robotiq Sensor Documentation](https://robotiq.com/) +- Original tactile_sensor_ui: `../tactile_sensor_ui/` diff --git a/tactile-cpp_versioned_docs/version-previous-versions/index.mdx b/tactile-cpp_versioned_docs/version-previous-versions/index.mdx new file mode 100644 index 0000000..1b8be6e --- /dev/null +++ b/tactile-cpp_versioned_docs/version-previous-versions/index.mdx @@ -0,0 +1,12 @@ +--- +title: C++ +sidebar_label: C++ +--- + +**Stable** currently tracks `tactile_sensors`'s newest release, **v2.0.0**. + +We only host **Development (main)** and **Stable** here — older releases aren't +archived on this site. Browse their own tag in the source repository +instead: + +- **v1.0.0** — [browse source](https://github.com/Robotiq/tactile_sensors/tree/v1.0.0) diff --git a/tactile-cpp_versioned_docs/version-stable/_readme.md b/tactile-cpp_versioned_docs/version-stable/_readme.md new file mode 100644 index 0000000..ae92b7c --- /dev/null +++ b/tactile-cpp_versioned_docs/version-stable/_readme.md @@ -0,0 +1,304 @@ +A lightweight, cross-platform C++ SDK for interfacing with Robotiq tactile sensors via USB. This SDK provides a simple, threaded API for collecting high-frequency (1KHz) sensor data from tactile arrays, IMU sensors. + +Contains +- Quick_start.cpp script for displaying sensor output in terminal +- setup_and_run.sh script for building sdk, setting permissions, applying udev rules (needs utils folder) +- test_data_flow.cpp script for checking data rate +- test_packat_analysis.cpp checks that logic to validate data is working + + +## Requirements + +### Build Dependencies + +- **C++11 Compiler**: GCC 4.8+, Clang 3.4+, or MSVC 2015+ +- **CMake**: 3.10 or higher +- **libserialport**: Cross-platform serial port library + +### Runtime Dependencies + +- **libserialport**: Must be installed on the system +- **USB Permissions** (Linux only): User must have access to serial ports + +## Installation + +run +```bash +bash setup_and_run.sh +``` + +this script: +- builds the SDK, +- finds and installs the correct dependancies +- sets the udev rules and sensor permissions +- launches Quick_start.cpp which connects to the sensors and visulizes the ouput in the terminal . + +launch just quick start with + +``` + ./build/Quick_start /dev/rq_tsf85_0 +``` + + +The `Quick_start` example displays real-time sensor data with ASCII visualization of the tactile pressure arrays. There is a logic in the script that waits for a +DISPLAY_UPDATE_INTERVAL_MS (currently set to 16ms) to ensure a nice smooth visulization. THis can be tuned. The packets are still sent and recieved at 1000hz. + +### Example Output + +``` +======================================== + Robotiq Tactile Sensor Quick Start +======================================== +Timestamp: 15234 ms + +--- Finger 0 --- + + +Static Tactile (7x4 pressure grid, baseline-subtracted): + -24 4 2 1 + -9 0 4 0 + -3 -8 -6 -1 + 4 8 3 5 + -12 1 16 18 + -2 14 2 -5 + 15 13 -3 6 + +Dynamic Tactile: 4 + +Accelerometer: X=44, Y=16244, Z=728 +Gyroscope: X=-127, Y=111, Z=-98 +Timestamp: 27678 +``` + + +## API Reference + +### RobotiqTactileSensor Class + +```cpp +class RobotiqTactileSensor +{ +public: + // Constructor: Opens port and starts data collection + RobotiqTactileSensor(const char* portName, + void (*dataCallback)(const Fingers&), + unsigned int period_ms = 1); + + // Destructor: Stops thread and closes port + ~RobotiqTactileSensor(); + + // Check if port opened successfully + bool isConnected() const; + + // Get last error message + const char* getLastError() const; + + // Get current data rate (bytes/sec) + uint64_t getDataRate() const; + + // Manually stop/start data collection + void stop(); + void start(); +}; +``` + +### Data Structures + +```cpp +struct FingerData +{ + uint16_t staticTactile[28]; // 4x7 pressure array (row-major) + int16_t dynamicTactile[1]; // Change in pressure + int16_t accelerometer[3]; // X, Y, Z (raw ADC values) + int16_t gyroscope[3]; // X, Y, Z (raw ADC values) + int16_t magnetometer[3]; // X, Y, Z (raw ADC values) + int16_t freebyte; // unassigned byte (raw ADC value) + uint16_t baseline[FINGER_STATIC_TACTILE_COUNT]; //place to save a baseline value to bias the sensors + +}; + +struct Fingers +{ + int64_t timestamp; // Milliseconds since start + FingerData finger[2]; // 2 fingers +}; +``` + + +### Static Tactile Array Access + +The `staticTactile` array is stored in row-major order: + +```cpp +// Access element at row r, column c (0-indexed) +int index = row * FINGER_STATIC_TACTILE_COL + col; +uint16_t pressure = data.finger[f].staticTactile[index]; + +// Example: Access top-left corner +uint16_t topLeft = data.finger[0].staticTactile[0]; + +// Example: Access bottom-right corner +uint16_t bottomRight = data.finger[0].staticTactile[27]; +``` + + + +## Troubleshooting + +### refresh rate + +the signal should be sent and recieved at 1000hz. run +```bash +./build/test_data_flow +``` +to see if samples are being read at 1ms + +### Example Output + +``` +======================================== + Data Flow Test +======================================== +Connecting to: /dev/rq_tsf85_0 + +Connected! Monitoring data flow... +Press Ctrl+C to exit + +Received 100 samples. Last timestamp: 100 ms +Received 200 samples. Last timestamp: 200 ms +Received 300 samples. Last timestamp: 300 ms +Received 400 samples. Last timestamp: 400 ms +``` + +Next, you can check that the dynamic data is being read correctly. A packt is considered "valid" if the dynamic data byte has a valudvalue in it. if you are losing packets it could be because this value is not being read correctly. check + +```bash +./build/test_packet_analysis +``` + +### Example Output + +``` +======================================== + Packet Analysis Test +======================================== +This will show how many packets contain dynamic tactile data + +Connected! Analyzing packets... +Let this run for 5-10 seconds... + +Packets: 100, With dynamic: 100 (100%) +Packets: 200, With dynamic: 200 (100%) +Packets: 300, With dynamic: 300 (100%) +``` + +if you are losing reresh rate, but all packets recieved are valid, there could be issues with your sensors, cables, or usb permissions. Try running the TactileSensor UI or the SensorQuickstart python scripts and see if the problem persists. + +### Linux: Permission Denied + +Add user to `dialout` group for serial port access: + +```bash +sudo usermod -a -G dialout $USER +# Log out and log back in for changes to take effect +``` + +Aplly `udev` rules in the utils folder: + +```bash +# Create udev rule +./apply_udev_rule.sh + +#find sensor +./find_sensor_devices.sh + +#give permissions +./set_sesnor_permissions.sh +``` + +### Connection Fails + +1. **Verify port name**: Use `ls /dev/ttyACM*` (Linux), or after udev rule look for `/dev/rq_tsf85_*` +2. **Check cable**: Ensure USB cable is properly connected +3. **Test with other software**: Verify sensor works with original UI or the SensorQuickstart python script +4. **Check baud rate**: SDK uses 115200 baud (hardware default) + +### No Data Received + +1. **Increase timeout**: Modify `sp_wait()` timeout in `threadLoop()` +2. **Check CRC errors**: CRC mismatches indicate communication issues +3. **Verify power**: Ensure sensor has adequate USB power +4. **Test with lower rate**: Try `period_ms = 10` instead of `1` + +### Build Errors + +**"libserialport not found"**: +```bash +# Ubuntu/Debian +sudo apt-get install libserialport-dev + +``` + +**"undefined reference to pthread"**: +```bash +# Add -lpthread to linker flags +g++ ... -lpthread +``` + +## Performance Notes + +- **Default Rate**: 1ms period = 1000 samples/second +- **Typical Data Rate**: 9000-10000 bytes/second at 1ms period +- **CPU Usage**: ~2-5% on modern systems (background thread) +- **Latency**: Sub-millisecond from sensor to callback + +## Architecture + +The SDK uses a non blocking threaded architecture: + +``` +User Application Thread Background USB Thread +------------------- --------------------- + +RobotiqTactileSensor() ------> Start thread + Configure serial port + Send autosend command + + Loop: + Wait for USB data + Parse packets +Your callback() <------- Call callback() + +~RobotiqTactileSensor() ------> Stop autosend + Join thread + Close port +``` + + + +**GNU General Public License v3.0** + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +See [LICENSE](https://github.com/robotiq/tactile_sensors/tree/v2.0.0/LICENSE) or [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/) for details. + +## Credits + +- **Original UI Implementation**: Shahbaz Youssefi (2016) +- **C++ SDK Adaptation**: 2026 +- **USB Protocol**: Based on Robotiq tactile sensor hardware specification + +## Support + +For issues, questions, or contributions: + +1. Check this README and troubleshooting section +2. Review the `Quick_start.cpp` example +3. Examine the original tactile_sensor_ui implementation +4. Open an issue in the project repository + +## References + +- [libserialport Documentation](https://sigrok.org/wiki/Libserialport) +- [Robotiq Sensor Documentation](https://robotiq.com/) +- Original tactile_sensor_ui: `../tactile_sensor_ui/` diff --git a/tactile-cpp_versioned_docs/version-stable/index.mdx b/tactile-cpp_versioned_docs/version-stable/index.mdx new file mode 100644 index 0000000..84542a3 --- /dev/null +++ b/tactile-cpp_versioned_docs/version-stable/index.mdx @@ -0,0 +1,25 @@ +--- +title: C++ +sidebar_label: C++ +--- + +
+ C++ logo +
+ +![Libraries](https://img.shields.io/badge/Category-Libraries-lightgrey) + +![Supported by Robotiq](https://img.shields.io/badge/Supported_by-Robotiq-blue) + +The source files of the TSF C++ driver, developed and maintained by the +Robotiq team, are available in the following repository: + +https://github.com/robotiq/tactile_sensors/tree/v2.0.0/sdk_cpp + +Below are the related instructions to use this driver. + +## Intro + +import Readme from './_readme.md'; + + \ No newline at end of file diff --git a/tactile-cpp_versioned_sidebars/version-previous-versions-sidebars.json b/tactile-cpp_versioned_sidebars/version-previous-versions-sidebars.json new file mode 100644 index 0000000..9120e79 --- /dev/null +++ b/tactile-cpp_versioned_sidebars/version-previous-versions-sidebars.json @@ -0,0 +1,208 @@ +{ + "tactileCppSidebar": [ + { + "type": "link", + "label": "Overview", + "href": "/docs/intro" + }, + { + "type": "category", + "label": "Adaptive grippers", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C++", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Melodic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Melodic" + }, + { + "type": "link", + "label": "ROS1 · Kinetic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Kinetic" + }, + { + "type": "link", + "label": "ROS1 · Indigo", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Indigo" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/Isaac%20Sim" + }, + { + "type": "link", + "label": "PyBullet", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/PyBullet" + }, + { + "type": "link", + "label": "MuJoCo", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/MuJoCo" + } + ] + }, + { + "type": "category", + "label": "Other", + "items": [ + { + "type": "link", + "label": "GraspGen", + "href": "/docs/drivers/Adaptive%20grippers/Other/GraspGen" + } + ] + } + ] + }, + { + "type": "category", + "label": "Tactile Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "doc", + "id": "index", + "label": "C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Tactile%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Noetic", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS1-Noetic" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Tactile%20Sensor/Simulation/Isaac%20Sim" + } + ] + } + ] + }, + { + "type": "category", + "label": "Force Torque Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/C" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Force%20Torque%20Sensor/ROS/ROS2-Humble" + } + ] + } + ] + }, + { + "type": "category", + "label": "EPick", + "items": [ + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/EPick/ROS/ROS2-Humble" + } + ] + } + ] + } + ] +} diff --git a/tactile-cpp_versioned_sidebars/version-stable-sidebars.json b/tactile-cpp_versioned_sidebars/version-stable-sidebars.json new file mode 100644 index 0000000..9120e79 --- /dev/null +++ b/tactile-cpp_versioned_sidebars/version-stable-sidebars.json @@ -0,0 +1,208 @@ +{ + "tactileCppSidebar": [ + { + "type": "link", + "label": "Overview", + "href": "/docs/intro" + }, + { + "type": "category", + "label": "Adaptive grippers", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C++", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Melodic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Melodic" + }, + { + "type": "link", + "label": "ROS1 · Kinetic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Kinetic" + }, + { + "type": "link", + "label": "ROS1 · Indigo", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Indigo" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/Isaac%20Sim" + }, + { + "type": "link", + "label": "PyBullet", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/PyBullet" + }, + { + "type": "link", + "label": "MuJoCo", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/MuJoCo" + } + ] + }, + { + "type": "category", + "label": "Other", + "items": [ + { + "type": "link", + "label": "GraspGen", + "href": "/docs/drivers/Adaptive%20grippers/Other/GraspGen" + } + ] + } + ] + }, + { + "type": "category", + "label": "Tactile Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "doc", + "id": "index", + "label": "C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Tactile%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Noetic", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS1-Noetic" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Tactile%20Sensor/Simulation/Isaac%20Sim" + } + ] + } + ] + }, + { + "type": "category", + "label": "Force Torque Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/C" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Force%20Torque%20Sensor/ROS/ROS2-Humble" + } + ] + } + ] + }, + { + "type": "category", + "label": "EPick", + "items": [ + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/EPick/ROS/ROS2-Humble" + } + ] + } + ] + } + ] +} diff --git a/tactile-cpp_versions.json b/tactile-cpp_versions.json new file mode 100644 index 0000000..37cafbd --- /dev/null +++ b/tactile-cpp_versions.json @@ -0,0 +1 @@ +["stable","previous-versions"] diff --git a/tactile-python_versioned_docs/version-previous-versions/_readme.md b/tactile-python_versioned_docs/version-previous-versions/_readme.md new file mode 100644 index 0000000..b79f9f8 --- /dev/null +++ b/tactile-python_versioned_docs/version-previous-versions/_readme.md @@ -0,0 +1,170 @@ +Lightweight cross-platform tool to test TSF-85 connections. + +## Quick Start — Terminal + +### Linux +```bash +cd sensor_quickstart +./run_quick_connect.sh +``` + +### Windows +```batch +cd sensor_quickstart +run_quick_connect.bat +``` + +That's it! The script handles everything automatically. + +--- + +## Quick Start — Web Viewer + +A browser-based dashboard with real-time heatmaps, dynamic time-series with FFT, and IMU plots. + +### Linux +```bash +cd sensor_quickstart +./run_web_viewer.sh +``` + +### Windows +```batch +cd sensor_quickstart +run_web_viewer.bat +``` + +The script sets up the environment, connects to the sensor, and opens your browser to `http://localhost:8080`. The dashboard has three tabs: + +- **Static** — live tactile heatmap (7x4 grid per finger) with baseline subtraction +- **Dynamic** — dynamic tactile time-series and FFT spectrum +- **IMU** — accelerometer and gyroscope plots + +The server shuts down automatically when you close the browser tab. + +--- + +## Requirements + +- **Python 3.7+**: [Download Python](https://www.python.org/downloads/) + - ✅ Check "Add Python to PATH" during installation + - ✅ After installing, restart your terminal/command prompt +- **pyserial**: Installed automatically by the script + +--- + +## What It Does + +1. Checks for Python installation +2. Creates virtual environment (`.venvSimpleCheck`) +3. Installs dependencies +4. Detects sensor +5. Displays real-time sensor data + +--- + +## Expected Output + +``` +================================================================================ + Robotiq Tactile Sensor Monitor +================================================================================ +Data Rate: 160.234 KB/s | Refresh Rate: 1000.1 Hz | Total Packets: 15234 + +FINGER 0 +-------------------------------------------------------------------------------- + Static Tactile (7 rows × 4 columns): + 0 1 2 3 + 4 5 6 7 + 8 9 10 11 + 12 13 14 15 + 16 17 18 19 + 20 21 22 23 + 24 25 26 27 + + Dynamic Tactile: 123 + + Accelerometer: X= 12 Y= -45 Z= 1024 + Gyroscope: X= 3 Y= -2 Z= 1 + Magnetometer: X= -15 Y= 23 Z= -87 + + Open Byte: 0 + +FINGER 1 +-------------------------------------------------------------------------------- + [... same format ...] + +================================================================================ +Press Ctrl+C to exit +``` + +--- + +## Troubleshooting + +### Sensor Not Found + +**Linux:** +- Check USB connection +- Verify sensor is plugged in +- Try different USB port + +**Windows:** +- Check Device Manager (Win+X → Device Manager → Ports) +- Sensor appears as "USB Serial Device" or "Cypress USB UART" +- Try different USB port +- **VM users**: USB passthrough may not work reliably for serial devices + +### No Data Displayed + +- Unplug and replug the sensor +- Close terminal and rerun script +- Sensor may need to be reset + +### Python Not Found (Windows) + +- Install Python from [python.org](https://www.python.org/downloads/) +- Must check "Add Python to PATH" during installation +- Restart command prompt after installing + +--- + +## Learn More + +- **Python**: [python.org](https://www.python.org/) +- **Virtual Environments**: [Python venv documentation](https://docs.python.org/3/library/venv.html) +- **pyserial**: [pyserial documentation](https://pyserial.readthedocs.io/) + +--- + +## Sensor Details + +- **Baud rate**: 115200 +- **Format**: 8N1 (8 data bits, no parity, 1 stop bit) +- **USB VID:PID**: `16d0:14cc` (Robotiq) or `04b4:f232` (Cypress, older units) +- **Data**: 28 tactile sensors per finger (7×4 grid) + IMU + dynamic sensor + +--- + +## File Structure + +``` +sensor_quickstart/ +├── quick_connect.py # Terminal-based sensor monitor +├── web_viewer.py # Web-based visualization server +├── protocol.py # USB protocol implementation +├── requirements.txt # Dependencies (pyserial, websockets) +├── run_quick_connect.sh # Linux launcher (terminal) +├── run_quick_connect.bat # Windows launcher (terminal) +├── run_web_viewer.sh # Linux launcher (web UI) +├── run_web_viewer.bat # Windows launcher (web UI) +├── web/ # Web UI assets +│ ├── index.html +│ ├── app.js +│ └── style.css +└── README.md +``` + +--- + +**Press Ctrl+C to stop the sensor monitor** diff --git a/tactile-python_versioned_docs/version-previous-versions/index.mdx b/tactile-python_versioned_docs/version-previous-versions/index.mdx new file mode 100644 index 0000000..c2b4400 --- /dev/null +++ b/tactile-python_versioned_docs/version-previous-versions/index.mdx @@ -0,0 +1,12 @@ +--- +title: Python +sidebar_label: Python +--- + +**Stable** currently tracks `tactile_sensors`'s newest release, **v2.0.0**. + +We only host **Development (main)** and **Stable** here — older releases aren't +archived on this site. Browse their own tag in the source repository +instead: + +- **v1.0.0** — [browse source](https://github.com/Robotiq/tactile_sensors/tree/v1.0.0) diff --git a/tactile-python_versioned_docs/version-stable/_readme.md b/tactile-python_versioned_docs/version-stable/_readme.md new file mode 100644 index 0000000..b79f9f8 --- /dev/null +++ b/tactile-python_versioned_docs/version-stable/_readme.md @@ -0,0 +1,170 @@ +Lightweight cross-platform tool to test TSF-85 connections. + +## Quick Start — Terminal + +### Linux +```bash +cd sensor_quickstart +./run_quick_connect.sh +``` + +### Windows +```batch +cd sensor_quickstart +run_quick_connect.bat +``` + +That's it! The script handles everything automatically. + +--- + +## Quick Start — Web Viewer + +A browser-based dashboard with real-time heatmaps, dynamic time-series with FFT, and IMU plots. + +### Linux +```bash +cd sensor_quickstart +./run_web_viewer.sh +``` + +### Windows +```batch +cd sensor_quickstart +run_web_viewer.bat +``` + +The script sets up the environment, connects to the sensor, and opens your browser to `http://localhost:8080`. The dashboard has three tabs: + +- **Static** — live tactile heatmap (7x4 grid per finger) with baseline subtraction +- **Dynamic** — dynamic tactile time-series and FFT spectrum +- **IMU** — accelerometer and gyroscope plots + +The server shuts down automatically when you close the browser tab. + +--- + +## Requirements + +- **Python 3.7+**: [Download Python](https://www.python.org/downloads/) + - ✅ Check "Add Python to PATH" during installation + - ✅ After installing, restart your terminal/command prompt +- **pyserial**: Installed automatically by the script + +--- + +## What It Does + +1. Checks for Python installation +2. Creates virtual environment (`.venvSimpleCheck`) +3. Installs dependencies +4. Detects sensor +5. Displays real-time sensor data + +--- + +## Expected Output + +``` +================================================================================ + Robotiq Tactile Sensor Monitor +================================================================================ +Data Rate: 160.234 KB/s | Refresh Rate: 1000.1 Hz | Total Packets: 15234 + +FINGER 0 +-------------------------------------------------------------------------------- + Static Tactile (7 rows × 4 columns): + 0 1 2 3 + 4 5 6 7 + 8 9 10 11 + 12 13 14 15 + 16 17 18 19 + 20 21 22 23 + 24 25 26 27 + + Dynamic Tactile: 123 + + Accelerometer: X= 12 Y= -45 Z= 1024 + Gyroscope: X= 3 Y= -2 Z= 1 + Magnetometer: X= -15 Y= 23 Z= -87 + + Open Byte: 0 + +FINGER 1 +-------------------------------------------------------------------------------- + [... same format ...] + +================================================================================ +Press Ctrl+C to exit +``` + +--- + +## Troubleshooting + +### Sensor Not Found + +**Linux:** +- Check USB connection +- Verify sensor is plugged in +- Try different USB port + +**Windows:** +- Check Device Manager (Win+X → Device Manager → Ports) +- Sensor appears as "USB Serial Device" or "Cypress USB UART" +- Try different USB port +- **VM users**: USB passthrough may not work reliably for serial devices + +### No Data Displayed + +- Unplug and replug the sensor +- Close terminal and rerun script +- Sensor may need to be reset + +### Python Not Found (Windows) + +- Install Python from [python.org](https://www.python.org/downloads/) +- Must check "Add Python to PATH" during installation +- Restart command prompt after installing + +--- + +## Learn More + +- **Python**: [python.org](https://www.python.org/) +- **Virtual Environments**: [Python venv documentation](https://docs.python.org/3/library/venv.html) +- **pyserial**: [pyserial documentation](https://pyserial.readthedocs.io/) + +--- + +## Sensor Details + +- **Baud rate**: 115200 +- **Format**: 8N1 (8 data bits, no parity, 1 stop bit) +- **USB VID:PID**: `16d0:14cc` (Robotiq) or `04b4:f232` (Cypress, older units) +- **Data**: 28 tactile sensors per finger (7×4 grid) + IMU + dynamic sensor + +--- + +## File Structure + +``` +sensor_quickstart/ +├── quick_connect.py # Terminal-based sensor monitor +├── web_viewer.py # Web-based visualization server +├── protocol.py # USB protocol implementation +├── requirements.txt # Dependencies (pyserial, websockets) +├── run_quick_connect.sh # Linux launcher (terminal) +├── run_quick_connect.bat # Windows launcher (terminal) +├── run_web_viewer.sh # Linux launcher (web UI) +├── run_web_viewer.bat # Windows launcher (web UI) +├── web/ # Web UI assets +│ ├── index.html +│ ├── app.js +│ └── style.css +└── README.md +``` + +--- + +**Press Ctrl+C to stop the sensor monitor** diff --git a/tactile-python_versioned_docs/version-stable/index.mdx b/tactile-python_versioned_docs/version-stable/index.mdx new file mode 100644 index 0000000..c07e643 --- /dev/null +++ b/tactile-python_versioned_docs/version-stable/index.mdx @@ -0,0 +1,25 @@ +--- +title: Python +sidebar_label: Python +--- + +
+ Python logo +
+ +![Libraries](https://img.shields.io/badge/Category-Libraries-lightgrey) + +![Supported by Robotiq](https://img.shields.io/badge/Supported_by-Robotiq-blue) + +Examples of TSF usage via Python, developed and maintained by the Robotiq +team, are available in the following repository: + +https://github.com/robotiq/tactile_sensors/tree/v2.0.0/sensor_quickstart + +Below are the related instructions to use this driver. + +## Intro + +import Readme from './_readme.md'; + + \ No newline at end of file diff --git a/tactile-python_versioned_sidebars/version-previous-versions-sidebars.json b/tactile-python_versioned_sidebars/version-previous-versions-sidebars.json new file mode 100644 index 0000000..e5e5fe3 --- /dev/null +++ b/tactile-python_versioned_sidebars/version-previous-versions-sidebars.json @@ -0,0 +1,208 @@ +{ + "tactilePythonSidebar": [ + { + "type": "link", + "label": "Overview", + "href": "/docs/intro" + }, + { + "type": "category", + "label": "Adaptive grippers", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C++", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Melodic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Melodic" + }, + { + "type": "link", + "label": "ROS1 · Kinetic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Kinetic" + }, + { + "type": "link", + "label": "ROS1 · Indigo", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Indigo" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/Isaac%20Sim" + }, + { + "type": "link", + "label": "PyBullet", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/PyBullet" + }, + { + "type": "link", + "label": "MuJoCo", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/MuJoCo" + } + ] + }, + { + "type": "category", + "label": "Other", + "items": [ + { + "type": "link", + "label": "GraspGen", + "href": "/docs/drivers/Adaptive%20grippers/Other/GraspGen" + } + ] + } + ] + }, + { + "type": "category", + "label": "Tactile Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C++", + "href": "/docs/drivers/Tactile%20Sensor/Libraries/C++" + }, + { + "type": "doc", + "id": "index", + "label": "Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Noetic", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS1-Noetic" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Tactile%20Sensor/Simulation/Isaac%20Sim" + } + ] + } + ] + }, + { + "type": "category", + "label": "Force Torque Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/C" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Force%20Torque%20Sensor/ROS/ROS2-Humble" + } + ] + } + ] + }, + { + "type": "category", + "label": "EPick", + "items": [ + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/EPick/ROS/ROS2-Humble" + } + ] + } + ] + } + ] +} diff --git a/tactile-python_versioned_sidebars/version-stable-sidebars.json b/tactile-python_versioned_sidebars/version-stable-sidebars.json new file mode 100644 index 0000000..e5e5fe3 --- /dev/null +++ b/tactile-python_versioned_sidebars/version-stable-sidebars.json @@ -0,0 +1,208 @@ +{ + "tactilePythonSidebar": [ + { + "type": "link", + "label": "Overview", + "href": "/docs/intro" + }, + { + "type": "category", + "label": "Adaptive grippers", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C++", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/C++" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Adaptive%20grippers/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Melodic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Melodic" + }, + { + "type": "link", + "label": "ROS1 · Kinetic", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Kinetic" + }, + { + "type": "link", + "label": "ROS1 · Indigo", + "href": "/docs/drivers/Adaptive%20grippers/ROS/ROS1-Indigo" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/Isaac%20Sim" + }, + { + "type": "link", + "label": "PyBullet", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/PyBullet" + }, + { + "type": "link", + "label": "MuJoCo", + "href": "/docs/drivers/Adaptive%20grippers/Simulation/MuJoCo" + } + ] + }, + { + "type": "category", + "label": "Other", + "items": [ + { + "type": "link", + "label": "GraspGen", + "href": "/docs/drivers/Adaptive%20grippers/Other/GraspGen" + } + ] + } + ] + }, + { + "type": "category", + "label": "Tactile Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C++", + "href": "/docs/drivers/Tactile%20Sensor/Libraries/C++" + }, + { + "type": "doc", + "id": "index", + "label": "Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Lyrical", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Lyrical" + }, + { + "type": "link", + "label": "ROS2 · Jazzy", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Jazzy" + }, + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS2-Humble" + }, + { + "type": "link", + "label": "ROS1 · Noetic", + "href": "/docs/drivers/Tactile%20Sensor/ROS/ROS1-Noetic" + } + ] + }, + { + "type": "category", + "label": "Simulation", + "items": [ + { + "type": "link", + "label": "Isaac Sim", + "href": "/docs/drivers/Tactile%20Sensor/Simulation/Isaac%20Sim" + } + ] + } + ] + }, + { + "type": "category", + "label": "Force Torque Sensor", + "items": [ + { + "type": "category", + "label": "Libraries", + "items": [ + { + "type": "link", + "label": "C", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/C" + }, + { + "type": "link", + "label": "Python", + "href": "/docs/drivers/Force%20Torque%20Sensor/Libraries/Python" + } + ] + }, + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/Force%20Torque%20Sensor/ROS/ROS2-Humble" + } + ] + } + ] + }, + { + "type": "category", + "label": "EPick", + "items": [ + { + "type": "category", + "label": "ROS", + "items": [ + { + "type": "link", + "label": "ROS2 · Humble", + "href": "/docs/drivers/EPick/ROS/ROS2-Humble" + } + ] + } + ] + } + ] +} diff --git a/tactile-python_versions.json b/tactile-python_versions.json new file mode 100644 index 0000000..37cafbd --- /dev/null +++ b/tactile-python_versions.json @@ -0,0 +1 @@ +["stable","previous-versions"] diff --git a/test/list-submodule-tags.test.js b/test/list-submodule-tags.test.js new file mode 100644 index 0000000..e039315 --- /dev/null +++ b/test/list-submodule-tags.test.js @@ -0,0 +1,67 @@ +// Unit tests for scripts/list-submodule-tags.js's pure parsing/sorting +// logic — no network access, no real git invocation. See +// docs/contribute/versioning.mdx for how this fits into the versioning +// pipeline (picking Stable's tag, listing Previous versions). +const test = require('node:test'); +const assert = require('node:assert/strict'); +const { parseLsRemote, compareSemver } = require('../scripts/list-submodule-tags'); + +test('parseLsRemote: annotated tag resolves to the peeled commit, plain line first', () => { + const output = [ + 'b551c0b1111111111111111111111111111111 refs/tags/v1.0.0', + 'ce638441111111111111111111111111111111 refs/tags/v1.0.0^{}', + ].join('\n'); + const tags = parseLsRemote(output); + assert.deepEqual(tags, [{ name: 'v1.0.0', commit: 'ce638441111111111111111111111111111111' }]); +}); + +test('parseLsRemote: annotated tag resolves to the peeled commit, peeled line first', () => { + const output = [ + 'ce638441111111111111111111111111111111 refs/tags/v1.0.0^{}', + 'b551c0b1111111111111111111111111111111 refs/tags/v1.0.0', + ].join('\n'); + const tags = parseLsRemote(output); + assert.deepEqual(tags, [{ name: 'v1.0.0', commit: 'ce638441111111111111111111111111111111' }]); +}); + +test('parseLsRemote: lightweight tag (no ^{} line) keeps its own SHA', () => { + const output = '58dcb371111111111111111111111111111111 refs/tags/v1.0.0'; + const tags = parseLsRemote(output); + assert.deepEqual(tags, [{ name: 'v1.0.0', commit: '58dcb371111111111111111111111111111111' }]); +}); + +test('parseLsRemote: non-semver tags and their ^{} lines are dropped', () => { + const output = [ + 'aaaaaaa1111111111111111111111111111111 refs/tags/latest', + 'bbbbbbb1111111111111111111111111111111 refs/tags/latest^{}', + 'ccccccc1111111111111111111111111111111 refs/tags/v1.0.0-rc1', + 'ddddddd1111111111111111111111111111111 refs/tags/foo', + 'eeeeeee1111111111111111111111111111111 refs/tags/v2.0.0', + ].join('\n'); + const tags = parseLsRemote(output); + assert.deepEqual(tags, [{ name: 'v2.0.0', commit: 'eeeeeee1111111111111111111111111111111' }]); +}); + +test('parseLsRemote: sorts newest first, numerically not lexically', () => { + const names = ['v1', 'v1.2', 'v1.9.0', 'v1.10.0', 'v2.0.0']; + const output = names + .map((name, i) => `${String(i).padStart(7, '0')}1111111111111111111111111111111 refs/tags/${name}`) + .join('\n'); + const tags = parseLsRemote(output); + assert.deepEqual( + tags.map((t) => t.name), + ['v2.0.0', 'v1.10.0', 'v1.9.0', 'v1.2', 'v1'] + ); +}); + +test('parseLsRemote: empty output gives []', () => { + assert.deepEqual(parseLsRemote(''), []); +}); + +test('compareSemver: orders descending, missing parts treated as 0', () => { + assert.ok(compareSemver('v2.0.0', 'v1.10.0') < 0); + assert.ok(compareSemver('v1.10.0', 'v1.9.0') < 0); + assert.ok(compareSemver('v1.9.0', 'v1.2') < 0); + assert.ok(compareSemver('v1.2', 'v1') < 0); + assert.equal(compareSemver('v1.0.0', 'v1.0.0'), 0); +}); diff --git a/test/prune.test.js b/test/prune.test.js new file mode 100644 index 0000000..8eac67b --- /dev/null +++ b/test/prune.test.js @@ -0,0 +1,111 @@ +// Unit tests for scripts/lib/prune.js — run against a throwaway temp +// directory (fs.mkdtempSync), no real submodule checkout or build needed. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const os = require('node:os'); +const {pruneStale, pruneLegacyFolder, cleanupLegacyDestRoot} = require('../scripts/lib/prune'); + +function tempDir() { + return fs.mkdtempSync(path.join(os.tmpdir(), 'prune-test-')); +} + +function write(root, relPath, content = 'x') { + const full = path.join(root, relPath); + fs.mkdirSync(path.dirname(full), {recursive: true}); + fs.writeFileSync(full, content); + return full; +} + +test('pruneStale: deletes synced files not in `written`, keeps the ones that are', () => { + const root = tempDir(); + const kept = write(root, 'a.md'); + const stale = write(root, 'b.md'); + const removed = pruneStale(root, new Set([kept])); + assert.deepEqual(removed, [stale]); + assert.ok(fs.existsSync(kept)); + assert.ok(!fs.existsSync(stale)); +}); + +test('pruneStale: never deletes index.* or README.*, even when not in `written`', () => { + const root = tempDir(); + const index = write(root, 'index.md'); + const indexMdx = write(root, 'sub/index.mdx'); + const readme = write(root, 'README.md'); + const removed = pruneStale(root, new Set()); + assert.deepEqual(removed, []); + assert.ok(fs.existsSync(index)); + assert.ok(fs.existsSync(indexMdx)); + assert.ok(fs.existsSync(readme)); +}); + +test('pruneStale: ignores extensions outside COPY_EXTS', () => { + const root = tempDir(); + const txt = write(root, 'notes.txt'); + const removed = pruneStale(root, new Set()); + assert.deepEqual(removed, []); + assert.ok(fs.existsSync(txt)); +}); + +test('pruneStale: removes directories it leaves empty', () => { + const root = tempDir(); + const stale = write(root, 'sub/deep/a.md'); + pruneStale(root, new Set()); + assert.ok(!fs.existsSync(path.join(root, 'sub'))); +}); + +test('pruneStale: a non-existent directory is a no-op', () => { + const root = tempDir(); + assert.deepEqual(pruneStale(path.join(root, 'missing'), new Set()), []); +}); + +test('pruneLegacyFolder: removes .md files, keeps .mdx, removes empty subdirs', () => { + const root = tempDir(); + const md = write(root, 'a.md'); + const indexMd = write(root, 'index.md'); // legacy folders get NO index/README exemption + const mdx = write(root, 'index.mdx'); + const nested = write(root, 'sub/b.md'); + pruneLegacyFolder(root); + assert.ok(!fs.existsSync(md)); + assert.ok(!fs.existsSync(indexMd)); + assert.ok(fs.existsSync(mdx)); + assert.ok(!fs.existsSync(path.join(root, 'sub'))); +}); + +test('cleanupLegacyDestRoot: no-op for a job without destRoot', () => { + const root = tempDir(); + write(root, 'docs/drivers/X/Y/_readme.md'); + const result = cleanupLegacyDestRoot(root, {to: 'X/Y/_readme.md'}); + assert.equal(result, undefined); + assert.ok(fs.existsSync(path.join(root, 'docs/drivers/X/Y/_readme.md'))); +}); + +test('cleanupLegacyDestRoot: removes a legacy folder job\'s old output, keeps a hand-authored index.mdx and sibling folders', () => { + const root = tempDir(); + write(root, 'docs/drivers/Adaptive grippers/Libraries/C++/API/a.md'); + write(root, 'docs/drivers/Adaptive grippers/Libraries/C++/API/index.md'); + const siblingIndex = write(root, 'docs/drivers/Adaptive grippers/Libraries/C++/docs/index.mdx'); + const siblingFolder = write(root, 'docs/drivers/Adaptive grippers/Other/GraspGen/index.mdx'); + + const result = cleanupLegacyDestRoot(root, {to: 'Adaptive grippers/Libraries/C++/API', destRoot: 'versioned-tools'}); + + assert.equal(result, path.join('docs', 'drivers', 'Adaptive grippers/Libraries/C++/API')); + assert.ok(!fs.existsSync(path.join(root, 'docs/drivers/Adaptive grippers/Libraries/C++/API'))); + assert.ok(fs.existsSync(siblingIndex)); + assert.ok(fs.existsSync(siblingFolder)); +}); + +test('cleanupLegacyDestRoot: removes a legacy file job\'s old output', () => { + const root = tempDir(); + const legacyReadme = write(root, 'docs/drivers/Tactile Sensor/Libraries/C++/_readme.md'); + const result = cleanupLegacyDestRoot(root, {to: 'Tactile Sensor/Libraries/C++/_readme.md', destRoot: 'versioned-tools'}); + assert.equal(result, path.join('docs', 'drivers', 'Tactile Sensor/Libraries/C++/_readme.md')); + assert.ok(!fs.existsSync(legacyReadme)); +}); + +test('cleanupLegacyDestRoot: no-op when nothing legacy exists', () => { + const root = tempDir(); + const result = cleanupLegacyDestRoot(root, {to: 'Tactile Sensor/Libraries/Python/_readme.md', destRoot: 'versioned-tools'}); + assert.equal(result, undefined); +}); diff --git a/test/site-nav-tree.test.mjs b/test/site-nav-tree.test.mjs new file mode 100644 index 0000000..e6783b4 --- /dev/null +++ b/test/site-nav-tree.test.mjs @@ -0,0 +1,103 @@ +// Unit tests for scripts/site-nav-tree.mjs. +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import { + buildMainSidebar, + buildInstanceSidebar, + regenerateInstanceSidebar, + VERSIONED_TOOL_PATHS, +} from '../scripts/site-nav-tree.mjs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); + +function collectVersionedLinks(items, found = []) { + for (const item of items) { + if (item.type === 'link') found.push(item); + if (item.type === 'category') { + assert.equal(item.link, undefined, `category "${item.label}" should not have a link in an instance sidebar`); + collectVersionedLinks(item.items, found); + } + } + return found; +} + +test('buildInstanceSidebar: the active tool node is exactly activeItem', () => { + const activeItem = {type: 'doc', id: 'index', label: 'C++'}; + const sidebar = buildInstanceSidebar('tactile-cpp', activeItem); + // Find it by walking: Tactile Sensor > Libraries > (activeItem) + const tactile = sidebar.find((n) => n.label === 'Tactile Sensor'); + const libraries = tactile.items.find((n) => n.label === 'Libraries'); + assert.deepEqual(libraries.items.find((n) => n.label === 'C++'), activeItem); +}); + +test('buildInstanceSidebar: every other versioned tool is a link to its encoded path', () => { + const sidebar = buildInstanceSidebar('tactile-cpp', {type: 'doc', id: 'index', label: 'C++'}); + const links = collectVersionedLinks(sidebar); + for (const [tool, urlPath] of Object.entries(VERSIONED_TOOL_PATHS)) { + if (tool === 'tactile-cpp') continue; + const expectedHref = encodeURI(urlPath); + const match = links.find((l) => l.href === expectedHref); + assert.ok(match, `expected a link with href ${expectedHref} for tool ${tool}`); + } +}); + +test('buildInstanceSidebar: href encoding — spaces become %20, + is left alone', () => { + const sidebar = buildInstanceSidebar('tactile-python', {type: 'doc', id: 'index', label: 'Python'}); + const links = collectVersionedLinks(sidebar); + const cpp = links.find((l) => l.label === 'C++' && l.href.includes('Tactile')); + assert.equal(cpp.href, '/docs/drivers/Tactile%20Sensor/Libraries/C++'); +}); + +test('buildInstanceSidebar: no category in an instance sidebar has its own link', () => { + const sidebar = buildInstanceSidebar('isaac-sim', {type: 'doc', id: 'index', label: 'Isaac Sim'}); + collectVersionedLinks(sidebar); // asserts internally on every category +}); + +test('buildMainSidebar: versioned-tool leaves are links, main-owned leaves are doc ids', () => { + const sidebar = buildMainSidebar(); + const adaptive = sidebar.find((n) => n.label === 'Adaptive grippers'); + const libraries = adaptive.items.find((n) => n.label === 'Libraries'); + const cpp = libraries.items.find((n) => n.label === 'C++'); + assert.equal(cpp.type, 'link'); + assert.equal(cpp.href, encodeURI(VERSIONED_TOOL_PATHS['adaptive-grippers-cpp'])); + // Python isn't versioned — plain doc id string, not a link object. + assert.ok(libraries.items.includes('drivers/Adaptive grippers/Libraries/Python/index')); +}); + +// The main regression test this file exists for: every already-cut +// version's frozen sidebar snapshot must already equal what +// regenerateInstanceSidebar produces from the CURRENT site-nav-tree.mjs. +// If this fails, someone changed site-nav-tree.mjs's SITE_TREE without +// re-running `node scripts/regenerate-versioned-sidebars.mjs` (also run +// automatically by `npm run generate` — see docs/contribute/versioning.mdx) +// — the same drift `ci.yml`'s "Verify generated content is committed" +// step would also catch after a real build, just faster and offline here. +test('drift guard: every committed versioned-sidebar snapshot matches what the live site nav tree would produce', () => { + const suffix = '_versioned_sidebars'; + const toolDirs = fs.readdirSync(ROOT, {withFileTypes: true}).filter((e) => e.isDirectory() && e.name.endsWith(suffix)); + assert.ok(toolDirs.length > 0, 'expected at least one _versioned_sidebars directory to exist'); + + for (const dirEntry of toolDirs) { + const tool = dirEntry.name.slice(0, -suffix.length); + const dir = path.join(ROOT, dirEntry.name); + for (const file of fs.readdirSync(dir)) { + if (!file.endsWith('-sidebars.json')) continue; + const filePath = path.join(dir, file); + const existing = JSON.parse(fs.readFileSync(filePath, 'utf8')); + const [sidebarKey, existingItems] = Object.entries(existing)[0]; + + const regenerated = regenerateInstanceSidebar(tool, existingItems); + assert.notEqual(regenerated, undefined, `${path.relative(ROOT, filePath)}: could not locate '${tool}'s own content — site nav tree shape changed structurally`); + assert.deepEqual( + regenerated, + existingItems, + `${path.relative(ROOT, filePath)} is stale — run: node scripts/regenerate-versioned-sidebars.mjs` + ); + assert.equal(typeof sidebarKey, 'string'); + } + } +}); diff --git a/test/youtube-embed.test.mjs b/test/youtube-embed.test.mjs new file mode 100644 index 0000000..4a9b5ee --- /dev/null +++ b/test/youtube-embed.test.mjs @@ -0,0 +1,104 @@ +// Unit tests for src/remark/youtubeEmbed.mjs. +// +// matchYoutubeParagraph is tested directly against hand-built mdast nodes +// (no parser needed). The compile-level tests below reproduce the one +// Docusaurus-specific detail this plugin actually depends on — rehype-raw +// running for `format: 'md'` with `passThrough: ['mdxJsxFlowElement', ...]` +// (see @docusaurus/mdx-loader's processor.js) — using the real +// @mdx-js/mdx + rehype-raw packages already in node_modules (transitive +// deps of @docusaurus/mdx-loader), not a hand-rolled approximation. This +// is what catches a regression back to a plain `html` node, which would +// silently keep working for 'md' but break 'mdx' — exactly the bug this +// plugin shipped with once (see PR #14). +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {compile, run} from '@mdx-js/mdx'; +import * as runtime from 'react/jsx-runtime'; +import {renderToStaticMarkup} from 'react-dom/server'; +import rehypeRaw from 'rehype-raw'; +import remarkYoutubeEmbed, {matchYoutubeParagraph} from '../src/remark/youtubeEmbed.mjs'; + +const THUMB = 'https://img.youtube.com/vi/dQw4w9WgXcQ/hqdefault.jpg'; +const PATTERN = `[![Title](${THUMB})](https://youtu.be/dQw4w9WgXcQ)\n`; + +function paragraph(children) { + return {type: 'paragraph', children}; +} + +function link(url, children) { + return {type: 'link', url, children}; +} + +function image(url, alt) { + return {type: 'image', url, alt}; +} + +test('matchYoutubeParagraph: matches youtu.be links', () => { + const node = paragraph([link('https://youtu.be/dQw4w9WgXcQ', [image(THUMB, 'Title')])]); + assert.deepEqual(matchYoutubeParagraph(node), {thumbnailId: 'dQw4w9WgXcQ', title: 'Title'}); +}); + +test('matchYoutubeParagraph: matches youtube.com/watch?v= links', () => { + const node = paragraph([ + link('https://youtube.com/watch?v=dQw4w9WgXcQ&t=30s', [image(THUMB, 'Title')]), + ]); + assert.deepEqual(matchYoutubeParagraph(node), {thumbnailId: 'dQw4w9WgXcQ', title: 'Title'}); +}); + +test('matchYoutubeParagraph: falls back to "Video" with no alt text', () => { + const node = paragraph([link('https://youtu.be/dQw4w9WgXcQ', [image(THUMB, undefined)])]); + assert.equal(matchYoutubeParagraph(node).title, 'Video'); +}); + +test('matchYoutubeParagraph: no match when the thumbnail and link ids differ', () => { + const node = paragraph([link('https://youtu.be/otherId12345', [image(THUMB, 'Title')])]); + assert.equal(matchYoutubeParagraph(node), undefined); +}); + +test('matchYoutubeParagraph: no match when the image is not on img.youtube.com', () => { + const node = paragraph([ + link('https://youtu.be/dQw4w9WgXcQ', [image('https://example.com/dQw4w9WgXcQ.jpg', 'Title')]), + ]); + assert.equal(matchYoutubeParagraph(node), undefined); +}); + +test('matchYoutubeParagraph: no match when the paragraph has more than one child', () => { + const node = paragraph([link('https://youtu.be/dQw4w9WgXcQ', [image(THUMB, 'Title')]), {type: 'text', value: '!'}]); + assert.equal(matchYoutubeParagraph(node), undefined); +}); + +test('matchYoutubeParagraph: no match when the link has text next to the image', () => { + const node = paragraph([ + link('https://youtu.be/dQw4w9WgXcQ', [image(THUMB, 'Title'), {type: 'text', value: ' watch'}]), + ]); + assert.equal(matchYoutubeParagraph(node), undefined); +}); + +test('matchYoutubeParagraph: no match on a plain paragraph', () => { + assert.equal(matchYoutubeParagraph(paragraph([{type: 'text', value: 'hello'}])), undefined); +}); + +async function renderAsFormat(format) { + const rehypePlugins = + format === 'md' ? [[rehypeRaw, {passThrough: ['mdxJsxFlowElement', 'mdxJsxTextElement']}]] : []; + const compiled = await compile(PATTERN, { + format, + outputFormat: 'function-body', + remarkPlugins: [remarkYoutubeEmbed], + rehypePlugins, + }); + const {default: Content} = await run(compiled, runtime); + return renderToStaticMarkup(Content()); +} + +test('compiles and renders an iframe with format "md" (rehype-raw + passThrough, as Docusaurus configures it)', async () => { + const html = await renderAsFormat('md'); + assert.match(html, /class="video-wrapper"/); + assert.match(html, /]*src="https:\/\/www\.youtube\.com\/embed\/dQw4w9WgXcQ"/); +}); + +test('compiles and renders an iframe with format "mdx" — regression test for PR #14 (used to throw "Cannot handle unknown node `raw`")', async () => { + const html = await renderAsFormat('mdx'); + assert.match(html, /class="video-wrapper"/); + assert.match(html, /]*src="https:\/\/www\.youtube\.com\/embed\/dQw4w9WgXcQ"/); +}); diff --git a/docs/drivers/Adaptive grippers/Libraries/C++/docs/index.mdx b/versioned-tools/Adaptive grippers/Libraries/C++/docs/index.mdx similarity index 100% rename from docs/drivers/Adaptive grippers/Libraries/C++/docs/index.mdx rename to versioned-tools/Adaptive grippers/Libraries/C++/docs/index.mdx diff --git a/docs/drivers/Adaptive grippers/Libraries/C++/index.mdx b/versioned-tools/Adaptive grippers/Libraries/C++/index.mdx similarity index 65% rename from docs/drivers/Adaptive grippers/Libraries/C++/index.mdx rename to versioned-tools/Adaptive grippers/Libraries/C++/index.mdx index ee7626f..0fbe4e8 100644 --- a/docs/drivers/Adaptive grippers/Libraries/C++/index.mdx +++ b/versioned-tools/Adaptive grippers/Libraries/C++/index.mdx @@ -4,13 +4,20 @@ sidebar_label: C++ ---
- 2F-85 gripper + C++ logo
![Libraries](https://img.shields.io/badge/Category-Libraries-lightgrey) ![Supported by Robotiq](https://img.shields.io/badge/Supported_by-Robotiq-blue) +:::tip Use a released version +This page documents unreleased `main`. Install a tagged release instead — +see **Stable** in the version switcher above, or this repository's own +tags on GitHub. `main` contains in-progress work; its API is not a +commitment — see the [API stability policy](/docs/api-stability). +::: + import Readme from './_readme.md'; diff --git a/docs/drivers/Adaptive grippers/Simulation/Isaac Sim/index.mdx b/versioned-tools/Adaptive grippers/Simulation/Isaac Sim/index.mdx similarity index 100% rename from docs/drivers/Adaptive grippers/Simulation/Isaac Sim/index.mdx rename to versioned-tools/Adaptive grippers/Simulation/Isaac Sim/index.mdx diff --git a/versioned-tools/Tactile Sensor/Libraries/C++/index.mdx b/versioned-tools/Tactile Sensor/Libraries/C++/index.mdx new file mode 100644 index 0000000..441c124 --- /dev/null +++ b/versioned-tools/Tactile Sensor/Libraries/C++/index.mdx @@ -0,0 +1,32 @@ +--- +title: C++ +sidebar_label: C++ +--- + +
+ C++ logo +
+ +![Libraries](https://img.shields.io/badge/Category-Libraries-lightgrey) + +![Supported by Robotiq](https://img.shields.io/badge/Supported_by-Robotiq-blue) + +:::tip Use a released version +This page documents unreleased `main`. Install a tagged release instead — +see **Stable** in the version switcher above, or this repository's own +tags on GitHub. `main` contains in-progress work; its API is not a +commitment — see the [API stability policy](/docs/api-stability). +::: + +The source files of the TSF C++ driver, developed and maintained by the +Robotiq team, are available in the following repository: + +https://github.com/robotiq/tactile_sensors/tree/main/sdk_cpp + +Below are the related instructions to use this driver. + +## Intro + +import Readme from './_readme.md'; + + \ No newline at end of file diff --git a/versioned-tools/Tactile Sensor/Libraries/Python/index.mdx b/versioned-tools/Tactile Sensor/Libraries/Python/index.mdx new file mode 100644 index 0000000..6fd9f73 --- /dev/null +++ b/versioned-tools/Tactile Sensor/Libraries/Python/index.mdx @@ -0,0 +1,32 @@ +--- +title: Python +sidebar_label: Python +--- + +
+ Python logo +
+ +![Libraries](https://img.shields.io/badge/Category-Libraries-lightgrey) + +![Supported by Robotiq](https://img.shields.io/badge/Supported_by-Robotiq-blue) + +:::tip Use a released version +This page documents unreleased `main`. Install a tagged release instead — +see **Stable** in the version switcher above, or this repository's own +tags on GitHub. `main` contains in-progress work; its API is not a +commitment — see the [API stability policy](/docs/api-stability). +::: + +Examples of TSF usage via Python, developed and maintained by the Robotiq +team, are available in the following repository: + +https://github.com/robotiq/tactile_sensors/tree/main/sensor_quickstart + +Below are the related instructions to use this driver. + +## Intro + +import Readme from './_readme.md'; + + \ No newline at end of file