You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
6.0 KiB
155 lines
6.0 KiB
import numpy as np
|
|
import xrobotoolkit_sdk as xrt
|
|
|
|
# xrt interface is defined in
|
|
# https://github.com/XR-Robotics/XRoboToolkit-PC-Service-Pybind/blob/main/bindings/py_bindings.cpp
|
|
|
|
|
|
class XrClient:
|
|
"""Client for the XrClient SDK to interact with XR devices."""
|
|
|
|
def __init__(self):
|
|
"""Initializes the XrClient and the SDK."""
|
|
xrt.init()
|
|
print("XRoboToolkit SDK initialized.")
|
|
|
|
def get_pose_by_name(self, name: str) -> np.ndarray:
|
|
"""Returns the pose of the specified device by name.
|
|
Valid names: "left_controller", "right_controller", "headset".
|
|
Pose is [x, y, z, qx, qy, qz, qw]."""
|
|
if name == "left_controller":
|
|
return xrt.get_left_controller_pose()
|
|
elif name == "right_controller":
|
|
return xrt.get_right_controller_pose()
|
|
elif name == "headset":
|
|
return xrt.get_headset_pose()
|
|
else:
|
|
raise ValueError(
|
|
f"Invalid name: {name}. Valid names are: 'left_controller', 'right_controller', 'headset'."
|
|
)
|
|
|
|
def get_key_value_by_name(self, name: str) -> float:
|
|
"""Returns the trigger/grip value by name (float).
|
|
Valid names: "left_trigger", "right_trigger", "left_grip", "right_grip".
|
|
"""
|
|
if name == "left_trigger":
|
|
return xrt.get_left_trigger()
|
|
elif name == "right_trigger":
|
|
return xrt.get_right_trigger()
|
|
elif name == "left_grip":
|
|
return xrt.get_left_grip()
|
|
elif name == "right_grip":
|
|
return xrt.get_right_grip()
|
|
else:
|
|
raise ValueError(
|
|
f"Invalid name: {name}. Valid names are: \
|
|
'left_trigger', 'right_trigger', 'left_grip', 'right_grip'."
|
|
)
|
|
|
|
def get_button_state_by_name(self, name: str) -> bool:
|
|
"""Returns the button state by name (bool).
|
|
Valid names: "A", "B", "X", "Y",
|
|
"left_menu_button", "right_menu_button",
|
|
"left_axis_click", "right_axis_click"
|
|
"""
|
|
if name == "A":
|
|
return xrt.get_A_button()
|
|
elif name == "B":
|
|
return xrt.get_B_button()
|
|
elif name == "X":
|
|
return xrt.get_X_button()
|
|
elif name == "Y":
|
|
return xrt.get_Y_button()
|
|
elif name == "left_menu_button":
|
|
return xrt.get_left_menu_button()
|
|
elif name == "right_menu_button":
|
|
return xrt.get_right_menu_button()
|
|
elif name == "left_axis_click":
|
|
return xrt.get_left_axis_click()
|
|
elif name == "right_axis_click":
|
|
return xrt.get_right_axis_click()
|
|
else:
|
|
raise ValueError(
|
|
f"Invalid name: {name}. Valid names are: 'A', 'B', 'X', 'Y', "
|
|
"'left_menu_button', 'right_menu_button', 'left_axis_click', 'right_axis_click'."
|
|
)
|
|
|
|
def get_timestamp_ns(self) -> int:
|
|
"""Returns the current timestamp in nanoseconds (int)."""
|
|
return xrt.get_time_stamp_ns()
|
|
|
|
def get_hand_tracking_state(self, hand: str) -> np.ndarray | None:
|
|
"""Returns the hand tracking state for the specified hand.
|
|
Valid hands: "left", "right".
|
|
State is a 27 x 7 numpy array, where each row is [x, y, z, qx, qy, qz, qw] for each joint.
|
|
Returns None if hand tracking is inactive (low quality).
|
|
"""
|
|
if hand.lower() == "left":
|
|
if not xrt.get_left_hand_is_active():
|
|
return None
|
|
return xrt.get_left_hand_tracking_state()
|
|
elif hand.lower() == "right":
|
|
if not xrt.get_right_hand_is_active():
|
|
return None
|
|
return xrt.get_right_hand_tracking_state()
|
|
else:
|
|
raise ValueError(f"Invalid hand: {hand}. Valid hands are: 'left', 'right'.")
|
|
|
|
def get_joystick_state(self, controller: str) -> list[float]:
|
|
"""Returns the joystick state for the specified controller.
|
|
Valid controllers: "left", "right".
|
|
State is a list with shape (2) representing [x, y] for each joystick.
|
|
"""
|
|
if controller.lower() == "left":
|
|
return xrt.get_left_axis()
|
|
elif controller.lower() == "right":
|
|
return xrt.get_right_axis()
|
|
else:
|
|
raise ValueError(
|
|
f"Invalid controller: {controller}. Valid controllers are: 'left', 'right'."
|
|
)
|
|
|
|
def get_motion_tracker_data(self) -> dict:
|
|
"""Returns a dictionary of motion tracker data, where the keys are the tracker serial numbers.
|
|
Each value is a dictionary containing the pose, velocity, and acceleration of the tracker.
|
|
"""
|
|
num_motion_data = xrt.num_motion_data_available()
|
|
if num_motion_data == 0:
|
|
return {}
|
|
|
|
poses = xrt.get_motion_tracker_pose()
|
|
velocities = xrt.get_motion_tracker_velocity()
|
|
accelerations = xrt.get_motion_tracker_acceleration()
|
|
serial_numbers = xrt.get_motion_tracker_serial_numbers()
|
|
|
|
tracker_data = {}
|
|
for i in range(num_motion_data):
|
|
serial = serial_numbers[i]
|
|
tracker_data[serial] = {
|
|
"pose": poses[i],
|
|
"velocity": velocities[i],
|
|
"acceleration": accelerations[i],
|
|
}
|
|
|
|
return tracker_data
|
|
|
|
def get_body_tracking_data(self) -> dict | None:
|
|
"""Returns complete body tracking data or None if unavailable.
|
|
|
|
Returns:
|
|
Dict with keys: 'poses', 'velocities', 'accelerations', 'imu_timestamps', 'body_timestamp'
|
|
- poses: (24, 7) array [x,y,z,qx,qy,qz,qw] for each joint
|
|
- velocities: (24, 6) array [vx,vy,vz,wx,wy,wz] for each joint
|
|
- accelerations: (24, 6) array [ax,ay,az,wax,way,waz] for each joint
|
|
"""
|
|
if not xrt.is_body_data_available():
|
|
return None
|
|
|
|
return {
|
|
"poses": xrt.get_body_joints_pose(),
|
|
"velocities": xrt.get_body_joints_velocity(),
|
|
"accelerations": xrt.get_body_joints_acceleration(),
|
|
}
|
|
|
|
def close(self):
|
|
xrt.close()
|