diff --git a/progpib/controller.py b/progpib/controller.py index 1966d0c..a099099 100644 --- a/progpib/controller.py +++ b/progpib/controller.py @@ -275,8 +275,13 @@ class GpibController: if not (0 <= char <= 255): raise ValueError("terminator char must be between 0 and 255") command = f"read {char}" + # Compute the timeout (may itself query read_tmo_ms) *before* sending + # the read command: once issued, the controller won't answer any + # further command until it completes, so querying read_tmo_ms after + # would race the very read it's trying to time. + wait = self._instrument_read_timeout(timeout) self._send_command(command) - return self._transport.read_until_quiet(timeout=self._instrument_read_timeout(timeout)) + return self._transport.read_until_quiet(timeout=wait) # -- raw instrument I/O --------------------------------------------- @@ -295,5 +300,9 @@ class GpibController: `vendortools/arb_eth.py` (`++auto 0` + explicit `++read`). """ self.write(data) + # Compute the timeout (may itself query read_tmo_ms) before sending + # "++read eoi" -- see the comment in read() for why the ordering + # matters. + wait = self._instrument_read_timeout(read_timeout) self._send_command("read eoi") - return self._transport.read_until_quiet(timeout=self._instrument_read_timeout(read_timeout)) + return self._transport.read_until_quiet(timeout=wait) diff --git a/tests/test_controller.py b/tests/test_controller.py index ab5b888..e6a8455 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -67,6 +67,21 @@ def test_status_and_lon_raise_in_controller_mode(fake_gpib_server): _ = ctrl.lon +def test_query_fetches_read_tmo_ms_before_sending_read_eoi(fake_gpib_server): + # Regression test: found against real hardware that the controller + # doesn't answer any further command while a "++read" it issued is + # still outstanding (waiting for EOI or its own read_tmo_ms timeout). + # So query()/read() must fetch read_tmo_ms (to size the host-side wait) + # *before* sending "++read eoi" -- fetching it after races the read + # it's meant to be timing. Assert that ordering on the wire. + server, tcp_server = fake_gpib_server + with GpibController.ethernet(tcp_server.host, tcp_server.port) as ctrl: + ctrl.query("*IDN?", read_timeout=None) + assert _wait_until(lambda: b"++read eoi" in server.raw_received) + raw = bytes(server.raw_received) + assert raw.index(b"++read_tmo_ms") < raw.index(b"++read eoi") + + def test_status_and_spoll_return_bits_with_rqs_at_index_6(fake_gpib_server): server, tcp_server = fake_gpib_server server.state["mode"] = "0" # DEVICE