fix: tie query()/read() host timeout to the controller's read_tmo_ms
Found against real hardware: a fixed 2.0s host-side read timeout was
shorter than the device's configured read_tmo_ms (3000ms). The
controller keeps a "++read" outstanding internally until EOI or its
own timeout, so the client gave up and sent the next command first --
that command's reply then got lost/delayed behind the still-pending
read, surfacing as a spurious TransportTimeout on the *following* call
(observed: query("*IDN?") timed out as expected with no instrument
attached, then srq() right after it timed out too, on a device with
read_tmo_ms=3000).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ from .exceptions import CommandError
|
|||||||
from .transport import EthernetTransport, SerialTransport, Transport
|
from .transport import EthernetTransport, SerialTransport, Transport
|
||||||
|
|
||||||
_DEFAULT_COMMAND_TIMEOUT = 1.0 # seconds, for ++cmd query replies
|
_DEFAULT_COMMAND_TIMEOUT = 1.0 # seconds, for ++cmd query replies
|
||||||
_DEFAULT_READ_TIMEOUT = 2.0 # seconds, for ++read / query() instrument data
|
_READ_TIMEOUT_MARGIN = 0.5 # seconds, added on top of read_tmo_ms (see _instrument_read_timeout)
|
||||||
|
|
||||||
|
|
||||||
class GpibController:
|
class GpibController:
|
||||||
@@ -59,6 +59,22 @@ class GpibController:
|
|||||||
if self.mode != 0:
|
if self.mode != 0:
|
||||||
raise CommandError(f"++{command_name} is only valid in DEVICE mode")
|
raise CommandError(f"++{command_name} is only valid in DEVICE mode")
|
||||||
|
|
||||||
|
def _instrument_read_timeout(self, explicit: float | None) -> float:
|
||||||
|
"""Host-side timeout for reading instrument data.
|
||||||
|
|
||||||
|
Must be longer than the controller's own `read_tmo_ms`: the
|
||||||
|
controller keeps a "++read" outstanding internally (waiting for
|
||||||
|
EOI or its own timeout) before it will process the next command,
|
||||||
|
so a shorter host-side timeout gives up and sends the next command
|
||||||
|
while the controller is still finishing the previous read -- that
|
||||||
|
reply then arrives late and gets mistaken for the next command's
|
||||||
|
reply (or times out entirely). Costs one extra round trip when the
|
||||||
|
caller doesn't pass an explicit timeout; pass one to skip it.
|
||||||
|
"""
|
||||||
|
if explicit is not None:
|
||||||
|
return explicit
|
||||||
|
return (self.read_tmo_ms / 1000.0) + _READ_TIMEOUT_MARGIN
|
||||||
|
|
||||||
# -- config properties ------------------------------------------------
|
# -- config properties ------------------------------------------------
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -260,7 +276,7 @@ class GpibController:
|
|||||||
raise ValueError("terminator char must be between 0 and 255")
|
raise ValueError("terminator char must be between 0 and 255")
|
||||||
command = f"read {char}"
|
command = f"read {char}"
|
||||||
self._send_command(command)
|
self._send_command(command)
|
||||||
return self._transport.read_until_quiet(timeout=timeout or _DEFAULT_READ_TIMEOUT)
|
return self._transport.read_until_quiet(timeout=self._instrument_read_timeout(timeout))
|
||||||
|
|
||||||
# -- raw instrument I/O ---------------------------------------------
|
# -- raw instrument I/O ---------------------------------------------
|
||||||
|
|
||||||
@@ -280,4 +296,4 @@ class GpibController:
|
|||||||
"""
|
"""
|
||||||
self.write(data)
|
self.write(data)
|
||||||
self._send_command("read eoi")
|
self._send_command("read eoi")
|
||||||
return self._transport.read_until_quiet(timeout=read_timeout or _DEFAULT_READ_TIMEOUT)
|
return self._transport.read_until_quiet(timeout=self._instrument_read_timeout(read_timeout))
|
||||||
|
|||||||
Reference in New Issue
Block a user