Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "tracker.hpp"

#include <cstdint>
#include <optional>
#include <vector>

namespace core
{

// Abstract base interface for opaque data channel tracker implementations.
// Returns raw bytes received from the XR_NV_opaque_data_channel extension.
class IOpaqueDataChannelTrackerImpl : public ITrackerImpl
{
public:
virtual std::optional<std::vector<uint8_t>> get_latest_message() const = 0;
};

} // namespace core
4 changes: 3 additions & 1 deletion src/core/deviceio_trackers/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20)
Expand All @@ -11,12 +11,14 @@ add_library(deviceio_trackers STATIC
generic_3axis_pedal_tracker.cpp
frame_metadata_tracker_oak.cpp
full_body_tracker_pico.cpp
opaque_data_channel_tracker.cpp
inc/deviceio_trackers/head_tracker.hpp
inc/deviceio_trackers/hand_tracker.hpp
inc/deviceio_trackers/controller_tracker.hpp
inc/deviceio_trackers/full_body_tracker_pico.hpp
inc/deviceio_trackers/generic_3axis_pedal_tracker.hpp
inc/deviceio_trackers/frame_metadata_tracker_oak.hpp
inc/deviceio_trackers/opaque_data_channel_tracker.hpp
)

target_include_directories(deviceio_trackers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <deviceio_base/opaque_data_channel_tracker_base.hpp>

#include <array>
#include <cstdint>
#include <optional>
#include <string_view>
#include <vector>

namespace core
{

// Receives arbitrary bytes from a CloudXR opaque data channel identified by UUID.
// The channel is created and polled by the live implementation; this public
// tracker exposes the latest received message to Python DeviceIO source nodes.
class OpaqueDataChannelTracker : public ITracker
{
public:
explicit OpaqueDataChannelTracker(std::array<uint8_t, 16> uuid);

std::string_view get_name() const override;

std::optional<std::vector<uint8_t>> get_latest_message(const ITrackerSession& session) const;

const std::array<uint8_t, 16>& get_uuid() const;

private:
static constexpr const char* TRACKER_NAME = "OpaqueDataChannelTracker";
std::array<uint8_t, 16> uuid_;
};

} // namespace core
29 changes: 29 additions & 0 deletions src/core/deviceio_trackers/cpp/opaque_data_channel_tracker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#include "inc/deviceio_trackers/opaque_data_channel_tracker.hpp"

namespace core
{

OpaqueDataChannelTracker::OpaqueDataChannelTracker(std::array<uint8_t, 16> uuid)
: uuid_(uuid)
{
}

std::string_view OpaqueDataChannelTracker::get_name() const
{
return TRACKER_NAME;
}

std::optional<std::vector<uint8_t>> OpaqueDataChannelTracker::get_latest_message(const ITrackerSession& session) const
{
return static_cast<const IOpaqueDataChannelTrackerImpl&>(session.get_tracker_impl(*this)).get_latest_message();
}

const std::array<uint8_t, 16>& OpaqueDataChannelTracker::get_uuid() const
{
return uuid_;
}

} // namespace core
27 changes: 27 additions & 0 deletions src/core/deviceio_trackers/python/tracker_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
#include <deviceio_trackers/generic_3axis_pedal_tracker.hpp>
#include <deviceio_trackers/hand_tracker.hpp>
#include <deviceio_trackers/head_tracker.hpp>
#include <deviceio_trackers/opaque_data_channel_tracker.hpp>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <schema/hand_generated.h>

#include <cstring>

namespace py = pybind11;

PYBIND11_MODULE(_deviceio_trackers, m)
Expand Down Expand Up @@ -95,6 +98,30 @@ PYBIND11_MODULE(_deviceio_trackers, m)
{ return self.get_body_pose(session); },
py::arg("session"), "Get full body pose tracked state (data is None if inactive)");

py::class_<core::OpaqueDataChannelTracker, core::ITracker, std::shared_ptr<core::OpaqueDataChannelTracker>>(
m, "OpaqueDataChannelTracker")
.def(py::init([](py::bytes uuid_bytes)
{
std::string raw = uuid_bytes;
if (raw.size() != 16)
throw std::invalid_argument("UUID must be exactly 16 bytes");
std::array<uint8_t, 16> arr;
std::memcpy(arr.data(), raw.data(), 16);
return std::make_shared<core::OpaqueDataChannelTracker>(arr);
}),
py::arg("uuid"), "Construct with a 16-byte UUID identifying the data channel")
.def(
"get_latest_message",
[](const core::OpaqueDataChannelTracker& self,
const core::ITrackerSession& session) -> std::optional<py::bytes>
{
auto msg = self.get_latest_message(session);
if (!msg)
return std::nullopt;
return py::bytes(reinterpret_cast<const char*>(msg->data()), msg->size());
},
py::arg("session"), "Get the latest received message bytes, or None if no message this frame");

