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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ option(BUILD_PLUGIN_NOITOM_MOCAP "Build Noitom mocap plugin (downloads MocapApi
option(BUILD_PLUGIN_OGLO "Build OGLO tactile glove plugin (BLE, Linux only; fetches SimpleBLE + nlohmann/json)" OFF)
option(BUILD_PLUGIN_WUJI_GLOVE "Build Wuji glove plugin (requires the wuji_sdk C SDK)" OFF)
option(BUILD_PLUGIN_SHARPA_AVATAR "Build Sharpa AvatarExo Glove plugin (Linux; skips if the Avatar SDK is missing)" OFF)
option(BUILD_PLUGIN_REVOHUMAN "Build RevoHuman glove plugin (Linux; skips if the RevoHuman C SDK is missing)" OFF)
option(BUILD_EXAMPLES "Build examples" ON)
option(BUILD_EXAMPLE_TELEOP_ROS2 "Build only the teleop_ros2 ROS 2 reference integration (e.g. for Docker)" OFF)
option(BUILD_TESTING "Build unit tests" ON)
Expand Down
1 change: 1 addition & 0 deletions cmake/ClangFormat.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ set(_cf_exclude_patterns
".*/deps/.*"
".*/manus/ManusSDK/.*"
".*/wuji_glove/wuji-sdk-c-.*"
".*/revohuman/revohuman-c-sdk-.*"
".*/node_modules/.*"
".*/third_party/.*"
".*/venv/.*"
Expand Down
17 changes: 17 additions & 0 deletions docs/source/_data/devices.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,23 @@ devices:
url: https://wuji.tech/en/
- email: sales@wuji.tech

- id: revohuman-glove
name: RevoHuman Glove
url: https://github.com/BrainCoTech/brainco-revohuman-sdk
group: peripheral
modes: Hand tracking, joint encoders, tactile
since: main
details:
setup:
- label: RevoHuman Glove docs
doc: /device/hand_tracking/revohuman
- label: RevoHuman Glove Plugin
url: https://github.com/NVIDIA/IsaacCapture/tree/main/src/plugins/revohuman
requirements:
- Linux, x86_64 or aarch64
- RevoHuman C SDK 0.1.3
- USB connection to the plugin host

- id: noitom
name: Noitom Robotics Motion Capture
url: https://www.noitomrobotics.com
Expand Down
1 change: 1 addition & 0 deletions docs/source/device/hand_tracking/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ and hand exoskeletons. See :doc:`/device/trackers/index` for the common tracking
wuji_glove
sharpa_avatar
haptikos
revohuman
227 changes: 227 additions & 0 deletions docs/source/device/hand_tracking/revohuman.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
.. SPDX-FileCopyrightText: Copyright (c) 2026 BrainCo. All rights reserved.
.. SPDX-License-Identifier: Apache-2.0

RevoHuman Glove
===============

A Linux-only plugin for BrainCo RevoHuman exoskeleton gloves. It reads each
glove's 21 encoder angles and its tactile forces through the RevoHuman C SDK,
maps the SDK's hand points to the 26 OpenXR hand joints and injects them into
the OpenXR hand-tracking layer. It also pushes the encoder angles as joint
states and the forces with the common tactile contract, laid out with the SDK's
taxel layout.

.. contents:: On this page
:local:
:depth: 2

Data flow
---------

.. code-block:: text

RevoHuman glove (USB) ──► RevoHuman C SDK ─┐
├─► revohuman plugin ──► CloudXR OpenXR runtime
Optical hand / controller ─────────────────┘ (hand points, (hand joints, joint states,
wrist-pose fusion) tactile forces)

The plugin computes no kinematics of its own: the SDK turns the angles into hand
points for the glove's mechanical model, and the plugin maps those points to
OpenXR order with corrections reviewed for that model. No URDF is loaded.

Outputs
-------

Per hand, with the default collection prefix ``revohuman``
(``--collection-prefix=NAME`` changes it):

.. list-table::
:widths: 40 60
:header-rows: 1

* - Output
- Contents
* - OpenXR hand ``/hand/left``, ``/hand/right``
- 26 joints relative to the wrist, placed at the wrist pose described below
* - ``revohuman_<side>_joint_state`` / tensor ``joint_state``
- ``JointStateOutput`` with the 21 encoder angles in radians, in SDK order;
see :doc:`/device/joint_space/index`
* - ``revohuman_<side>_tactile`` / tensor ``tactile``
- ``TactileDataArray`` taxel forces: the SDK's calibrated reading, unchanged,
along each taxel's outward normal; see :doc:`/device/tactile/index`
* - ``revohuman_<side>_tactile/metadata``
- The ``TactileMetadata`` layout the forces belong to

Started by hand, the plugin publishes ``<id>/device_status`` only with
``--plugin-root-id=<id>``.

Prerequisites
-------------

- **Linux** — x86_64 or aarch64.
- **Source-build prerequisites** — the toolchain and system packages in
:doc:`/development/build_from_source/index`, for ``install.sh`` and the manual
CMake build alike.
- **RevoHuman C SDK 0.1.3** — the ``c-sdk-v0.1.3`` release of
`brainco-revohuman-sdk <https://github.com/BrainCoTech/brainco-revohuman-sdk>`_,
which extracts to ``include/revohuman.h``, ``lib/librevohuman.so`` with its
versioned names, and ``LICENSE``. The install script downloads it
automatically. A Python SDK wheel cannot replace it.
- **RevoHuman gloves** connected over USB, with access to their serial ports.
- **CloudXR runtime** running on the plugin host.

Installation
------------

``install.sh`` downloads the pinned C SDK release, verifies it against a
per-architecture SHA-256, extracts it next to the script (re-runs reuse the
extracted copy), and configures, builds, and installs the plugin with the SDK
library and its licence next to it:

.. code-block:: bash

./src/plugins/revohuman/install.sh

Pass ``--build-dir <path>`` to reuse a non-default top-level CMake build
directory and ``--install-prefix <dir>`` to choose where the plugin goes.
``--sdk-root <dir>`` (or ``REVOHUMAN_SDK_ROOT``) uses an SDK package you already
extracted.

To drive CMake directly:

.. code-block:: bash

cmake -B build -DBUILD_PLUGINS=ON -DBUILD_PLUGIN_REVOHUMAN=ON \
-DREVOHUMAN_SDK_ROOT=/path/to/revohuman-c-sdk
cmake --build build --target revohuman_plugin
cmake --install build --component revohuman

The installed plugin finds the SDK library next to itself, so it needs neither
the download nor the build directory afterwards. Keep the library's symlinks.

Running the Plugin
------------------

The plugin reaches the Teleop session through the CloudXR / OpenXR runtime, so
start the runtime first and source its environment in the shell that launches
the plugin:

.. code-block:: bash

python -m isaaccapture.cloudxr.service start # runs in the background
source ~/.cloudxr/run/cloudxr.env
./install/plugins/revohuman/revohuman_plugin

See :ref:`dedicated-cloudxr-runtime` and
:ref:`load-cloudxr-environment-variables` for the full service setup.
Applications can also launch it through its ``plugin.yaml`` using
``TeleopSession``. The gloves are its only source, so it takes no source
argument; an unknown argument such as ``--live`` or ``--replay`` is ignored with
a warning.

The plugin neither records nor plays back: record its collections with
``McapRecordingConfig`` and play them back with ``McapReplayConfig``, as for any
other tracker (see :doc:`/references/mcap_record_replay`).

The plugin finds the gloves through the SDK, at most one left and one right,
with nothing to declare. When both are plugged in, it opens them in one
two-hand SDK kit with time sync on, so the two hands' samples come from the
same USB frame. A glove plugged in alone opens on its own; when the other one
arrives, both are reopened together. Gloves that cannot be opened together
keep streaming on their own. Until a glove is found, the plugin looks for it
every 2 s and reports the SDK's reason in its status, without holding up the
other hand. The SDK reconnects a glove after a short dropout; when the stream
ends anyway, the plugin closes the glove, together with its partner when the
two share a kit, and looks for them again.

Each glove's mechanical model follows the product the SDK identifies: a PTV1
glove uses the DV1 model and a P1 glove the P model. A P1 glove has no tactile
sensors, so it streams the hand and joint states without tactile outputs. A
glove of the wrong product or with incompatible firmware is reported as failed.

Wrist pose
----------

The glove stream contains wrist-relative joints but no absolute 6DoF wrist
pose. Before injection, the plugin places the hand at a wrist pose from optical
hand tracking or from a controller aim pose combined with a rigid per-hand
offset.

Set ``REVOHUMAN_WRIST_SOURCE`` (or pass ``--wrist-source``) to ``auto`` (the
default), ``hand_tracking``, or ``controller``. In ``auto`` mode, optical
tracking is preferred; the controller is used when optical tracking provides no
valid pose. The plugin refuses a ``--wrist-source`` that disagrees with the
variable.

A controller reports its aim pose, not the wrist. No controller mount is
calibrated for these gloves yet, so the aim-to-wrist offsets default to
identity and the plugin warns for each hand without one unless the source is
``hand_tracking``. Set them with:

.. code-block:: bash

./install/plugins/revohuman/revohuman_plugin \
--aim-to-wrist-left=px,py,pz,qx,qy,qz,qw \
--aim-to-wrist-right=px,py,pz,qx,qy,qz,qw

Position values are in meters and quaternion values use ``(x, y, z, w)``.

Without a tracked wrist the hand is still injected, without tracking flags, at
the last valid wrist pose (or the stage origin), and its status is degraded.

The glove's wrist is a reference point of its mechanical model: the origin of
its ``palm_link``, on the back of the hand near the end of the forearm, moved
about 12.6 mm towards the palm to the depth of the palm centre. It is not the
anatomical wrist centre that the OpenXR wrist joint stands for. Nominally the
wrist centre is about 4 cm further towards the fingers and about 0.2 to 0.7 cm
further towards the palm; these figures come from typical adult hand
dimensions, not from measurements on the glove, and vary with the wearer. With
optical hand tracking, the injected hand therefore sits that much further
towards the fingers and the palm than the tracked hand. With a controller, set
the aim-to-wrist offsets to end at the glove's wrist point. Verify the
alignment for the wearer.

Tactile layout
--------------

At start-up the plugin builds each hand's ``TactileMetadata`` from the C SDK's
taxel layout for the DV1 model (``rh_hand_model_taxel_layout``); there is no
layout file or option. Each taxel is bound rigidly to the OpenXR joint its SDK
link moves with, with its +Z along the outward normal. That joint is rigid on
the link, so the skinned taxels stay on the SDK's taxels as the fingers bend.
The log names the layout source, taxel count and SHA-256 of each hand's
metadata. When the SDK has no layout, the plugin logs why and pushes no tactile
data; the other outputs are unaffected.

A force sample is pushed only while it is calibrated, recent and newer than the
last one, and its status names the reason when it is not. Consumers must check
that status as well as the presence of data.

Troubleshooting
---------------

.. list-table::
:widths: 40 60
:header-rows: 1

* - Symptom
- Fix
* - ``install.sh`` fails with a SHA-256 mismatch
- The downloaded package does not match the pinned release. Re-run to
rule out a corrupted download; if it persists, do not bypass the check
— verify the release assets upstream.
* - No ``revohuman_plugin`` is built, ``install.sh`` refuses the install, or
the plugin stops before the OpenXR session naming an SDK function
- The SDK found during configuration is incomplete: its library is missing
or lacks a function the plugin calls. Point ``REVOHUMAN_SDK_ROOT`` at a
complete C SDK release and reconfigure.
* - A glove is never opened and its status names a busy port
- Another process, such as the SDK viewer, holds the glove's serial port.
Close it; the plugin opens the glove on its next attempt.
* - A glove is reported as failed
- The status carries the SDK's reason: a product or variant the plugin
does not support, or incompatible firmware. These are not retried.
* - Hands appear but a downstream client sees no tactile data
- A P1 glove has no tactile sensors. Otherwise check the start-up log for
why there is no ``TactileMetadata`` (an SDK without the taxel layout),
and the tactile status for the reason a sample was not pushed.
5 changes: 5 additions & 0 deletions src/plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ endif()
if(BUILD_PLUGIN_SHARPA_AVATAR)
add_subdirectory(sharpa_avatar)
endif()
# Gated: Linux + the RevoHuman C SDK (REVOHUMAN_SDK_ROOT). Missing SDK skips the plugin and its tests;
# an SDK without a library the live source links against skips only the plugin executable.
if(BUILD_PLUGIN_REVOHUMAN)
add_subdirectory(revohuman)
endif()
5 changes: 5 additions & 0 deletions src/plugins/revohuman/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 BrainCo. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Vendor SDK extracted by install.sh
revohuman-c-sdk-*/
107 changes: 107 additions & 0 deletions src/plugins/revohuman/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 BrainCo. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# RevoHuman exoskeleton gloves: OpenXR hands, encoder joint states and tactile data with its
# TactileMetadata, pushed via OpenXR.

# Outcome of this configure for scripts such as install.sh, which must not accept a build without
# the plugin; OFF until the live source is known to link, so an early return below leaves it OFF.
set(REVOHUMAN_LIVE_AVAILABLE OFF CACHE INTERNAL "RevoHuman live source links against the C SDK")

if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Skipping revohuman plugin (Linux only)")
add_custom_target(revohuman_plugin
COMMAND ${CMAKE_COMMAND} -E echo "Skipping revohuman: Linux only")
return()
endif()

# The plugin needs the C SDK header (revohuman.h); the live source also needs its library.
# There is no default location: without the cache variable or the environment variable the
# plugin is skipped, as it is when the header is missing. install.sh always passes it.
set(REVOHUMAN_SDK_ROOT "" CACHE PATH "RevoHuman C SDK root (contains include/revohuman.h and lib/)")
if(NOT REVOHUMAN_SDK_ROOT AND DEFINED ENV{REVOHUMAN_SDK_ROOT})
set(REVOHUMAN_SDK_ROOT "$ENV{REVOHUMAN_SDK_ROOT}")
endif()
set(_revohuman_no_sdk "")
if(NOT REVOHUMAN_SDK_ROOT)
set(_revohuman_no_sdk "RevoHuman C SDK root not set (set -DREVOHUMAN_SDK_ROOT=/path/to/sdk or REVOHUMAN_SDK_ROOT)")
elseif(NOT EXISTS "${REVOHUMAN_SDK_ROOT}/include/revohuman.h")
set(_revohuman_no_sdk "RevoHuman C SDK not found (set -DREVOHUMAN_SDK_ROOT=/path/to/sdk)")
endif()
if(_revohuman_no_sdk)
message(STATUS "${_revohuman_no_sdk} — skipping revohuman plugin build.")
return()
endif()
set(REVOHUMAN_SDK_INCLUDE_DIR "${REVOHUMAN_SDK_ROOT}/include")

add_library(revohuman_sdk_headers INTERFACE)
target_include_directories(revohuman_sdk_headers INTERFACE $<BUILD_INTERFACE:${REVOHUMAN_SDK_INCLUDE_DIR}>)
add_library(revohuman_plugin::sdk_headers ALIAS revohuman_sdk_headers)

# The library is used only when it links with every function the live source calls, so a partial
# SDK build skips the plugin executable instead of failing the build. Checked on every configure,
# and searched again whenever the SDK root changes (find_library alone keeps its first result).
if(NOT "${REVOHUMAN_SDK_ROOT}" STREQUAL "${_REVOHUMAN_SDK_LIBRARY_ROOT}")
unset(REVOHUMAN_SDK_LIBRARY CACHE)
set(_REVOHUMAN_SDK_LIBRARY_ROOT "${REVOHUMAN_SDK_ROOT}" CACHE INTERNAL "SDK root REVOHUMAN_SDK_LIBRARY was found in")
endif()
find_library(REVOHUMAN_SDK_LIBRARY NAMES revohuman PATHS "${REVOHUMAN_SDK_ROOT}/lib" NO_DEFAULT_PATH)
set(REVOHUMAN_LIVE_AVAILABLE OFF)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REVOHUMAN_LIVE_AVAILABLE is written four times in this file (9, 49, 79, 93) and this normal var shadows the cache one; keep the reset at 9, write 79 as CACHE INTERNAL (INTERNAL implies FORCE) and drop this one and 93

if(REVOHUMAN_SDK_LIBRARY)
include(CheckCXXSourceCompiles)
include(CMakePushCheckState)
cmake_push_check_state(RESET)
set(CMAKE_REQUIRED_INCLUDES "${REVOHUMAN_SDK_INCLUDE_DIR}")
set(CMAKE_REQUIRED_LIBRARIES "${REVOHUMAN_SDK_LIBRARY}")
set(CMAKE_REQUIRED_QUIET ON)
unset(REVOHUMAN_SDK_LINKS CACHE)
check_cxx_source_compiles([=[

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this list mirrors the calls in live_source.cpp by hand and will drift; linking the plugin checks the same thing and fails loudly instead of silently dropping the executable

#include <revohuman.h>
using Fn = void (*)();
#define RH_FN(f) reinterpret_cast<Fn>(&f)
int main(int argc, char**)
{
const Fn used[] = {
RH_FN(rh_init_options_init), RH_FN(rh_init), RH_FN(rh_shutdown), RH_FN(rh_version),
RH_FN(rh_set_log_callback), RH_FN(rh_last_error), RH_FN(rh_last_error_code), RH_FN(rh_error_class_name),
RH_FN(rh_kit_options_init), RH_FN(rh_kit_open),
RH_FN(rh_kit_close), RH_FN(rh_kit_identity), RH_FN(rh_kit_glove), RH_FN(rh_glove_info),
RH_FN(rh_joint_name), RH_FN(rh_joints_to_radians), RH_FN(rh_subscribe_options_init),
RH_FN(rh_glove_subscribe), RH_FN(rh_sub_recv_glove), RH_FN(rh_sub_dropped), RH_FN(rh_sub_close),
RH_FN(rh_glove_capture_ok), RH_FN(rh_glove_capture_cause), RH_FN(rh_scan_options_init),
RH_FN(rh_scan), RH_FN(rh_scan_count), RH_FN(rh_scan_get), RH_FN(rh_scan_free),
};
return used[argc] == nullptr;
}
]=] REVOHUMAN_SDK_LINKS)
cmake_pop_check_state()
if(REVOHUMAN_SDK_LINKS)
set(REVOHUMAN_LIVE_AVAILABLE ON)
add_library(revohuman_plugin::sdk UNKNOWN IMPORTED)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
add_library(revohuman_plugin::sdk UNKNOWN IMPORTED)
add_library(revohuman_plugin::sdk UNKNOWN IMPORTED GLOBAL)

so tests/cpp/plugins/revohuman/integration can link the target instead of the raw library path (its CMakeLists.txt:23)

set_target_properties(revohuman_plugin::sdk PROPERTIES
IMPORTED_LOCATION "${REVOHUMAN_SDK_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${REVOHUMAN_SDK_INCLUDE_DIR}")
message(STATUS "RevoHuman C SDK library ${REVOHUMAN_SDK_LIBRARY}: live source available "
"(installed next to the plugin)")
else()
message(STATUS "RevoHuman C SDK library ${REVOHUMAN_SDK_LIBRARY} lacks functions the live source "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the user asked for the plugin and the SDK is there but incomplete; fail the configure like sharpa_avatar does for a failed SDK check rather than quietly skipping the executable

"calls — skipping the plugin executable")
endif()
else()
message(STATUS "RevoHuman C SDK library not found in ${REVOHUMAN_SDK_ROOT}/lib — skipping the plugin executable")
endif()
set(REVOHUMAN_LIVE_AVAILABLE "${REVOHUMAN_LIVE_AVAILABLE}"
CACHE INTERNAL "RevoHuman live source links against the C SDK")

add_subdirectory(core)
add_subdirectory(live)
if(REVOHUMAN_LIVE_AVAILABLE)
add_subdirectory(kinematics)
endif()
add_subdirectory(app)

# One component for the plugin, its manifest, README and the SDK library (app/), so install.sh installs
# exactly these; a plain `cmake --install` still installs every component. No plugin, no manifest.
if(REVOHUMAN_LIVE_AVAILABLE)
install(FILES plugin.yaml README.md DESTINATION plugins/revohuman COMPONENT revohuman)
endif()
Loading