Skip to content

Commit a6a76f0

Browse files
committed
Add message channel tracker
1 parent 4dde48a commit a6a76f0

25 files changed

Lines changed: 1297 additions & 0 deletions
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
/*!
4+
* @file
5+
* @brief Header for XR_NV_opaque_data_channel extension.
6+
*/
7+
#ifndef XR_NV_OPAQUE_DATA_CHANNEL_H
8+
#define XR_NV_OPAQUE_DATA_CHANNEL_H 1
9+
10+
#include "openxr_extension_helpers.h"
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
#define XR_NV_opaque_data_channel 1
17+
#define XR_NV_opaque_data_channel_SPEC_VERSION 1
18+
#define XR_NV_OPAQUE_DATA_CHANNEL_EXTENSION_NAME "XR_NV_opaque_data_channel"
19+
20+
XR_DEFINE_HANDLE(XrOpaqueDataChannelNV)
21+
22+
XR_STRUCT_ENUM(XR_TYPE_OPAQUE_DATA_CHANNEL_CREATE_INFO_NV, 1000526001);
23+
XR_STRUCT_ENUM(XR_TYPE_OPAQUE_DATA_CHANNEL_STATE_NV, 1000526002);
24+
25+
XR_RESULT_ENUM(XR_ERROR_CHANNEL_ALREADY_CREATED_NV, -1000526000);
26+
XR_RESULT_ENUM(XR_ERROR_CHANNEL_NOT_CONNECTED_NV, -1000526001);
27+
28+
typedef enum XrOpaqueDataChannelStatusNV {
29+
XR_OPAQUE_DATA_CHANNEL_STATUS_CONNECTING_NV = 0,
30+
XR_OPAQUE_DATA_CHANNEL_STATUS_CONNECTED_NV = 1,
31+
XR_OPAQUE_DATA_CHANNEL_STATUS_SHUTTING_NV = 2,
32+
XR_OPAQUE_DATA_CHANNEL_STATUS_DISCONNECTED_NV = 3,
33+
XR_OPAQUE_DATA_CHANNEL_STATUS_MAX_ENUM = 0x7FFFFFFF,
34+
} XrOpaqueDataChannelStatusNV;
35+
36+
typedef struct XrOpaqueDataChannelCreateInfoNV {
37+
XrStructureType type;
38+
const void* next;
39+
XrSystemId systemId;
40+
XrUuidEXT uuid;
41+
} XrOpaqueDataChannelCreateInfoNV;
42+
43+
typedef struct XrOpaqueDataChannelStateNV {
44+
XrStructureType type;
45+
void* next;
46+
XrOpaqueDataChannelStatusNV state;
47+
} XrOpaqueDataChannelStateNV;
48+
49+
typedef XrResult(XRAPI_PTR* PFN_xrCreateOpaqueDataChannelNV)(XrInstance instance,
50+
const XrOpaqueDataChannelCreateInfoNV* createInfo,
51+
XrOpaqueDataChannelNV* opaqueDataChannel);
52+
typedef XrResult(XRAPI_PTR* PFN_xrDestroyOpaqueDataChannelNV)(XrOpaqueDataChannelNV opaqueDataChannel);
53+
typedef XrResult(XRAPI_PTR* PFN_xrGetOpaqueDataChannelStateNV)(XrOpaqueDataChannelNV opaqueDataChannel,
54+
XrOpaqueDataChannelStateNV* state);
55+
typedef XrResult(XRAPI_PTR* PFN_xrSendOpaqueDataChannelNV)(XrOpaqueDataChannelNV opaqueDataChannel,
56+
uint32_t opaqueDataInputCount,
57+
const uint8_t* opaqueDatas);
58+
typedef XrResult(XRAPI_PTR* PFN_xrReceiveOpaqueDataChannelNV)(XrOpaqueDataChannelNV opaqueDataChannel,
59+
uint32_t opaqueDataCapacityInput,
60+
uint32_t* opaqueDataCountOutput,
61+
uint8_t* opaqueDatas);
62+
typedef XrResult(XRAPI_PTR* PFN_xrShutdownOpaqueDataChannelNV)(XrOpaqueDataChannelNV opaqueDataChannel);
63+
64+
#ifdef __cplusplus
65+
}
66+
#endif
67+
68+
#endif
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python3
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""
6+
Message channel example using TeleopSession + retargeting source/sink nodes.
7+
8+
Behavior:
9+
- Prints any incoming messages each frame.
10+
- Once channel status is CONNECTED, sends one message every second.
11+
"""
12+
13+
import argparse
14+
import sys
15+
import time
16+
import uuid
17+
18+
from isaacteleop.retargeting_engine.deviceio_source_nodes import (
19+
MessageChannelConnectionStatus,
20+
message_channel_config,
21+
)
22+
from isaacteleop.retargeting_engine.interface import TensorGroup
23+
from isaacteleop.schema import MessageChannelMessages, MessageChannelMessagesTrackedT
24+
from isaacteleop.teleop_session_manager import TeleopSession, TeleopSessionConfig
25+
26+
27+
def _parse_uuid_bytes(uuid_text: str) -> bytes:
28+
"""Parse canonical UUID text to 16-byte payload."""
29+
return uuid.UUID(uuid_text).bytes
30+
31+
32+
def _enqueue_outbound_message(sink, payload: bytes) -> None:
33+
"""Push one outbound message through MessageChannelSink."""
34+
tg = TensorGroup(sink.input_spec()["messages_tracked"])
35+
tg[0] = MessageChannelMessagesTrackedT([MessageChannelMessages(payload)])
36+
sink.compute({"messages_tracked": tg}, {})
37+
38+
39+
def main() -> int:
40+
parser = argparse.ArgumentParser(
41+
description="Message channel TeleopSession example"
42+
)
43+
parser.add_argument(
44+
"--channel-uuid",
45+
type=str,
46+
required=True,
47+
help="Message channel UUID (canonical form, e.g. 550e8400-e29b-41d4-a716-446655440000)",
48+
)
49+
parser.add_argument(
50+
"--channel-name",
51+
type=str,
52+
default="example_message_channel",
53+
help="Optional channel display name",
54+
)
55+
parser.add_argument(
56+
"--outbound-queue-capacity",
57+
type=int,
58+
default=256,
59+
help="Bounded outbound queue length",
60+
)
61+
args = parser.parse_args()
62+
63+
channel_uuid = _parse_uuid_bytes(args.channel_uuid)
64+
65+
source, sink = message_channel_config(
66+
name="message_channel",
67+
channel_uuid=channel_uuid,
68+
channel_name=args.channel_name,
69+
outbound_queue_capacity=args.outbound_queue_capacity,
70+
)
71+
72+
config = TeleopSessionConfig(
73+
app_name="MessageChannelExample",
74+
pipeline=source,
75+
)
76+
77+
print("=" * 80)
78+
print("Message Channel TeleopSession Example")
79+
print("=" * 80)
80+
print(f"Channel UUID: {args.channel_uuid}")
81+
print(f"Channel Name: {args.channel_name}")
82+
print("Press Ctrl+C to exit.")
83+
print()
84+
85+
send_counter = 0
86+
last_send_time = 0.0
87+
88+
with TeleopSession(config) as session:
89+
while True:
90+
result = session.step()
91+
status = result["status"][0]
92+
messages_tracked = result["messages_tracked"][0]
93+
messages = (
94+
messages_tracked.data if messages_tracked.data is not None else []
95+
)
96+
97+
for msg in messages:
98+
payload = bytes(msg.payload)
99+
try:
100+
decoded = payload.decode("utf-8")
101+
print(f"[rx] {decoded}")
102+
except UnicodeDecodeError:
103+
print(f"[rx] 0x{payload.hex()}")
104+
105+
now = time.monotonic()
106+
if (
107+
status == MessageChannelConnectionStatus.CONNECTED
108+
and now - last_send_time >= 1.0
109+
):
110+
payload_text = f"hello #{send_counter} @ {time.time():.3f}"
111+
_enqueue_outbound_message(sink, payload_text.encode("utf-8"))
112+
print(f"[tx] {payload_text}")
113+
last_send_time = now
114+
send_counter += 1
115+
116+
time.sleep(0.01)
117+
118+
119+
if __name__ == "__main__":
120+
try:
121+
sys.exit(main())
122+
except KeyboardInterrupt:
123+
print("\nExiting.")
124+
sys.exit(0)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#pragma once
5+
6+
#include "tracker.hpp"
7+
8+
#include <cstdint>
9+
#include <vector>
10+
11+
namespace core
12+
{
13+
14+
struct MessageChannelMessagesT;
15+
struct MessageChannelMessagesTrackedT;
16+
17+
enum class MessageChannelStatus : int32_t
18+
{
19+
CONNECTING = 0,
20+
CONNECTED = 1,
21+
SHUTTING = 2,
22+
DISCONNECTED = 3,
23+
UNKNOWN = -1,
24+
};
25+
26+
class IMessageChannelTrackerImpl : public ITrackerImpl
27+
{
28+
public:
29+
virtual MessageChannelStatus get_status() const = 0;
30+
virtual const MessageChannelMessagesTrackedT& get_messages() const = 0;
31+
virtual void send_message(const std::vector<uint8_t>& payload) const = 0;
32+
};
33+
34+
} // namespace core

src/core/deviceio_trackers/cpp/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ add_library(deviceio_trackers STATIC
88
hand_tracker.cpp
99
head_tracker.cpp
1010
controller_tracker.cpp
11+
message_channel_tracker.cpp
1112
generic_3axis_pedal_tracker.cpp
1213
frame_metadata_tracker_oak.cpp
1314
full_body_tracker_pico.cpp
1415
inc/deviceio_trackers/head_tracker.hpp
1516
inc/deviceio_trackers/hand_tracker.hpp
1617
inc/deviceio_trackers/controller_tracker.hpp
18+
inc/deviceio_trackers/message_channel_tracker.hpp
1719
inc/deviceio_trackers/full_body_tracker_pico.hpp
1820
inc/deviceio_trackers/generic_3axis_pedal_tracker.hpp
1921
inc/deviceio_trackers/frame_metadata_tracker_oak.hpp
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#pragma once
5+
6+
#include <deviceio_base/message_channel_tracker_base.hpp>
7+
#include <schema/message_channel_generated.h>
8+
9+
#include <array>
10+
#include <cstddef>
11+
#include <cstdint>
12+
#include <memory>
13+
#include <string>
14+
#include <vector>
15+
16+
namespace core
17+
{
18+
19+
class MessageChannelTracker : public ITracker
20+
{
21+
public:
22+
static constexpr size_t DEFAULT_MAX_MESSAGE_SIZE = 64 * 1024;
23+
static constexpr size_t CHANNEL_UUID_SIZE = 16;
24+
25+
explicit MessageChannelTracker(const std::array<uint8_t, CHANNEL_UUID_SIZE>& channel_uuid,
26+
const std::string& channel_name = "",
27+
size_t max_message_size = DEFAULT_MAX_MESSAGE_SIZE);
28+
29+
std::string_view get_name() const override
30+
{
31+
return TRACKER_NAME;
32+
}
33+
34+
MessageChannelStatus get_status(const ITrackerSession& session) const;
35+
const MessageChannelMessagesTrackedT& get_messages(const ITrackerSession& session) const;
36+
void send_message(const ITrackerSession& session, const std::vector<uint8_t>& payload) const;
37+
38+
const std::array<uint8_t, CHANNEL_UUID_SIZE>& channel_uuid() const
39+
{
40+
return channel_uuid_;
41+
}
42+
43+
const std::string& channel_name() const
44+
{
45+
return channel_name_;
46+
}
47+
48+
size_t max_message_size() const
49+
{
50+
return max_message_size_;
51+
}
52+
53+
private:
54+
static constexpr const char* TRACKER_NAME = "MessageChannelTracker";
55+
56+
std::array<uint8_t, CHANNEL_UUID_SIZE> channel_uuid_{};
57+
std::string channel_name_;
58+
size_t max_message_size_{ DEFAULT_MAX_MESSAGE_SIZE };
59+
};
60+
61+
} // namespace core
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#include "inc/deviceio_trackers/message_channel_tracker.hpp"
5+
6+
#include <stdexcept>
7+
8+
namespace core
9+
{
10+
11+
MessageChannelTracker::MessageChannelTracker(const std::array<uint8_t, CHANNEL_UUID_SIZE>& channel_uuid,
12+
const std::string& channel_name,
13+
size_t max_message_size)
14+
: channel_uuid_(channel_uuid), channel_name_(channel_name), max_message_size_(max_message_size)
15+
{
16+
if (max_message_size_ == 0)
17+
{
18+
throw std::invalid_argument("MessageChannelTracker: max_message_size must be > 0");
19+
}
20+
}
21+
22+
MessageChannelStatus MessageChannelTracker::get_status(const ITrackerSession& session) const
23+
{
24+
return static_cast<const IMessageChannelTrackerImpl&>(session.get_tracker_impl(*this)).get_status();
25+
}
26+
27+
const MessageChannelMessagesTrackedT& MessageChannelTracker::get_messages(const ITrackerSession& session) const
28+
{
29+
return static_cast<const IMessageChannelTrackerImpl&>(session.get_tracker_impl(*this)).get_messages();
30+
}
31+
32+
void MessageChannelTracker::send_message(const ITrackerSession& session, const std::vector<uint8_t>& payload) const
33+
{
34+
static_cast<const IMessageChannelTrackerImpl&>(session.get_tracker_impl(*this)).send_message(payload);
35+
}
36+
37+
} // namespace core

src/core/deviceio_trackers/python/deviceio_trackers_init.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
HandTracker,
99
HeadTracker,
1010
ControllerTracker,
11+
MessageChannelStatus,
12+
MessageChannelTracker,
1113
FrameMetadataTrackerOak,
1214
Generic3AxisPedalTracker,
1315
FullBodyTrackerPico,
@@ -21,6 +23,8 @@
2123

2224
__all__ = [
2325
"ControllerTracker",
26+
"MessageChannelStatus",
27+
"MessageChannelTracker",
2428
"FrameMetadataTrackerOak",
2529
"FullBodyTrackerPico",
2630
"Generic3AxisPedalTracker",

0 commit comments

Comments
 (0)