Skip to content

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.

logger module-attribute ¤

logger = getLogger(__name__)

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(holder: object | None = None) -> bool

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.

VisaDriver¤

VISA transport driver. Wraps pyvisa; callers own command strings, this owns I/O and locking.

logger module-attribute ¤

logger = getLogger(__name__)

DEFAULT_VISA_BACKEND module-attribute ¤

DEFAULT_VISA_BACKEND = '@ivi'

FALLBACK_VISA_BACKEND module-attribute ¤

FALLBACK_VISA_BACKEND = '@py'

StopBits ¤

Bases: Enum

ONE class-attribute instance-attribute ¤

ONE = 1

ONE_POINT_FIVE class-attribute instance-attribute ¤

ONE_POINT_FIVE = 1.5

TWO class-attribute instance-attribute ¤

TWO = 2

Parity ¤

Bases: Enum

NONE class-attribute instance-attribute ¤

NONE = 'N'

ODD class-attribute instance-attribute ¤

ODD = 'O'

EVEN class-attribute instance-attribute ¤

EVEN = 'E'

MARK class-attribute instance-attribute ¤

MARK = 'M'

SPACE class-attribute instance-attribute ¤

SPACE = 'S'

ControlFlow ¤

Bases: IntEnum

NONE class-attribute instance-attribute ¤

NONE = 0

XON_XOFF class-attribute instance-attribute ¤

XON_XOFF = 1

RTS_CTS class-attribute instance-attribute ¤

RTS_CTS = 2

DTR_DSR class-attribute instance-attribute ¤

DTR_DSR = 4

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.

baud_rate class-attribute instance-attribute ¤

baud_rate: int = 9600

data_bits class-attribute instance-attribute ¤

data_bits: int = 8

stop_bits class-attribute instance-attribute ¤

stop_bits: StopBits = ONE

parity class-attribute instance-attribute ¤

parity: Parity = NONE

flow_control class-attribute instance-attribute ¤

flow_control: ControlFlow = NONE

TerminatorConfig dataclass ¤

TerminatorConfig(read: str = '\n', write: str = '\r\n')

Read and write terminators applied to the VISA resource.

read class-attribute instance-attribute ¤

read: str = '\n'

write class-attribute instance-attribute ¤

write: str = '\r\n'

TimeoutConfig dataclass ¤

TimeoutConfig(connect: int = 30, recv: int = 15, send: int = 15)

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.

connect class-attribute instance-attribute ¤

connect: int = 30

recv class-attribute instance-attribute ¤

recv: int = 15

send class-attribute instance-attribute ¤

send: int = 15

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::SOCKET or USB0::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 @py when 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.

visa_resource instance-attribute ¤

visa_resource: str

visa_backend class-attribute instance-attribute ¤

visa_backend: str | None = None

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)

tcp_nodelay class-attribute instance-attribute ¤

tcp_nodelay: bool = True

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.

is_open property ¤

is_open: bool

Whether the underlying VISA resource is currently open.

write ¤

write(command: str) -> None

Write command to the instrument; the configured write terminator is appended.

read ¤

read() -> str

Read a response, stripping the configured read terminator.

query ¤

query(command: str) -> str

Write command and read the response.

write_raw ¤

write_raw(data: bytes) -> None

Write raw bytes verbatim. Caller owns framing for binary payloads.

read_raw ¤

read_raw() -> bytes

Read raw bytes from the instrument.

query_raw ¤

query_raw(command: str) -> bytes

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 ¤

temporary_timeout(timeout_ms: int) -> Iterator[None]

Hold the lock and override the operation timeout to timeout_ms ms; restored on exit (even if raises).

open ¤

open(holder: object | None = None) -> bool

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.

Modbus transport¤

Modbus transport driver. Wraps pymodbus; callers own register maps, this owns I/O, framing, and locking.

logger module-attribute ¤

logger = getLogger(__name__)

RegisterType module-attribute ¤

RegisterType = Literal['holding', 'input', 'coil', 'discrete']

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 ¤

check_unit_id(unit_id: int) -> int

Validate unit_id against this physical layer's range (0 to :attr:_max_unit_id) and return it.

read_holding_registers ¤

read_holding_registers(
    address: int, count: int, *, unit_id: int
) -> list[int]

Read holding registers by address (FC03).

read_input_registers ¤

