diff --git a/data_only_viz/iphone_usb_source.py b/data_only_viz/iphone_usb_source.py index 65076cc..3f64fd6 100644 --- a/data_only_viz/iphone_usb_source.py +++ b/data_only_viz/iphone_usb_source.py @@ -239,6 +239,14 @@ class IphoneUSBSource: # USB hiccup ends iter_frames; we re-establish :7000). with self._lock: self._frame = None + # Clear stale hand state so the renderer doesn't freeze on the + # last known hands after a stream drop. + if self.state is not None: + with self.state.lock(): + self.state.persons_hands_iphone = [] + self.state.persons_hands_chirality = [] + if self._write_hands: + self.state.persons_hands = [] self._opened = False sock = None # forces the reconnect at the loop top if not self._stop.is_set(): diff --git a/data_only_viz/tests/test_iphone_usb_reconnect.py b/data_only_viz/tests/test_iphone_usb_reconnect.py index 4071db3..fb6f771 100644 --- a/data_only_viz/tests/test_iphone_usb_reconnect.py +++ b/data_only_viz/tests/test_iphone_usb_reconnect.py @@ -4,6 +4,7 @@ race (app not ready when data_only_viz starts), not only a mid-session drop.""" import time import data_only_viz.iphone_usb_source as m +from data_only_viz.state import PoseKp, State def test_start_keeps_retrying_when_no_device(monkeypatch): @@ -23,3 +24,42 @@ def test_start_keeps_retrying_when_no_device(monkeypatch): "start() should keep retrying; connect_device called %d time(s)" % calls["n"]) finally: src.release() + + +def test_hands_cleared_on_stream_drop(monkeypatch): + """After a USB stream drop, persons_hands* lists must be emptied so the + renderer does not freeze on the last known hand positions.""" + + class FakeSock: + def close(self): pass + def shutdown(self, *a): pass + + connect_calls = [0] + + def fake_connect(): + connect_calls[0] += 1 + if connect_calls[0] == 1: + return FakeSock() # initial connection succeeds + return None # reconnect fails; release() will interrupt the wait + + def fake_iter_frames(sock): + raise OSError("stream dropped") # simulate immediate disconnect + + state = State() + dummy_hand = [PoseKp(x=0.5, y=0.5, z=0.0, c=1.0)] * 21 + with state.lock(): + state.persons_hands = [dummy_hand] + state.persons_hands_iphone = [dummy_hand] + state.persons_hands_chirality = [1] + + monkeypatch.setattr(m, "connect_device", fake_connect) + monkeypatch.setattr(m, "iter_frames", fake_iter_frames) + + src = m.IphoneUSBSource(state=state, write_hands=True) + src.start() + src.release() # joins thread; ensures clearing happened before we check + + with state.lock(): + assert state.persons_hands == [], "persons_hands not cleared on drop" + assert state.persons_hands_iphone == [], "persons_hands_iphone not cleared on drop" + assert state.persons_hands_chirality == [], "persons_hands_chirality not cleared on drop"