diff --git a/BridgeApp/app_runner.py b/BridgeApp/app_runner.py index 79c5497..cb03988 100644 --- a/BridgeApp/app_runner.py +++ b/BridgeApp/app_runner.py @@ -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) @@ -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) @@ -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)) diff --git a/BridgeApp/target_ovr.py b/BridgeApp/target_ovr.py index e1aab1a..22b7692 100644 --- a/BridgeApp/target_ovr.py +++ b/BridgeApp/target_ovr.py @@ -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): @@ -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)