This guide shows how to capture full 6DoF poses from HTC's VIVE Ultimate Trackers on Ubuntu using USB HID or the wireless dongle without owning the Vive HMD (Head Mounted Display).
Acknowledgement: This fork builds upon vive_ultimate_tracker_re reverse-engineering work.
- Hardware: at least one VIVE Ultimate Tracker; optional wireless dongle (preferred for multi-tracker work) or direct USB connection. Host PC must be able to talk to HID devices and, for map resets, provide
adbaccess.
Do the initial SLAM map construction on Windows with SteamVR + VIVE Streaming Hub; after the tracker stores the map, you can return to Ubuntu for everything else in this guide.
- Install SteamVR.
- Enable the SteamVR "null driver" (virtual headset) using SteamVRNoHeadset.
- Install the VIVE Streaming Hub from HTC's site.
- Follow the VIVE Streaming Hub instructions to create a new map; you can ignore the final step that asks for a SteamVR headset connection.
- Once your trackers indicate they are "ready" in Streaming Hub, verify any additional requirements from the sections below (Wi-Fi config, dongle pairing, etc.).
- Python 3.x plus the
hid,numpy, andpygamepackages (pip install hid numpy pygame). - HID dependency note: this code imports
hid(nothidapi). On Debian/Ubuntu installlibhidapi-hidraw0andlibhidapi-libusb0(e.g.,sudo apt install libhidapi-hidraw0 libhidapi-libusb0) beforepip install hidso the native bindings build. - Permissions: Ubuntu users may need udev rules so HID devices are accessible without
sudo; for quick tests you can runsudo chmod a+r /dev/hidraw${NUM}on the device path printed byhid_test.pyorrf_hid_test.py. - Wi-Fi setup: edit
pyvut/wifi_info.jsonwith the SSID, password, country, and frequency you want the SLAM host tracker to broadcast; this step is required when you operate multiple trackers so they can sync over the host AP.
- Plug a tracker directly over USB.
python hid_test.pyenumerates HID interface 0, configures camera policy/FPS, and requests PCVR power (set_power_pcvr(1)).- Uncomment the loop at the bottom to continuously parse incoming pose packets (
while True: parse_incoming(); kick_watchdog()).
- Plug the wireless dongle into the host PC.
python rf_hid_test.pyenumerates the dongle, issues safe queries (fusion mode, role ID, IDs/SN, ROM version, capability dumps), and prints responses.- Example RF control and pairing helpers live near the bottom (e.g.,
send_rf_command(0x1D, ...)). Avoid flash/write opcodes unless you are prepared for risky behavior.
- Update Wi-Fi credentials in
pyvut/wifi_info.json. - Pair trackers with the dongle; the pyvut helper auto-selects the first tracker as SLAM host.
python pyvut/tracker_core.pystreams ACK/status traffic, auto-sends SLAM role and Wi-Fi ACKs, and keeps pose state for up to five trackers.python scripts/visualize_pygame.pyrenders simple 3D markers for live position/orientation; close the window to exit.
The repository now ships as a standard Python package. Install it into a virtual environment (editable mode recommended while hacking):
python -m venv .venv
source .venv/bin/activate
pip install -e .[visualizer]The core dependencies are hid and numpy; the optional visualizer extra pulls in pygame for scripts/visualize_pygame.py.
Import pyvut.UltimateTrackerAPI to receive tracker poses (position + quaternion rotation + acceleration) through callbacks or polling. The API reuses the existing HID plumbing and exposes each pose as a TrackerPose dataclass.
import time
from pyvut import TrackerPose, UltimateTrackerAPI
def on_pose(pose: TrackerPose) -> None:
print(
f"Tracker {pose.tracker_index} @ {pose.mac}: "
f"pos={pose.position} rot={pose.rotation} tracking={pose.tracking_status}"
)
with UltimateTrackerAPI(mode="DONGLE_USB") as api:
api.add_pose_callback(on_pose)
while True:
time.sleep(1)UltimateTrackerAPIsupports both dongle (mode="DONGLE_USB") and direct USB tracker (mode="TRACKER_USB") paths.- Use
api.get_latest_pose(idx)to fetch the current pose for a tracker without registering callbacks. - Provide a custom Wi-Fi config via
UltimateTrackerAPI(..., wifi_info_path="/path/to/wifi_info.json")if you do not want to edit the packaged default. - Prefer a quick CLI demo? Run
python scripts/stream_poses.py --mode DONGLE_USBto print live pose samples.
hid_test.py: minimal direct-USB HID pokes; bring up PCVR mode, send haptics, parse pose packets.rf_hid_test.py: experimental HID access to the wireless dongle; includes many RF/dongle command IDs plus hardware queries.pyvut/: higher-level helpers and enums for HID/RF communication.tracker_core.py:DongleHID,TrackerHID, andViveTrackerGroupabstractions for pairing, SLAM role assignment, Wi-Fi ACKs, and pose tracking.clear_maps.sh: adb helper to wipe/data/lambdaand/data/mapdataon a tracker.enums_*.py: command and response constants (USB HID, RF, Wi-Fi, status, ACKs, dongle commands).
scripts/visualize_pygame.py: lightweight pygame visualizer for up to 5 trackers.scripts/stream_poses.py: CLI demo that prints pose samples viaUltimateTrackerAPI.ota_parse.py: extracts and CRC-checks partitions from HTC OTA firmware images (trackers/firmware/TX_FW.ota).how-to-use.md: this README distilled version; keep it handy for detailed background.
set_tracking_mode(mode): toggle between gyro and SLAM modes (IDs defined inpyvut/enums_horusd_status.py).send_haptic(...): trigger haptics on the connected tracker.parse_pose_data(...): decode position, rotation, and acceleration from raw HID packets.
- Enumerates dongle HID endpoints, queries role IDs, SNs, ROM versions, and capability bitfields.
- Includes example pairing (
send_rf_command(0x1D, ...)) and control commands for trackers. fuzz_blacklistand inline comments flag dangerous opcodes—respect them.
DongleHIDpairs trackers, assigns SLAM host/role, forwards ACKs, and maintains Wi-Fi credentials.TrackerHIDoffers a direct USB transport option (ViveTrackerGroup(mode="TRACKER_USB")).ViveTrackerGroupkeeps arrays of poses and exposesget_pos(idx)/get_rot(idx)for consumers.- SLAM/map helpers react to ACKs (
ACK_MAP_STATUS,ACK_LAMBDA_STATUS, etc.) by requesting or ending maps when trackers get stuck.
- Place
trackers/firmware/TX_FW.otain the repo. - Run
python ota_parse.py. - The script lists OTA segments, validates HTC's CRC-128, and dumps each segment as
seg_<index>_<mem_addr>.binfor further inspection.
- HID interface numbers are currently hard-coded (interface 0 for HID1); if enumeration fails, inspect
hid.enumerate(...)output for the right path. - Many command IDs were inferred from binaries or experimentation; logging dumps raw payloads to help future reverse engineering.
- Several dongle commands reboot or brick hardware. Review
DCMDS_THAT_RESTART/DCMDS_THAT_WRITE_FLASHinpyvut/enums_horusd_dongle.pybefore experimenting. - Scripts often run tight loops without throttling; sprinkle
time.sleep(...)if USB polling needs to be kinder to your system. clear_maps.shwipes tracker-side SLAM data viaadb shell; handy when map state gets wedged.