read_input_registers(
    address: int, count: int, *, unit_id: int
) -> list[int]

Read input registers by address (FC04).

write_holding_register ¤

write_holding_register(
    address: int, value: int, *, unit_id: int
) -> None

Write a single holding register by address (FC06).

write_holding_registers ¤

write_holding_registers(
    address: int, values: list[int], *, unit_id: int
) -> None

Write multiple holding registers by address (FC16).

read_coils ¤

read_coils(address: int, count: int, *, unit_id: int) -> list[bool]

Read coils by address (FC01).

write_coil ¤

write_coil(address: int, value: bool, *, unit_id: int) -> None

Write a single coil by address (FC05).

write_coils ¤

write_coils(address: int, values: list[bool], *, unit_id: int) -> None

Write multiple coils by address (FC15).

read_discrete_inputs ¤

read_discrete_inputs(
    address: int, count: int, *, unit_id: int
) -> list[bool]

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(holder: object | None = None) -> bool

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.

ModbusTCPTransport ¤

ModbusTCPTransport(host: str, port: int = 502, timeout: float = 3.0)

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(holder: object | None = None) -> bool

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 ¤

check_unit_id(unit_id: int) -> int

Validate unit_id against this physical layer's range (0 to :attr:_max_unit_id) and return it.

read_holding_registers ¤

read_holding_registers(
    address: int, count: int, *, unit_id: int
) -> list[int]

Read holding registers by address (FC03).

read_input_registers ¤

read_input_registers(
    address: int, count: int, *, unit_id: int
) -> list[int]

Read input registers by address (FC04).

write_holding_register ¤

write_holding_register(
    address: int, value: int, *, unit_id: int
) -> None

Write a single holding register by address (FC06).

write_holding_registers ¤

write_holding_registers(
    address: int, values: list[int], *, unit_id: int
) -> None

Write multiple holding registers by address (FC16).

read_coils ¤

read_coils(address: int, count: int, *, unit_id: int) -> list[bool]

Read coils by address (FC01).

write_coil ¤

write_coil(address: int, value: bool, *, unit_id: int) -> None

Write a single coil by address (FC05).

write_coils ¤

write_coils(address: int, values: list[bool], *, unit_id: int) -> None

Write multiple coils by address (FC15).

read_discrete_inputs ¤

read_discrete_inputs(
    address: int, count: int, *, unit_id: int
) -> list[bool]

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(holder: object | None = None) -> bool

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 ¤

check_unit_id(unit_id: int) -> int

Validate unit_id against this physical layer's range (0 to :attr:_max_unit_id) and return it.

read_holding_registers ¤

read_holding_registers(
    address: int, count: int, *, unit_id: int
) -> list[int]

Read holding registers by address (FC03).

read_input_registers ¤

read_input_registers(
    address: int, count: int, *, unit_id: int
) -> list[int]

Read input registers by address (FC04).

write_holding_register ¤

write_holding_register(
    address: int, value: int, *, unit_id: int
) -> None

Write a single holding register by address (FC06).

write_holding_registers ¤

write_holding_registers(
    address: int, values: list[int], *, unit_id: int
) -> None

Write multiple holding registers by address (FC16).

read_coils ¤

read_coils(address: int, count: int, *, unit_id: int) -> list[bool]

Read coils by address (FC01).

write_coil ¤

write_coil(address: int, value: bool, *, unit_id: int) -> None

Write a single coil by address (FC05).

write_coils ¤

write_coils(address: int, values: list[bool], *, unit_id: int) -> None

Write multiple coils by address (FC15).

read_discrete_inputs ¤

read_discrete_inputs(
    address: int, count: int, *, unit_id: int
) -> list[bool]

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 ¤

register_count(data_type: DataType) -> int

Number of 16-bit registers data_type spans (uint16→1, uint32→2, uint64→4).

decode_registers ¤

decode_registers(
    registers: list[int],
    data_type: DataType,
    byte_swap: bool = False,
    word_swap: bool = False,
    long_swap: bool = False,
) -> int | float

Decode raw 16-bit registers into a typed value, applying byte/word/long swaps as configured.

encode_value ¤

encode_value(
    value: int | float | bool,
    data_type: DataType,
    byte_swap: bool = False,
    word_swap: bool = False,
    long_swap: bool = False,
) -> list[int]

Encode a typed value into 16-bit registers, applying byte/word/long swaps as configured.