Skip to content

Haptikos hands plugin#486

Open
giorgosbabanis wants to merge 6 commits intoNVIDIA:mainfrom
The-Magos:haptikos-plugin
Open

Haptikos hands plugin#486
giorgosbabanis wants to merge 6 commits intoNVIDIA:mainfrom
The-Magos:haptikos-plugin

Conversation

@giorgosbabanis
Copy link
Copy Markdown

@giorgosbabanis giorgosbabanis commented May 8, 2026

A plugin that utilizes the Haptikos Exoskeletons through our API to inject hand data into an OpenXR session replacing the headset's optical hand tracking. It is modeled after controller synthetic hands plugin.
It was tested and developed on an Ubuntu 24.04 computer with an Ada RTX 6000 48 GB, using the Meta Quest 3 Headset. It does require a controller to calculate the position of the wrist in 3D space, so it should work with any Headset that has a controller and is compatible with the Isaac Teleop framework. It has a dependency on our API, but if it is not provided the plugin is just skipped.
To validate that the plugin provides the correct data we teleoperated the Fourier GR1T2 robot inside IsaacLab using the pick and place task.

Summary by CodeRabbit

  • New Features

    • Added Haptikos plugin that integrates hand tracking with OpenXR runtime
    • Reads controller pose and hand tracking data from Haptikos Core App
    • Supports left and right hand joint pose injection at 90Hz target cadence
  • Documentation

    • Added comprehensive setup guide and usage instructions for the Haptikos plugin
  • Chores

    • Updated build configuration to include new Haptikos plugin

Signed-off-by: Giorgos Babanis <giorgosbaba@hotmail.com>
Signed-off-by: Giorgos Babanis <giorgosbaba@hotmail.com>
Signed-off-by: Giorgos Babanis <giorgosbaba@hotmail.com>
Signed-off-by: Giorgos Babanis <giorgosbaba@hotmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a complete Haptikos Hands Plugin for Isaac Teleop that integrates hand tracking data from Haptikos exoskeletons into OpenXR. The implementation includes a HaptikosHandsPlugin class that runs a 90Hz worker thread reading controller and hand pose snapshots from Haptikos, transforming them via coordinate remapping, and injecting synthesized hand joint locations into OpenXR. Build configuration conditionally enables the plugin when HaptikosAPI is available, installs required shared libraries, and includes comprehensive documentation for setup and usage.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main changeset: adding a new Haptikos hands plugin to the codebase with supporting build configuration, documentation, and implementation files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

📝 Docs preview is not auto-deployed for fork PRs.

A maintainer with write access to NVIDIA/IsaacTeleop can deploy a preview by
commenting /preview-docs on this PR. Once deployed, the preview
will live at:

https://nvidia.github.io/IsaacTeleop/preview/pr-486/

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/plugins/haptikos/haptikos_hands_plugin.cpp`:
- Around line 82-95: The catch blocks in HaptikosHandsPlugin's update thread
currently call std::exit(1), which force-terminates the process; instead set
m_running = false, ensure m_left_injector.reset() and m_right_injector.reset()
are called, and return from the thread so the worker loop exits normally and
destructor/RAII cleanup runs; update the catch(...) and catch(const
std::exception& e) handlers in the update function to log the error, set
m_running = false, reset injectors, and return rather than calling std::exit.
- Around line 103-121: The m_client.GetData(...) calls inside worker_thread
(e.g., the call used to populate right_data and the symmetric left call) can
throw and are not currently caught; wrap each GetData invocation (or the loop
body around calculate_hand_pose and injector logic in worker_thread) in a
try-catch that catches std::exception (and a catch-all) and logs the error, then
continues the loop so the thread doesn't terminate; ensure you reference
m_client.GetData, right_data/left_data and the injector logic
(m_right_injector/m_left_injector) when adding the try-catch and avoid letting
exceptions escape worker_thread.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1a19f2f3-a796-4656-a1bb-d3eab88203fa

📥 Commits

Reviewing files that changed from the base of the PR and between c7054ef and 06492af.

📒 Files selected for processing (8)
  • CMakeLists.txt
  • src/plugins/haptikos/.gitignore
  • src/plugins/haptikos/CMakeLists.txt
  • src/plugins/haptikos/README.md
  • src/plugins/haptikos/haptikos_hands_plugin.cpp
  • src/plugins/haptikos/inc/haptikos/haptikos_hands_plugin.hpp
  • src/plugins/haptikos/main.cpp
  • src/plugins/haptikos/plugin.yaml

Comment on lines +82 to +95
catch (const std::exception& e)
{
std::cerr << "HaptikosHandsPlugin update error: " << e.what() << std::endl;
m_left_injector.reset();
m_right_injector.reset();
std::exit(1);
}
catch (...)
{
std::cerr << "HaptikosHandsPlugin update error: unknown exception" << std::endl;
m_left_injector.reset();
m_right_injector.reset();
std::exit(1);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

std::exit(1) from worker thread bypasses all cleanup.

Calling std::exit(1) here terminates the process immediately without running the HaptikosHandsPlugin destructor or main's exception handlers. The reset() calls before exit don't help—std::exit() won't return to let them complete properly.

Set m_running = false and return from the thread instead. Let the main loop detect the shutdown and handle cleanup via RAII.

Proposed fix
         catch (const std::exception& e)
         {
             std::cerr << "HaptikosHandsPlugin update error: " << e.what() << std::endl;
             m_left_injector.reset();
             m_right_injector.reset();
-            std::exit(1);
+            m_running = false;
+            return;
         }
         catch (...)
         {
             std::cerr << "HaptikosHandsPlugin update error: unknown exception" << std::endl;
             m_left_injector.reset();
             m_right_injector.reset();
-            std::exit(1);
+            m_running = false;
+            return;
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/haptikos/haptikos_hands_plugin.cpp` around lines 82 - 95, The
catch blocks in HaptikosHandsPlugin's update thread currently call std::exit(1),
which force-terminates the process; instead set m_running = false, ensure
m_left_injector.reset() and m_right_injector.reset() are called, and return from
the thread so the worker loop exits normally and destructor/RAII cleanup runs;
update the catch(...) and catch(const std::exception& e) handlers in the update
function to log the error, set m_running = false, reset injectors, and return
rather than calling std::exit.