m.attr("NUM_JOINTS") = static_cast<int>(core::HandJoint_NUM_JOINTS);
m.attr("JOINT_PALM") = static_cast<int>(core::HandJoint_PALM);
m.attr("JOINT_WRIST") = static_cast<int>(core::HandJoint_WRIST);
Expand Down
4 changes: 3 additions & 1 deletion src/core/live_trackers/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20)
Expand All @@ -12,6 +12,7 @@ add_library(live_trackers STATIC
live_full_body_tracker_pico_impl.cpp
live_generic_3axis_pedal_tracker_impl.cpp
live_frame_metadata_tracker_oak_impl.cpp
live_opaque_data_channel_tracker_impl.cpp
inc/live_trackers/schema_tracker_base.hpp
inc/live_trackers/schema_tracker.hpp
inc/live_trackers/live_deviceio_factory.hpp
Expand All @@ -21,6 +22,7 @@ add_library(live_trackers STATIC
live_full_body_tracker_pico_impl.hpp
live_generic_3axis_pedal_tracker_impl.hpp
live_frame_metadata_tracker_oak_impl.hpp
live_opaque_data_channel_tracker_impl.hpp
)

target_include_directories(live_trackers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#pragma once
Expand Down Expand Up @@ -32,6 +32,8 @@ class HandTracker;
class IHandTrackerImpl;
class HeadTracker;
class IHeadTrackerImpl;
class OpaqueDataChannelTracker;
class IOpaqueDataChannelTrackerImpl;
struct OpenXRSessionHandles;

/**
Expand Down Expand Up @@ -61,6 +63,8 @@ class LiveDeviceIOFactory
const Generic3AxisPedalTracker* tracker);
std::unique_ptr<IFrameMetadataTrackerOakImpl> create_frame_metadata_tracker_oak_impl(
const FrameMetadataTrackerOak* tracker);
std::unique_ptr<IOpaqueDataChannelTrackerImpl> create_opaque_data_channel_tracker_impl(
const OpaqueDataChannelTracker* tracker);

private:
bool should_record(const ITracker* tracker) const;
Expand Down
17 changes: 16 additions & 1 deletion src/core/live_trackers/cpp/live_deviceio_factory.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#include "inc/live_trackers/live_deviceio_factory.hpp"
Expand All @@ -9,13 +9,15 @@
#include "live_generic_3axis_pedal_tracker_impl.hpp"
#include "live_hand_tracker_impl.hpp"
#include "live_head_tracker_impl.hpp"
#include "live_opaque_data_channel_tracker_impl.hpp"

#include <deviceio_trackers/controller_tracker.hpp>
#include <deviceio_trackers/frame_metadata_tracker_oak.hpp>
#include <deviceio_trackers/full_body_tracker_pico.hpp>
#include <deviceio_trackers/generic_3axis_pedal_tracker.hpp>
#include <deviceio_trackers/hand_tracker.hpp>
#include <deviceio_trackers/head_tracker.hpp>
#include <deviceio_trackers/opaque_data_channel_tracker.hpp>
#include <oxr_utils/oxr_time.hpp>

#include <cassert>
Expand Down Expand Up @@ -77,6 +79,12 @@ std::unique_ptr<ITrackerImpl> try_create_oak_impl(LiveDeviceIOFactory& factory,
return typed ? factory.create_frame_metadata_tracker_oak_impl(typed) : nullptr;
}

std::unique_ptr<ITrackerImpl> try_create_opaque_channel_impl(LiveDeviceIOFactory& factory, const ITracker& tracker)
{
auto* typed = dynamic_cast<const OpaqueDataChannelTracker*>(&tracker);
return typed ? factory.create_opaque_data_channel_tracker_impl(typed) : nullptr;
}

using CollectExtensionsFn = bool (*)(const ITracker&, std::set<std::string>&);
using TryCreateFn = std::unique_ptr<ITrackerImpl> (*)(LiveDeviceIOFactory&, const ITracker&);

Expand All @@ -94,6 +102,7 @@ inline const TrackerDispatchEntry k_tracker_dispatch[] = {
{ &try_add_extensions<FullBodyTrackerPico, LiveFullBodyTrackerPicoImpl>, &try_create_full_body_pico_impl },
{ &try_add_extensions<Generic3AxisPedalTracker, LiveGeneric3AxisPedalTrackerImpl>, &try_create_generic_pedal_impl },
{ &try_add_extensions<FrameMetadataTrackerOak, LiveFrameMetadataTrackerOakImpl>, &try_create_oak_impl },
{ &try_add_extensions<OpaqueDataChannelTracker, LiveOpaqueDataChannelTrackerImpl>, &try_create_opaque_channel_impl },
};

} // namespace
Expand Down Expand Up @@ -235,4 +244,10 @@ std::unique_ptr<IFrameMetadataTrackerOakImpl> LiveDeviceIOFactory::create_frame_
return std::make_unique<LiveFrameMetadataTrackerOakImpl>(handles_, tracker, std::move(channels));
}

std::unique_ptr<IOpaqueDataChannelTrackerImpl> LiveDeviceIOFactory::create_opaque_data_channel_tracker_impl(
const OpaqueDataChannelTracker* tracker)
{
return std::make_unique<LiveOpaqueDataChannelTrackerImpl>(handles_, tracker);
}

} // namespace core
133 changes: 133 additions & 0 deletions src/core/live_trackers/cpp/live_opaque_data_channel_tracker_impl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#include "live_opaque_data_channel_tracker_impl.hpp"

#include <deviceio_trackers/opaque_data_channel_tracker.hpp>
#include <oxr_utils/oxr_funcs.hpp>

#include <cstring>
#include <iostream>
#include <stdexcept>

namespace core
{

LiveOpaqueDataChannelTrackerImpl::LiveOpaqueDataChannelTrackerImpl(const OpenXRSessionHandles& handles,
const OpaqueDataChannelTracker* tracker)
: instance_(handles.instance)
{
auto core_funcs = OpenXRCoreFunctions::load(handles.instance, handles.xrGetInstanceProcAddr);

XrSystemId system_id;
XrSystemGetInfo system_info{ XR_TYPE_SYSTEM_GET_INFO };
system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;

XrResult result = core_funcs.xrGetSystem(handles.instance, &system_info, &system_id);
if (XR_FAILED(result))
{
throw std::runtime_error("[OpaqueDataChannel] Failed to get OpenXR system: " + std::to_string(result));
}

loadExtensionFunction(handles.instance, handles.xrGetInstanceProcAddr, "xrCreateOpaqueDataChannelNV",
reinterpret_cast<PFN_xrVoidFunction*>(&pfn_create_));
loadExtensionFunction(handles.instance, handles.xrGetInstanceProcAddr, "xrDestroyOpaqueDataChannelNV",
reinterpret_cast<PFN_xrVoidFunction*>(&pfn_destroy_));
loadExtensionFunction(handles.instance, handles.xrGetInstanceProcAddr, "xrGetOpaqueDataChannelStateNV",
reinterpret_cast<PFN_xrVoidFunction*>(&pfn_get_state_));
loadExtensionFunction(handles.instance, handles.xrGetInstanceProcAddr, "xrReceiveOpaqueDataChannelNV",
reinterpret_cast<PFN_xrVoidFunction*>(&pfn_receive_));
loadExtensionFunction(handles.instance, handles.xrGetInstanceProcAddr, "xrShutdownOpaqueDataChannelNV",
reinterpret_cast<PFN_xrVoidFunction*>(&pfn_shutdown_));

XrOpaqueDataChannelCreateInfoNV create_info{};
create_info.type = XR_TYPE_OPAQUE_DATA_CHANNEL_CREATE_INFO_NV;
create_info.next = nullptr;
create_info.systemId = system_id;

const auto& uuid = tracker->get_uuid();
static_assert(sizeof(create_info.uuid.data) == 16);
std::memcpy(create_info.uuid.data, uuid.data(), 16);

result = pfn_create_(handles.instance, &create_info, &channel_);
if (XR_FAILED(result))
{
throw std::runtime_error("[OpaqueDataChannel] xrCreateOpaqueDataChannelNV failed: " + std::to_string(result));
}

std::cout << "OpaqueDataChannelTracker initialized (channel created, waiting for connection)" << std::endl;
}

LiveOpaqueDataChannelTrackerImpl::~LiveOpaqueDataChannelTrackerImpl()
{
if (channel_ != XR_NULL_HANDLE)
{
if (status_ == XR_OPAQUE_DATA_CHANNEL_STATUS_CONNECTED_NV)
{
pfn_shutdown_(channel_);
}
pfn_destroy_(channel_);
channel_ = XR_NULL_HANDLE;
}
}

void LiveOpaqueDataChannelTrackerImpl::update(int64_t monotonic_time_ns)
{
last_update_time_ = monotonic_time_ns;
latest_message_.reset();

if (channel_ == XR_NULL_HANDLE)
{
return;
}

XrOpaqueDataChannelStateNV state{};
state.type = XR_TYPE_OPAQUE_DATA_CHANNEL_STATE_NV;
state.next = nullptr;

XrResult result = pfn_get_state_(channel_, &state);
if (XR_FAILED(result))
{
return;
}
status_ = state.state;

if (status_ != XR_OPAQUE_DATA_CHANNEL_STATUS_CONNECTED_NV &&
status_ != XR_OPAQUE_DATA_CHANNEL_STATUS_SHUTTING_NV)
{
return;
}

// Drain all queued messages, keeping only the latest.
// xrReceiveOpaqueDataChannelNV dequeues one message per call.
std::vector<uint8_t> buffer;
while (true)
{
uint32_t byte_count = 0;
result = pfn_receive_(channel_, 0, &byte_count, nullptr);
if (result == XR_ERROR_CHANNEL_NOT_CONNECTED_NV || byte_count == 0)
{
break;
}
if (XR_FAILED(result))
{
break;
}

buffer.resize(byte_count);
result = pfn_receive_(channel_, byte_count, &byte_count, buffer.data());
if (XR_FAILED(result))
{
break;
}

latest_message_ = buffer;
}
}

std::optional<std::vector<uint8_t>> LiveOpaqueDataChannelTrackerImpl::get_latest_message() const
{
return latest_message_;
}

} // namespace core
Loading
Loading