From b7b2041e3d351e8a3ad036e880017e0ccc602b3b Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Sat, 19 Sep 2026 22:18:51 -0600 Subject: [PATCH] Fix wake beacon serial latency and pretty-print JSON --- src/switch_pico_bridge/wake_beacon.py | 17 ++++++++++++----- tests/test_wake_beacon.py | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/switch_pico_bridge/wake_beacon.py b/src/switch_pico_bridge/wake_beacon.py index bc4477b..f40bc5c 100644 --- a/src/switch_pico_bridge/wake_beacon.py +++ b/src/switch_pico_bridge/wake_beacon.py @@ -224,13 +224,20 @@ class WakeBeaconClient: def _exchange(self, command: bytes, deadline: float) -> WakeBeaconStatus: try: - self.transport.write_timeout = min(0.25, self._remaining(deadline)) + write_timeout = min(0.25, self._remaining(deadline)) + if self.transport.write_timeout != write_timeout: + self.transport.write_timeout = write_timeout # A partial write may already have reached the device. Never resend. if self.transport.write(command) != len(command): raise TransportError("Incomplete serial write; command was not retried") response = bytearray() while True: - self.transport.timeout = min(0.1, self._remaining(deadline)) + read_timeout = min(0.1, self._remaining(deadline)) + # pyserial reconfigures the Windows port on every assignment, + # even for an unchanged timeout. Avoid USB control transfers + # per response byte, while still shortening reads at deadline. + if self.transport.timeout != read_timeout: + self.transport.timeout = read_timeout chunk = self.transport.read(1) if not chunk: continue @@ -398,7 +405,7 @@ def main(argv: Sequence[str] | None = None) -> int: "status": exc.status.to_dict() if exc.status else None, } if args.json: - print(json.dumps(result, separators=(",", ":"))) + print(json.dumps(result, indent=2)) else: print(f"Wake beacon: {exc}", file=sys.stderr) return 1 @@ -406,11 +413,11 @@ def main(argv: Sequence[str] | None = None) -> int: print( json.dumps( {"ok": True, "action": action, "status": status.to_dict()}, - separators=(",", ":"), + indent=2, ) ) elif args.status: - print(json.dumps(status.to_dict(), sort_keys=True)) + print(json.dumps(status.to_dict(), indent=2, sort_keys=True)) else: print( f"Advertising burst complete (request {status.request_id}); console power state is not confirmed." diff --git a/tests/test_wake_beacon.py b/tests/test_wake_beacon.py index 70c9579..849898a 100644 --- a/tests/test_wake_beacon.py +++ b/tests/test_wake_beacon.py @@ -179,6 +179,28 @@ def test_fragmented_burst_completes_once_and_excludes_retained_id(beacon): assert beacon.closed +def test_slow_serial_reconfiguration_does_not_delay_each_response_byte( + beacon, monkeypatch +): + # pyserial reconfigures an open Windows port even when assigned the same + # timeout. Model USB control-transfer latency on each such assignment. + def set_timeout(device, value): + device.__dict__["timeout"] = value + device.clock.sleep(0.02) + + monkeypatch.setattr( + SimulatedBeacon, + "timeout", + property(lambda device: device.__dict__["timeout"], set_timeout), + raising=False, + ) + result = wake.request_wake("COM13", timeout=1) + assert result.state == "complete" + assert beacon.bursts == 1 + assert beacon.clock.now < 1 + assert beacon.closed + + @pytest.mark.parametrize( "change", [