Transports¤
Transport drivers own I/O, locking, and connection lifecycle. Concrete instrument drivers compose a transport in their constructor rather than extending it.
TransportBase¤
TransportBase is the base every transport implements: _open_session, _teardown_session, and is_open
are the required contract, and the base itself provides the open/close lifecycle with shared
ownership, so more than one driver can share a single connection. The first open(holder) opens it,
and it stays open until the last close(holder) frees it. See Transports
in the guides for the lifecycle contract, a worked combined-instrument example, and a walkthrough for
implementing a new transport.
Base every transport implements: connection lifecycle plus deferred-teardown shared ownership.
TransportBase
¤
TransportBase()
Bases: ABC
The contract a transport implements: _open_session, _teardown_session, is_open, plus shared ownership.
Ownership is deferred-teardown: opened by the first owner, torn down by the last. Holders are the objects that own the connection, so a device serving several categories holds the transport once on its own behalf and does its own device-level teardown before releasing.
is_open
abstractmethod
property
¤
is_open: bool
Whether the underlying connection is currently open.
open
¤
Open the connection (idempotent); with a holder, admit it and return True only for the first owner.
close
¤
close(holder: object | None = None) -> None
Close and teardown; with a holder, remove it and tear down only when the last owner leaves.
A holder that needs to talk to the instrument before the connection goes (releasing a remote
lock, say) does that in its own close before calling this, while the session is still up.
Raises UnknownHolderError when an unrecognized holder closes while others still own the
connection, because that is the case where ignoring it silently strands the real owners and
leaves the connection open forever. Once the last owner has left there is nothing to strand,
so a repeat close is a no-op and close stays idempotent.
VisaDriver¤
VISA transport driver. Wraps pyvisa; callers own command strings, this owns I/O and locking.
StopBits
¤
Parity
¤
ControlFlow
¤
SerialConfig
dataclass
¤
SerialConfig(
baud_rate: int = 9600,
data_bits: int = 8,
stop_bits: StopBits = ONE,
parity: Parity = NONE,
flow_control: ControlFlow = NONE,
)
Serial-line settings, applied when the VISA resource is an ASRL interface.
TerminatorConfig
dataclass
¤
TimeoutConfig
dataclass
¤
Operation timeouts in seconds.
recv is applied as the pyvisa session timeout once the resource is open;
connect and send are reserved for future per-operation overrides.
VisaConfig
dataclass
¤
VisaConfig(
visa_resource: str,
visa_backend: str | None = None,
serial_config: SerialConfig = SerialConfig(),
terminator: TerminatorConfig = TerminatorConfig(),
timeout: TimeoutConfig = TimeoutConfig(),
tcp_nodelay: bool = True,
)
Connection parameters for a VISA resource.
Attributes:
-
visa_resource(str) –VISA resource string, e.g.
TCPIP0::host::5025::SOCKETorUSB0::0x2A8D::0x0101::MY12345::INSTR. -
visa_backend(str | None) –pyvisa backend specifier. When unset (
None), uses the system IVI VISA implementation (@ivi) and falls back to@pywhen no IVI backend is installed. An explicitly set backend is used as-is, with no fallback. -
serial_config(SerialConfig) –Serial settings applied when the VISA resource is an ASRL (RS-232/RS-485) interface.
-
terminator(TerminatorConfig) –Read and write terminators.
-
timeout(TimeoutConfig) –Operation timeouts.
-
tcp_nodelay(bool) –Disable Nagle's algorithm on raw TCP SOCKET connections. NI-VISA does this by default; pyvisa-py does not, which can wedge instruments that reset on coalesced writes (issue #156). No effect on non-socket transports. Defaults to
True.
serial_config
class-attribute
instance-attribute
¤
serial_config: SerialConfig = field(default_factory=SerialConfig)
terminator
class-attribute
instance-attribute
¤
terminator: TerminatorConfig = field(default_factory=TerminatorConfig)
timeout
class-attribute
instance-attribute
¤
timeout: TimeoutConfig = field(default_factory=TimeoutConfig)
VisaDriver
¤
VisaDriver(visa_resource: str | VisaConfig)
Bases: TransportBase
Transport for VISA-attached instruments. Composed by concrete drivers, not extended.
Supports shared ownership: a device serving several categories holds one connection for
all of them, passing each category view as the holder to :meth:open/:meth:close, and
it closes only when the last owner closes it. Thread-safe at the I/O level via an internal
lock; use :meth:lock to keep a multi-step VISA sequence atomic.
write
¤
write(command: str) -> None
Write command to the instrument; the configured write terminator is appended.
write_raw
¤
write_raw(data: bytes) -> None
Write raw bytes verbatim. Caller owns framing for binary payloads.
query_raw
¤
Write command (with terminator) and read raw bytes — reply is not decoded or stripped.
query_binary_values
¤
query_binary_values(
command: str,
datatype: str = "B",
is_big_endian: bool = False,
container: type = list,
) -> Any
Send command and decode the IEEE-488.2 definite-length binary block reply.
Use for waveforms, screenshots, settings dumps.
Parameters:
-
(command¤str) –SCPI query that returns a binary block (e.g.
"CURV?"). -
(datatype¤str, default:'B') –struct-style format char ("B"u8,"h"i16,"H"u16,"f"f32). -
(is_big_endian¤bool, default:False) –Byte order of multi-byte elements.
-
(container¤type, default:list) –Container for decoded values (default
list).
clear
¤
clear() -> None
VISA device clear — aborts any pending operation. Use after a timed-out blocking read.
temporary_timeout
¤
Hold the lock and override the operation timeout to timeout_ms ms; restored on exit (even if raises).
open
¤
Open the connection (idempotent); with a holder, admit it and return True only for the first owner.
close
¤
close(holder: object | None = None) -> None
Close and teardown; with a holder, remove it and tear down only when the last owner leaves.
A holder that needs to talk to the instrument before the connection goes (releasing a remote
lock, say) does that in its own close before calling this, while the session is still up.
Raises UnknownHolderError when an unrecognized holder closes while others still own the
connection, because that is the case where ignoring it silently strands the real owners and
leaves the connection open forever. Once the last owner has left there is nothing to strand,
so a repeat close is a no-op and close stays idempotent.
Modbus transport¤
Modbus transport driver. Wraps pymodbus; callers own register maps, this owns I/O, framing, and locking.
DataType
module-attribute
¤
DataType = Literal[
"uint16",
"int16",
"uint32",
"int32",
"uint64",
"int64",
"float32",
"float64",
"bool",
]
ModbusTransport
¤
ModbusTransport()
Bases: TransportBase, ABC
Abstract Modbus line: owns the session, locking, and every wire op, addressed per call via unit_id. Annotate against it; construct a concrete subclass.
is_open
property
¤
is_open: bool
Whether the client has been opened and not closed. Stays True across a dropped socket, which the next op reconnects.
check_unit_id
¤
Validate unit_id against this physical layer's range (0 to :attr:_max_unit_id) and return it.
read_holding_registers
¤
Read holding registers by address (FC03).
read_input_registers
¤
Read input registers by address (FC04).
write_holding_register
¤
Write a single holding register by address (FC06).
write_holding_registers
¤
Write multiple holding registers by address (FC16).
read_coils
¤
Read coils by address (FC01).
write_coil
¤
Write a single coil by address (FC05).
write_coils
¤
Write multiple coils by address (FC15).
read_discrete_inputs
¤
Read discrete inputs by address (FC02).
read_typed
¤
read_typed(
register_type: RegisterType,
address: int,
data_type: DataType,
*,
unit_id: int,
byte_swap: bool = False,
word_swap: bool = False,
long_swap: bool = False,
) -> int | float | bool
Read address as data_type, dispatching by register_type and decoding across registers.
Coils and discrete inputs are single-bit, so data_type must be "bool" and the read returns a bool.
write_typed
¤
write_typed(
register_type: RegisterType,
address: int,
value: int | float | bool,
data_type: DataType,
*,
unit_id: int,
byte_swap: bool = False,
word_swap: bool = False,
long_swap: bool = False,
) -> None
Encode value as data_type and write it to address, dispatching by register_type.
Coils are single-bit, so data_type must be "bool" and value must be a bool (no numeric coercion).
open
¤
Open the connection (idempotent); with a holder, admit it and return True only for the first owner.
close
¤
close(holder: object | None = None) -> None
Close and teardown; with a holder, remove it and tear down only when the last owner leaves.
A holder that needs to talk to the instrument before the connection goes (releasing a remote
lock, say) does that in its own close before calling this, while the session is still up.
Raises UnknownHolderError when an unrecognized holder closes while others still own the
connection, because that is the case where ignoring it silently strands the real owners and
leaves the connection open forever. Once the last owner has left there is nothing to strand,
so a repeat close is a no-op and close stays idempotent.
ModbusTCPTransport
¤
Bases: ModbusTransport
A Modbus TCP line. One socket, addressed per wire op via unit_id.
is_open
property
¤
is_open: bool
Whether the client has been opened and not closed. Stays True across a dropped socket, which the next op reconnects.
open
¤
Open the connection (idempotent); with a holder, admit it and return True only for the first owner.
close
¤
close(holder: object | None = None) -> None
Close and teardown; with a holder, remove it and tear down only when the last owner leaves.
A holder that needs to talk to the instrument before the connection goes (releasing a remote
lock, say) does that in its own close before calling this, while the session is still up.
Raises UnknownHolderError when an unrecognized holder closes while others still own the
connection, because that is the case where ignoring it silently strands the real owners and
leaves the connection open forever. Once the last owner has left there is nothing to strand,
so a repeat close is a no-op and close stays idempotent.
lock
¤
lock() -> RLock
Return the reentrant resource lock for atomic multi-step sequences; the holder may write/query/read inside it.
check_unit_id
¤
Validate unit_id against this physical layer's range (0 to :attr:_max_unit_id) and return it.
read_holding_registers
¤
Read holding registers by address (FC03).
read_input_registers
¤
Read input registers by address (FC04).
write_holding_register
¤
Write a single holding register by address (FC06).
write_holding_registers
¤
Write multiple holding registers by address (FC16).
read_coils
¤
Read coils by address (FC01).
write_coil
¤
Write a single coil by address (FC05).
write_coils
¤
Write multiple coils by address (FC15).
read_discrete_inputs
¤
Read discrete inputs by address (FC02).
read_typed
¤
read_typed(
register_type: RegisterType,
address: int,
data_type: DataType,
*,
unit_id: int,
byte_swap: bool = False,
word_swap: bool = False,
long_swap: bool = False,
) -> int | float | bool
Read address as data_type, dispatching by register_type and decoding across registers.
Coils and discrete inputs are single-bit, so data_type must be "bool" and the read returns a bool.
write_typed
¤
write_typed(
register_type: RegisterType,
address: int,
value: int | float | bool,
data_type: DataType,
*,
unit_id: int,
byte_swap: bool = False,
word_swap: bool = False,
long_swap: bool = False,
) -> None
Encode value as data_type and write it to address, dispatching by register_type.
Coils are single-bit, so data_type must be "bool" and value must be a bool (no numeric coercion).
ModbusRTUTransport
¤
ModbusRTUTransport(
port: str,
baudrate: int = 9600,
parity: Literal["N", "E", "O"] = "N",
stopbits: Literal[1, 2] = 1,
bytesize: Literal[5, 6, 7, 8] = 8,
timeout: float = 3.0,
framer: Literal["rtu", "ascii"] = "rtu",
)
Bases: ModbusTransport
A Modbus serial line (RTU or ASCII framing). One port, addressed per wire op via unit_id.
is_open
property
¤
is_open: bool
Whether the client has been opened and not closed. Stays True across a dropped socket, which the next op reconnects.
open
¤
Open the connection (idempotent); with a holder, admit it and return True only for the first owner.
close
¤
close(holder: object | None = None) -> None
Close and teardown; with a holder, remove it and tear down only when the last owner leaves.
A holder that needs to talk to the instrument before the connection goes (releasing a remote
lock, say) does that in its own close before calling this, while the session is still up.
Raises UnknownHolderError when an unrecognized holder closes while others still own the
connection, because that is the case where ignoring it silently strands the real owners and
leaves the connection open forever. Once the last owner has left there is nothing to strand,
so a repeat close is a no-op and close stays idempotent.
lock
¤
lock() -> RLock
Return the reentrant resource lock for atomic multi-step sequences; the holder may write/query/read inside it.
check_unit_id
¤
Validate unit_id against this physical layer's range (0 to :attr:_max_unit_id) and return it.
read_holding_registers
¤
Read holding registers by address (FC03).
read_input_registers
¤
Read input registers by address (FC04).
write_holding_register
¤
Write a single holding register by address (FC06).
write_holding_registers
¤
Write multiple holding registers by address (FC16).
read_coils
¤
Read coils by address (FC01).
write_coil
¤
Write a single coil by address (FC05).
write_coils
¤
Write multiple coils by address (FC15).
read_discrete_inputs
¤
Read discrete inputs by address (FC02).
read_typed
¤
read_typed(
register_type: RegisterType,
address: int,
data_type: DataType,
*,
unit_id: int,
byte_swap: bool = False,
word_swap: bool = False,
long_swap: bool = False,
) -> int | float | bool
Read address as data_type, dispatching by register_type and decoding across registers.
Coils and discrete inputs are single-bit, so data_type must be "bool" and the read returns a bool.
write_typed
¤
write_typed(
register_type: RegisterType,
address: int,
value: int | float | bool,
data_type: DataType,
*,
unit_id: int,
byte_swap: bool = False,
word_swap: bool = False,
long_swap: bool = False,
) -> None
Encode value as data_type and write it to address, dispatching by register_type.
Coils are single-bit, so data_type must be "bool" and value must be a bool (no numeric coercion).
register_count
¤
Number of 16-bit registers data_type spans (uint16→1, uint32→2, uint64→4).