Skip to content

Commit

Permalink
[WIP] [Update] Support for Tundra Tracker part 2
Browse files Browse the repository at this point in the history
WORK IN PROGRESS - do not merge without testing!

Compared to first pass commit...

* Detects Tundra Trackers, leaving Vive Tracker/etc support alone
* Fixes the Identify button!
* Warns if haptic strength tries to exceed 100%
  • Loading branch information
digitalf0x committed Jun 1, 2024
1 parent 955eb35 commit 8a524b4
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 42 deletions.
95 changes: 70 additions & 25 deletions BridgeApp/app_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,48 @@ def __init__(self, config: AppConfig, tracker: VRTracker, pulse_function, batter
self.strength_delta: float = 0.0
self.last_str_set_time = time.time()

#self.interval_ms = 50 # millis
# HACK: test spacing out pulses
self.interval_ms = 5.1 # millis
# FIXME: Find the slowest interval that allows for a full-duration
# pulse on e.g. Tundra Trackers.
# Some devices use microseconds instead of milliseconds for the legacy
# triggerHapticPulse() function, but then limit you to around 3999µs.
#
# TODO:
# Try switching from "triggerHapticPulse()" to IVRInput
# triggerHapticPulse() is deprecated as per
# See https://github.com/ValveSoftware/openvr/blob/v2.2.3/headers/openvr.h#L2431-L2433
# And https://github.com/ValveSoftware/openvr/blob/v2.2.3/headers/openvr.h#L5216-L5218
# TODO: That number is probably not exact; check with an oscilloscope.
#
# This might be specific to Tundra Trackers vs. Vive Trackers, as
# Tundra ships their IO Expansion board with a LRA/haptic actuator
# See https://steamcommunity.com/app/358720/discussions/0/405693392914144440/
#
# Failing that, this thread could detect Tundra Trackers by model and
# adjust the interval, and adapt the force_pulse() function to
# automatically queue up anything that exceeds the max duration to be
# applied as a series of max duration pulses (until queue is empty).
# In these situations, the update loop needs to run much faster to
# avoid exceeding the maximum duration of a haptic pulse. Any manual
# pulses also need to be spread out over time to not exceed the limit.
#
# Example:
# force_pulse() -> self.force_pulse_queue = 500 (ms)
# run()
# inside while True, also check if force_pulse_queue > 0
# triggerHapticPulse(max_duration)
# force_pulse_queue -= max_duration
# TODO: Try switching from "triggerHapticPulse()" to IVRInput
# triggerHapticPulse() is deprecated as per Valve:
# * https://github.com/ValveSoftware/openvr/blob/v2.2.3/headers/openvr.h#L2431-L2433
# * https://github.com/ValveSoftware/openvr/blob/v2.2.3/headers/openvr.h#L5216-L5218
#
# This might be specific to Tundra Trackers vs. Vive Trackers, as
# Tundra ships their IO Expansion board with a LRA/haptic actuator

# Assume trackers that behave as expected
self.interval_ms = 50 # millis

# Hack: multiplier to convert from milliseconds to the actual unit
self.hack_pulse_mult_to_ms = 0
# Hack: maximum allowed pulse duration in milliseconds
self.hack_pulse_limit_ms = 0
# Hack: when to stop a queued force pulse
self.hack_pulse_force_stop_time = 0

# Special-case as needed
if self.tracker.model == "Tundra Tracker":
# Tundra Tracker
# Works in microseconds
self.hack_pulse_mult_to_ms = 1 / 1000
# Has a limit of roughly 4000 microseconds
self.hack_pulse_limit_ms = 4000 * self.hack_pulse_mult_to_ms

if self.hack_pulse_limit_ms > 0:
# Apply pulse duration hack/workaround
self.interval_ms = self.hack_pulse_limit_ms
print(f"[VibrationManager] Using {self.interval_ms} ms pulse limit workaround for {self.tracker.serial}")

self.interval_s = self.interval_ms / 1000 # seconds

self.vp = VibrationPattern(self.config)
Expand All @@ -68,10 +84,34 @@ def run(self):
while True:
start_time = time.time()

pulse_length = 0

strength = self.calculate_strength(start_time)
# So we pulse every 50 ms that means a 50 ms pulse would be 100%
# So we pulse every self.interval_ms (e.g. 50) ms. That means a
# self.interval_ms (50/etc) ms pulse would be 100%.
if strength > 0:
self.pulse_function(self.tracker.index, int(strength * self.interval_ms))
# Cap strength to 100%
if strength > 1:
print(f"[VibrationManager] Strength >100% ({round(strength * 100)}) for {self.tracker.serial}")
strength = 1
pulse_length = int(strength * self.interval_ms)

# Check if there's a queued force pulse
if start_time < self.hack_pulse_force_stop_time:
# Convert to milliseconds
force_pulse_duration = (self.hack_pulse_force_stop_time - start_time) * 1000
# Cap to self.interval_ms duration
force_pulse_capped = int(min(force_pulse_duration, self.interval_ms))
# Pick the biggest number for pulse_length
pulse_length = max(pulse_length, force_pulse_capped)

# Convert to target unit of time if necessary
if self.hack_pulse_mult_to_ms:
pulse_length = int(pulse_length / self.hack_pulse_mult_to_ms)

# Trigger pulse if nonzero length requested
if pulse_length > 0:
self.pulse_function(self.tracker.index, pulse_length)

sleep = max(self.interval_s - (time.time() - start_time), 0.0)
time.sleep(sleep)
Expand Down Expand Up @@ -102,4 +142,9 @@ def apply_multiplier(self, strength):
* self.config.get_tracker_config(self.tracker.serial).multiplier_override)

def force_pulse(self, length):
self.pulse_function(self.tracker.index, int(length * self.tracker.pulse_multiplier))
if self.hack_pulse_limit_ms > 0:
# Add the pulse length in milliseconds to the current time in
# seconds, determining the new target time to stop
self.hack_pulse_force_stop_time = time.time() + (length / 1000)
else:
self.pulse_function(self.tracker.index, int(length * self.tracker.pulse_multiplier))
17 changes: 0 additions & 17 deletions BridgeApp/target_ovr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from app_config import VRTracker, AppConfig
from typing import List, Dict

## HACK: test spacing out pulses
#import time

class OpenVRTracker:
def __init__(self, config: AppConfig):
Expand Down Expand Up @@ -81,18 +79,3 @@ def is_alive(self):
def __pulse(self, index, pulse_length: int = 200):
if self.is_alive():
self.vr.triggerHapticPulse(index, 0, pulse_length)
# HACK: test spacing out pulses
#pulse_max_amount = 3999
pulse_max_amount = 5000
#sleep_min_delay = pulse_max_amount / 1000000
#sleep_min_delay = 5000 / 1000000
if pulse_length > pulse_max_amount:
print("PULSE LENGTH > " + str(pulse_max_amount) + ": " + str(pulse_length))
#print("PULSE LENGTH: " + str(pulse_length))
#while pulse_length > 3999:
# self.vr.triggerHapticPulse(index, 0, pulse_max_amount)
# pulse_length -= pulse_max_amount
# time.sleep(sleep_min_delay)
#if pulse_length > 0:
# self.vr.triggerHapticPulse(index, 0, pulse_length)
# time.sleep(sleep_min_delay)

0 comments on commit 8a524b4

Please sign in to comment.