Comment on lines +103 to +121
if (right_tracked.data)
{
Haptikos::HandData right_data = m_client.GetData(true, Haptikos::GlobalToWrist, true, true, false);
bool valid_wrist = false;
XrPosef rigth_controller = oxr_utils::get_aim_pose(*right_tracked.data, valid_wrist);

if (right_data.IsValid() == 1 && valid_wrist)
{
calculate_hand_pose(right_joints, right_data, rigth_controller);
if (!m_right_injector)
{
const auto handles = m_session->get_handles();
m_right_injector = std::make_unique<plugin_utils::HandInjector>(
handles.instance, handles.session, XR_HAND_RIGHT_EXT, handles.space);
}

m_right_injector->push(right_joints, time);
rigth_published = true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

m_client.GetData() can throw but is not caught.

The GetData() calls at lines 105 and 134 are outside the try-catch block. If the Haptikos API throws an exception here, it will propagate out of worker_thread(), causing std::terminate() since threads don't propagate exceptions to the caller.

Either wrap the entire loop body in the try-catch, or specifically handle potential exceptions from the Haptikos client.

Proposed fix: extend try-catch scope
     while (m_running)
     {
         auto frame_start = std::chrono::steady_clock::now();
+        try
+        {
             core::ControllerSnapshotTrackedT left_tracked;
             core::ControllerSnapshotTrackedT right_tracked;
 
             core::HandPoseTrackedT left_hand;
             core::HandPoseTrackedT rigth_hand;
 
-        try
-        {
             // Update DeviceIOSession (handles time and tracker updates)
             m_deviceio_session->update();
 
             // Read tracker data in the same exception boundary as update.
             left_tracked = m_controller_tracker->get_left_controller(*m_deviceio_session);
             right_tracked = m_controller_tracker->get_right_controller(*m_deviceio_session);
-        }
-        catch (const std::exception& e)
-        // ... rest of try-catch stays the same but now covers GetData() calls too
+
+            // ... rest of loop body including GetData() calls ...
+
+            std::this_thread::sleep_until(frame_start + target_frame_duration);
+        }
+        catch (const std::exception& e)
+        {
+            std::cerr << "HaptikosHandsPlugin error: " << e.what() << std::endl;
+            m_left_injector.reset();
+            m_right_injector.reset();
+            m_running = false;
+            return;
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/haptikos/haptikos_hands_plugin.cpp` around lines 103 - 121, The
m_client.GetData(...) calls inside worker_thread (e.g., the call used to
populate right_data and the symmetric left call) can throw and are not currently
caught; wrap each GetData invocation (or the loop body around
calculate_hand_pose and injector logic in worker_thread) in a try-catch that
catches std::exception (and a catch-all) and logs the error, then continues the
loop so the thread doesn't terminate; ensure you reference m_client.GetData,
right_data/left_data and the injector logic (m_right_injector/m_left_injector)
when adding the try-catch and avoid letting exceptions escape worker_thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant