Instrument architecture and drivers

The instrument subsystem is built around composition and discovery:

BaseInstrument composition model

Each instrument instance contains:

  • a transport object (byte-level I/O and connection lifecycle), and

  • a protocol object (command/query formatting, response parsing, and error checks).

This keeps instrument behaviour independent from physical connection details. A single driver can be reused with different transports (for example serial, Ethernet, GPIB, or null transport for tests) as long as the protocol matches the instrument command set.

Base instrument class providing the composition of transport and protocol.

BaseInstrument is the foundation of the instrument hierarchy. Every concrete instrument class is a (possibly indirect) subclass of BaseInstrument and gains the ability to communicate with a physical instrument by holding references to a BaseTransport and a BaseProtocol instance.

class stoner_measurement.instruments.base_instrument.BaseInstrument(transport: BaseTransport, protocol: BaseProtocol, *, auto_check_errors: bool = False)[source]

Bases: ABC

Base class for all instrument drivers.

Uses a composition pattern: each instrument instance holds a transport responsible for the physical byte-level communication, and a protocol responsible for formatting commands and parsing responses. This design allows any transport/protocol combination to be substituted without changing the instrument driver code.

Error checking is performed on demand via check_for_errors() and can be enabled automatically for every write() and query() call by setting auto_check_errors to True.

Two strategies are supported transparently, chosen based on the protocol:

  • Response-embedded (Oxford, Lakeshore) — check_for_errors() inspects the last query response for an inline error token.

  • Error-queue (SCPI) — check_for_errors() polls the instrument’s error queue. If the transport supports an out-of-band status byte (read_status_byte(), e.g. GPIB serial poll), only the ESB bit is checked first to avoid an unnecessary round-trip when no error is pending.

Attributes:
transport (BaseTransport):

Transport layer instance (serial, Ethernet, GPIB, …).

protocol (BaseProtocol):

Protocol instance (SCPI, Oxford, Lakeshore, …).

auto_check_errors (bool):

When True, write() and query() automatically call check_for_errors() after each operation. Defaults to False.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b"ACME,Model1,SN001,v1.0\n"])
>>> instr = BaseInstrument(transport=t, protocol=ScpiProtocol())
>>> instr.connect()
>>> instr.is_connected
True
>>> instr.query("*IDN?")
'ACME,Model1,SN001,v1.0'
>>> instr.disconnect()
>>> instr.is_connected
False
check_for_errors(*, command: str | None = None) None[source]

Poll the instrument for errors and raise if one is found.

The strategy depends on the protocol and transport combination:

  1. If the protocol uses response-embedded errors (errors_in_response is True), this method is a no-op — errors are detected inline by query().

  2. If the transport supports an out-of-band status byte (read_status_byte() returns a non-None value), the IEEE 488.2 Event Status Bit (ESB, bit 2) is checked. If it is clear, no error query is sent.

  3. Otherwise (or if the ESB bit is set) the protocol’s error_query command is sent and check_error() is called on the response.

Keyword Parameters:
command (str | None):

The command that preceded this check, used to populate the command field of any raised exception. Defaults to None.

Raises:
InstrumentError:

If the instrument reports an error.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b'+0,"No error"\n'])
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.check_for_errors()   # no exception — queue is clear
>>> instr.disconnect()
connect() None[source]

Open the transport connection to the instrument.

Raises:
ConnectionError:

If the underlying transport cannot be opened.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> instr = BaseInstrument(NullTransport(), ScpiProtocol())
>>> instr.connect()
>>> instr.is_connected
True
>>> instr.disconnect()
disconnect() None[source]

Close the transport connection to the instrument.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> instr = BaseInstrument(NullTransport(), ScpiProtocol())
>>> instr.connect()
>>> instr.disconnect()
>>> instr.is_connected
False
identify() str[source]

Return the instrument identification string (*IDN?).

Sends the standard IEEE 488.2 *IDN? query and returns the response. Instruments that do not support *IDN? should override this method.

Returns:
(str):

Identification string returned by the instrument.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b"ACME,Model1,SN001,v1.0\n"])
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.identify()
'ACME,Model1,SN001,v1.0'
>>> instr.disconnect()
property is_connected: bool

True if the underlying transport is currently open.

Returns:
(bool):

Connection state of the transport.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> instr = BaseInstrument(NullTransport(), ScpiProtocol())
>>> instr.is_connected
False
query(command: str) str[source]

Send a query and return the instrument’s response.

Combines write() and read() into a single call. The query is formatted by protocol before transmission.

If auto_check_errors is True:

  • For protocols with response-embedded errors (Oxford, Lakeshore), check_error() is called on the parsed response directly.

  • For error-queue protocols (SCPI), check_for_errors() is called after the response is returned.

Args:
command (str):

Query string in the instrument’s command language.

Returns:
(str):

Parsed response string.

Raises:
ConnectionError:

If the transport is not open.

TimeoutError:

If no response is received within the transport timeout.

InstrumentError:

If auto_check_errors is True and the instrument signals an error.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b"ACME,1,SN,v1\n"])
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.query("*IDN?")
'ACME,1,SN,v1'
>>> instr.disconnect()
read() str[source]

Read a response from the instrument.

Reads until the protocol’s terminator character is received and parses the raw bytes using protocol.

Returns:
(str):

Parsed response string.

Raises:
ConnectionError:

If the transport is not open.

TimeoutError:

If no data is received within the transport timeout.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b"+1.234\n"])
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.read()
'+1.234'
>>> instr.disconnect()
reset() None[source]

Send the standard IEEE 488.2 reset command (*RST).

Instruments that do not support *RST should override this method.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport()
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.reset()
>>> t.write_log
[b'*RST\n']
>>> instr.disconnect()
write(command: str) None[source]

Send a command to the instrument without expecting a response.

The command is formatted by protocol before being passed to transport. If auto_check_errors is True and the protocol uses an error queue (not response-embedded errors), check_for_errors() is called after the command is sent.

Args:
command (str):

Command string in the instrument’s command language.

Raises:
ConnectionError:

If the transport is not open.

InstrumentError:

If auto_check_errors is True and the instrument reports an error after processing the command.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport()
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.write("OUTP ON")
>>> t.write_log
[b'OUTP ON\n']
>>> instr.disconnect()

Transport layer classes for instrument communications.

Provides an abstract BaseTransport interface and concrete implementations for serial (RS-232/RS-485), Ethernet (TCP/IP socket), UDP, and GPIB connections, plus a NullTransport suitable for simulation and unit tests.

class stoner_measurement.instruments.transport.BaseTransport(timeout: float = 2.0)[source]

Bases: ABC

Abstract base for all instrument transport layers.

A transport is responsible for the physical transmission and reception of raw bytes over a specific interface (serial port, Ethernet socket, GPIB bus, etc.). Higher-level protocol formatting is handled separately by BaseProtocol.

Attributes:
timeout (float):

Read timeout in seconds. A value of 0 means non-blocking and None means block indefinitely.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> t = NullTransport()
>>> t.is_open
False
>>> t.open()
>>> t.is_open
True
>>> t.close()
>>> t.is_open
False
abstractmethod close() None[source]

Close the physical connection to the instrument.

flush() None[source]

Flush any pending data in the transport buffers.

The default implementation is a no-op; override in subclasses where a hardware flush operation is meaningful.

classmethod from_uri(uri: str) BaseTransport[source]

Construct the appropriate transport instance from a URI or VISA resource string.

Two input formats are recognised:

URI strings use a scheme://... format:

  • serial:///dev/ttyUSB0?baud_rate=9600SerialTransport. On Windows use serial://COM3?baud_rate=9600. Query parameters mirror the SerialTransport constructor keyword arguments: baud_rate (alias baud), data_bits, stop_bits, parity, timeout.

  • tcp://host:port?timeout=2.0EthernetTransport.

  • udp://host:port?timeout=2.0UdpTransport.

  • gpib://board:address/?timeout=2.0 or gpib://address/?timeout=2.0GpibTransport. When only one component is present it is treated as the address; the board defaults to 0.

VISA resource strings follow the IVI/VISA convention and do not contain ://:

  • GPIB[N]::address::INSTRGpibTransport.

  • TCPIP[N]::host::port::SOCKETEthernetTransport.

  • TCPIP[N]::host::INSTREthernetTransport on the default SCPI port 5025.

  • ASRL<port>::INSTRSerialTransport. The port name is everything after ASRL and before ::INSTR, e.g. ASRL/dev/ttyUSB0::INSTR or ASRLCOM3::INSTR.

Args:
uri (str):

URI string or VISA resource string describing the transport.

Returns:
(BaseTransport):

A concrete transport instance corresponding to uri.

Raises:
ValueError:

If the URI scheme or VISA resource type is not supported, or if required components (host, port, address) are missing.

Examples:
>>> from stoner_measurement.instruments.transport import BaseTransport, SerialTransport
>>> t = BaseTransport.from_uri("serial:///dev/ttyUSB0?baud_rate=9600")
>>> isinstance(t, SerialTransport)
True
>>> t.port
'/dev/ttyUSB0'
>>> t.baud_rate
9600
>>> from stoner_measurement.instruments.transport import EthernetTransport
>>> t = BaseTransport.from_uri("tcp://192.168.1.100:5025")
>>> isinstance(t, EthernetTransport)
True
>>> t.host
'192.168.1.100'
>>> t.port
5025
>>> from stoner_measurement.instruments.transport import GpibTransport
>>> t = BaseTransport.from_uri("GPIB0::22::INSTR")
>>> isinstance(t, GpibTransport)
True
>>> t.address
22
>>> t.board
0
property is_open: bool

True if the transport connection is currently open.

abstractmethod open() None[source]

Open the physical connection to the instrument.

Raises:
ConnectionError:

If the connection cannot be established.

abstractmethod read(num_bytes: int = 4096) bytes[source]

Read up to num_bytes from the instrument.

Args:
num_bytes (int):

Maximum number of bytes to read. Defaults to 4096.

Returns:
(bytes):

The bytes received from the instrument.

Raises:
ConnectionError:

If the transport is not open.

TimeoutError:

If no data is received within timeout seconds.

read_status_byte() int | None[source]

Return the instrument status byte via an out-of-band mechanism.

Some transports (notably GPIB) can perform a serial poll to read the IEEE 488.2 status byte (STB) without sending a command, allowing the caller to check whether an error condition exists before issuing an expensive error-queue query.

The default implementation returns None, indicating that no out-of-band mechanism is available. Subclasses that support hardware-level status polling (e.g. GpibTransport) should override this method.

If read_status_byte() returns None, check_for_errors() will fall back to issuing the protocol’s error_query command directly.

Returns:
(int | None):

The 8-bit status byte, or None if not supported.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> NullTransport().read_status_byte() is None
True
read_until(terminator: bytes = b'\n') bytes[source]

Read bytes from the instrument until terminator is encountered.

The default implementation calls read() one byte at a time. Subclasses may override this with a more efficient implementation.

Args:
terminator (bytes):

Byte sequence that marks the end of a response. Defaults to b"\n".

Returns:
(bytes):

All bytes received up to and including the terminator.

Raises:
ConnectionError:

If the transport is not open.

TimeoutError:

If no data is received within timeout seconds.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> t = NullTransport(responses=[b"hello\n"])
>>> t.open()
>>> t.read_until(b"\n")
b'hello\n'
>>> t.close()
property timeout: float

Read timeout in seconds.

property transport_address: str

Human-readable address string identifying the remote endpoint.

Subclasses override this to expose the connection address in a format appropriate to the transport type (e.g. "192.168.1.100:5025" for TCP, "GPIB0::22::INSTR" for GPIB, "/dev/ttyUSB0" for serial).

The default implementation returns an empty string, which is used by NullTransport and any transport that does not carry an addressable endpoint.

Returns:
(str):

Address string, or "" if not applicable.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> NullTransport().transport_address
''
abstractmethod write(data: bytes) None[source]

Send data to the instrument.

Args:
data (bytes):

Raw bytes to transmit.

Raises:
ConnectionError:

If the transport is not open.

OSError:

If a low-level I/O error occurs.

class stoner_measurement.instruments.transport.EthernetTransport(host: str, port: int, timeout: float = 2.0)[source]

Bases: BaseTransport

TCP/IP socket transport for network-connected instruments.

Attributes:
host (str):

Hostname or IP address of the instrument.

port (int):

TCP port number (most instruments use 5025 for SCPI).

timeout (float):

Socket read timeout in seconds. Defaults to 2.0.

Examples:
>>> from stoner_measurement.instruments.transport import EthernetTransport
>>> t = EthernetTransport(host="192.168.1.100", port=5025)
>>> t.host
'192.168.1.100'
>>> t.port
5025
close() None[source]

Close the TCP socket.

flush() None[source]

Drain any unread data from the socket receive buffer.

Temporarily sets the socket to non-blocking mode and discards any data already in the receive buffer.

open() None[source]

Connect to the instrument over TCP.

Raises:
ConnectionError:

If the TCP connection cannot be established.

read(num_bytes: int = 4096) bytes[source]

Receive up to num_bytes from the instrument.

Args:
num_bytes (int):

Maximum number of bytes to read. Defaults to 4096.

Returns:
(bytes):

Bytes received from the instrument.

Raises:
ConnectionError:

If the socket is not open.

TimeoutError:

If no data arrives within timeout seconds.

property transport_address: str

Return the remote endpoint as "<host>:<port>".

Returns:
(str):

Ethernet address string, e.g. "192.168.1.100:5025".

Examples:
>>> from stoner_measurement.instruments.transport import EthernetTransport
>>> EthernetTransport(host="192.168.1.100", port=5025).transport_address
'192.168.1.100:5025'
write(data: bytes) None[source]

Send data to the instrument.

Args:
data (bytes):

Raw bytes to transmit.

Raises:
ConnectionError:

If the socket is not open.

class stoner_measurement.instruments.transport.GpibTransport(address: int, board: int = 0, timeout: float = 2.0)[source]

Bases: BaseTransport

GPIB transport using PyVISA.

Wraps a PyVISA GPIBInstrument resource to provide the standard BaseTransport interface. The VISA resource string is constructed from address and board as "GPIB<board>::<address>::INSTR".

Attributes:
address (int):

GPIB primary address of the instrument (0–30).

board (int):

GPIB board (interface) index. Defaults to 0.

timeout (float):

Read timeout in seconds. Defaults to 2.0.

Examples:
>>> from stoner_measurement.instruments.transport import GpibTransport
>>> t = GpibTransport(address=22)
>>> t.address
22
>>> t.board
0
>>> t.resource_string
'GPIB0::22::INSTR'
close() None[source]

Close the GPIB resource.

open() None[source]

Open the GPIB resource via PyVISA.

Raises:
ConnectionError:

If the resource cannot be opened.

read(num_bytes: int = 4096) bytes[source]

Read up to num_bytes from the GPIB instrument.

Args:
num_bytes (int):

Maximum number of bytes to read. Defaults to 4096.

Returns:
(bytes):

Bytes received.

Raises:
ConnectionError:

If the transport is not open.

TimeoutError:

If no data arrives within timeout seconds.

read_status_byte() int | None[source]

Return the IEEE 488.2 status byte via a GPIB serial poll.

Performs an out-of-band serial poll (PyVISA read_stb()) without transmitting any command to the instrument. This is the preferred mechanism for checking the GPIB status byte because it does not consume a response from the instrument’s output buffer.

Returns:
(int | None):

The 8-bit status byte, or None if the transport is not currently open.

Examples:
>>> from stoner_measurement.instruments.transport import GpibTransport
>>> t = GpibTransport(address=22)
>>> t.read_status_byte() is None  # not open
True
property resource_string: str

VISA resource string for this GPIB address.

Returns:
(str):

Resource string in the form "GPIB<board>::<address>::INSTR".

Examples:
>>> from stoner_measurement.instruments.transport import GpibTransport
>>> GpibTransport(address=14, board=1).resource_string
'GPIB1::14::INSTR'
property transport_address: str

Return the VISA resource string for this GPIB instrument.

Returns:
(str):

VISA resource string, e.g. "GPIB0::22::INSTR".

Examples:
>>> from stoner_measurement.instruments.transport import GpibTransport
>>> GpibTransport(address=22).transport_address
'GPIB0::22::INSTR'
write(data: bytes) None[source]

Write data to the GPIB instrument.

Args:
data (bytes):

Raw bytes to transmit. The bytes are decoded as ASCII before being passed to PyVISA’s write_raw.

Raises:
ConnectionError:

If the transport is not open.

class stoner_measurement.instruments.transport.NullTransport(responses: list[bytes] | None = None, timeout: float = 2.0)[source]

Bases: BaseTransport

Loopback transport for simulation and unit testing.

write() appends bytes to write_log. read() returns the next entry from the responses queue provided at construction time. When the queue is exhausted read() returns b"".

Attributes:
write_log (list[bytes]):

All data written via write() since the transport was created.

timeout (float):

Nominal timeout (not actually enforced). Defaults to 2.0.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> t = NullTransport(responses=[b"+0.123\n"])
>>> t.open()
>>> t.write(b"MEAS:VOLT?\n")
>>> t.read_until(b"\n")
b'+0.123\n'
>>> t.write_log
[b'MEAS:VOLT?\n']
>>> t.close()
clear_log() None[source]

Clear the write log and any pending responses.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> t = NullTransport(responses=[b"data\n"])
>>> t.open()
>>> t.write(b"CMD\n")
>>> t.clear_log()
>>> t.write_log
[]
>>> t.close()
close() None[source]

Mark the transport as closed.

open() None[source]

Mark the transport as open.

queue_response(response: bytes) None[source]

Append response to the end of the response queue.

Useful when building up a sequence of expected replies during a test.

Args:
response (bytes):

Bytes to add to the response queue.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> t = NullTransport()
>>> t.open()
>>> t.queue_response(b"OK\n")
>>> t.read_until()
b'OK\n'
>>> t.close()
read(num_bytes: int = 4096) bytes[source]

Return the next pre-loaded response, or b"" when exhausted.

Args:
num_bytes (int):

Ignored; present for interface compatibility.

Returns:
(bytes):

Next response from the queue, or b"" if the queue is empty.

Raises:
ConnectionError:

If the transport has not been opened.

read_until(terminator: bytes = b'\n') bytes[source]

Return the next pre-loaded response regardless of terminator.

Args:
terminator (bytes):

Ignored; present for interface compatibility.

Returns:
(bytes):

Next response from the queue, or b"" if exhausted.

Raises:
ConnectionError:

If the transport has not been opened.

write(data: bytes) None[source]

Record data in write_log.

Args:
data (bytes):

Bytes to record.

Raises:
ConnectionError:

If the transport has not been opened.

class stoner_measurement.instruments.transport.SerialTransport(port: str, baud_rate: int = 9600, data_bits: int = 8, stop_bits: float = 1, parity: str = 'N', xonxoff: bool = False, rtscts: bool = False, timeout: float = 2.0)[source]

Bases: BaseTransport

Serial-port transport using pyserial.

The default parameters match the most common factory settings found on scientific instruments (Keithley, Agilent/Keysight, Oxford Instruments, Lakeshore, Stanford Research Systems, etc.): 9600 baud, 8 data bits, 1 stop bit, no parity, no flow control. Adjust when an instrument’s manual specifies different values.

Attributes:
port (str):

System device name (e.g. "/dev/ttyUSB0" or "COM3").

baud_rate (int):

Baud rate in bits per second. Common values: 9600, 19200, 57600, 115200. Defaults to 9600.

data_bits (int):

Number of data bits per character (5–8). Defaults to 8.

stop_bits (float):

Number of stop bits (1, 1.5, or 2). Defaults to 1.

parity (str):

Parity mode: "N" (none), "E" (even), "O" (odd), "M" (mark), or "S" (space). Defaults to "N".

xonxoff (bool):

Enable software (XON/XOFF) flow control. Defaults to False.

rtscts (bool):

Enable hardware (RTS/CTS) flow control. Defaults to False.

timeout (float):

Read timeout in seconds. Defaults to 2.0.

Examples:
>>> from stoner_measurement.instruments.transport import SerialTransport
>>> t = SerialTransport(port="/dev/ttyUSB0", baud_rate=9600)
>>> t.port
'/dev/ttyUSB0'
>>> t.baud_rate
9600
>>> t.xonxoff
False
>>> t.rtscts
False
close() None[source]

Close the serial port.

flush() None[source]

Flush both input and output buffers of the serial port.

open() None[source]

Open the serial port.

Raises:
ConnectionError:

If the port cannot be opened.

read(num_bytes: int = 4096) bytes[source]

Read up to num_bytes from the serial port.

Args:
num_bytes (int):

Maximum number of bytes to read. Defaults to 4096.

Returns:
(bytes):

Bytes received.

Raises:
ConnectionError:

If the port is not open.

TimeoutError:

If no data arrives within timeout seconds.

read_until(terminator: bytes = b'\n') bytes[source]

Read from the serial port until terminator is received.

Uses pyserial’s native read_until for efficiency.

Args:
terminator (bytes):

Terminator byte sequence. Defaults to b"\n".

Returns:
(bytes):

All bytes up to and including the terminator.

Raises:
ConnectionError:

If the port is not open.

TimeoutError:

If no data arrives within timeout seconds.

property transport_address: str

Return the serial port device name.

Returns:
(str):

Port name, e.g. "/dev/ttyUSB0" or "COM3".

Examples:
>>> from stoner_measurement.instruments.transport import SerialTransport
>>> SerialTransport(port="/dev/ttyUSB0").transport_address
'/dev/ttyUSB0'
write(data: bytes) None[source]

Send data over the serial port.

Args:
data (bytes):

Raw bytes to transmit.

Raises:
ConnectionError:

If the port is not open.

class stoner_measurement.instruments.transport.UdpTransport(host: str, port: int, timeout: float = 2.0)[source]

Bases: BaseTransport

UDP socket transport for network-connected instruments.

Attributes:
host (str):

Hostname or IP address of the instrument.

port (int):

UDP port number.

timeout (float):

Socket read timeout in seconds. Defaults to 2.0.

Examples:
>>> from stoner_measurement.instruments.transport import UdpTransport
>>> t = UdpTransport(host="192.168.1.100", port=5025)
>>> t.host
'192.168.1.100'
>>> t.port
5025
close() None[source]

Close the UDP socket.

open() None[source]

Create a UDP socket and record the default remote address.

socket.connect() is called on the UDP socket so that subsequent socket.send() and socket.recv() calls are directed to and filtered from the given host and port without requiring explicit address arguments.

Raises:
ConnectionError:

If the socket cannot be created or bound.

read(num_bytes: int = 4096) bytes[source]

Receive up to num_bytes from the instrument.

Args:
num_bytes (int):

Maximum number of bytes to read per datagram. Defaults to 4096.

Returns:
(bytes):

Bytes received from the instrument.

Raises:
ConnectionError:

If the socket is not open.

TimeoutError:

If no datagram arrives within timeout seconds.

property transport_address: str

Return the remote endpoint as "<host>:<port>".

Returns:
(str):

UDP address string, e.g. "192.168.1.100:5025".

Examples:
>>> from stoner_measurement.instruments.transport import UdpTransport
>>> UdpTransport(host="192.168.1.100", port=5025).transport_address
'192.168.1.100:5025'
write(data: bytes) None[source]

Send data to the instrument as a single UDP datagram.

Args:
data (bytes):

Raw bytes to transmit.

Raises:
ConnectionError:

If the socket is not open.

OSError:

If a low-level I/O error occurs.

Protocol layer classes for instrument communications.

Provides an abstract BaseProtocol interface and concrete implementations for SCPI (ScpiProtocol), Oxford Instruments carriage-return-terminated (OxfordProtocol), and Lakeshore simple-ASCII (LakeshoreProtocol) protocols.

class stoner_measurement.instruments.protocol.BaseProtocol[source]

Bases: ABC

Abstract base for all instrument communication protocols.

A protocol defines the command syntax and response parsing rules for a particular family of instruments. Concrete subclasses implement the format_command(), format_query(), and parse_response() methods according to the protocol conventions.

Two complementary error-detection strategies are supported:

Response-embedded errors (Oxford Instruments, Lakeshore):

The instrument encodes an error indicator directly inside its normal response string (e.g. a leading "?"). errors_in_response returns True for these protocols and check_error() should be called on every parsed response.

Error-queue errors (SCPI):

The instrument maintains an internal error queue that must be polled separately, typically via a SYST:ERR? query. error_query returns the query string and check_error() is called on the result of that query rather than on normal responses.

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> p = ScpiProtocol()
>>> p.format_command("VOLT 1.0")
b'VOLT 1.0\n'
>>> p.format_query("VOLT?")
b'VOLT?\n'
>>> p.parse_response(b' +1.234E+00\n')
'+1.234E+00'
>>> p.errors_in_response
False
>>> p.error_query
'SYST:ERR?'
check_error(response: str, *, command: str | None = None) None[source]

Raise InstrumentError if response indicates an error.

The default implementation is a no-op. Override in subclasses that support error checking.

For protocols where errors_in_response is True (Oxford, Lakeshore), response is the parsed reply to any query and the method must inspect it for error indicators.

For error-queue protocols (SCPI), response is the parsed reply to the error_query command.

Args:
response (str):

The parsed response string to examine.

Keyword Parameters:
command (str | None):

The original command that was sent (used to populate the command field). Defaults to None.

Raises:
InstrumentError:

If an instrument-level error is detected in the response.

property error_query: str | None

Query string used to retrieve the first error from the error queue.

Returns None (default) for protocols that embed errors in responses. Override in subclasses that maintain a separate error queue (e.g. SCPI returns "SYST:ERR?").

Returns:
(str | None):

Query command string, or None if not applicable.

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> ScpiProtocol().error_query
'SYST:ERR?'
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> OxfordProtocol().error_query is None
True
property errors_in_response: bool

True if errors are embedded in normal query responses.

When this property returns True (Oxford, Lakeshore), the instrument signals command errors directly inside its reply to a query. check_error() should be called on every parsed response.

When False (SCPI), errors are held in a separate error queue and must be retrieved via error_query.

Returns:
(bool):

True for response-embedded error protocols; False (default) for error-queue protocols.

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> ScpiProtocol().errors_in_response
False
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> OxfordProtocol().errors_in_response
True
abstractmethod format_command(command: str) bytes[source]

Format a command string for transmission.

Args:
command (str):

The human-readable command string, without any protocol- specific terminators.

Returns:
(bytes):

Bytes ready to be passed to BaseTransport.write().

abstractmethod format_query(query: str) bytes[source]

Format a query string for transmission.

Args:
query (str):

The human-readable query string. Many protocols distinguish between commands (which do not elicit a response) and queries (which do); subclasses may add or modify a suffix accordingly.

Returns:
(bytes):

Bytes ready to be passed to BaseTransport.write().

abstractmethod parse_response(raw: bytes) str[source]

Parse a raw response from the instrument.

Args:
raw (bytes):

Raw bytes as returned by the transport layer.

Returns:
(str):

The cleaned-up response string, stripped of any protocol- specific framing, terminators, or whitespace.

class stoner_measurement.instruments.protocol.LakeshoreProtocol(terminator: bytes = b'\r\n')[source]

Bases: BaseProtocol

Lakeshore simple ASCII CRLF-terminated protocol.

Lakeshore instruments signal an unrecognised command by returning "?" or a "?"-prefixed string in place of the expected value. Because the error indicator is embedded in the normal query response, this protocol sets errors_in_response to True.

Attributes:
terminator (bytes):

Byte sequence appended to outgoing messages. Defaults to b"\r\n".

Examples:
>>> from stoner_measurement.instruments.protocol import LakeshoreProtocol
>>> p = LakeshoreProtocol()
>>> p.format_command("SETP 1,10.0")
b'SETP 1,10.0\r\n'
>>> p.format_query("KRDG? A")
b'KRDG? A\r\n'
>>> p.parse_response(b'+273.150\r\n')
'+273.150'
>>> p.errors_in_response
True
>>> p.error_query is None
True
check_error(response: str, *, command: str | None = None) None[source]

Raise InstrumentError if response indicates an error.

Some Lakeshore models return "?" or a "?"-prefixed string for unrecognised commands.

Args:
response (str):

Parsed response string.

Keyword Parameters:
command (str | None):

The original command that was sent.

Raises:
InstrumentError:

If the response is "?" or starts with "?".

Examples:
>>> from stoner_measurement.instruments.protocol import LakeshoreProtocol
>>> LakeshoreProtocol().check_error("+77.350")  # no exception
>>> try:
...     LakeshoreProtocol().check_error("?", command="KRDG? Z")
... except Exception as exc:
...     print(type(exc).__name__, exc)
InstrumentError Lakeshore: unrecognised command (command: KRDG? Z)
property errors_in_response: bool

True — Lakeshore errors are embedded in the response payload.

Returns:
(bool):

Always True for LakeshoreProtocol.

Examples:
>>> from stoner_measurement.instruments.protocol import LakeshoreProtocol
>>> LakeshoreProtocol().errors_in_response
True
format_command(command: str) bytes[source]

Format a Lakeshore command for transmission.

Args:
command (str):

Command string without terminator.

Returns:
(bytes):

ASCII-encoded command with CRLF appended.

Examples:
>>> from stoner_measurement.instruments.protocol import LakeshoreProtocol
>>> LakeshoreProtocol().format_command("RAMP 1,1,0.5")
b'RAMP 1,1,0.5\r\n'
format_query(query: str) bytes[source]

Format a Lakeshore query for transmission.

Args:
query (str):

Query string without terminator.

Returns:
(bytes):

ASCII-encoded query with CRLF appended.

Examples:
>>> from stoner_measurement.instruments.protocol import LakeshoreProtocol
>>> LakeshoreProtocol().format_query("SRDG? B")
b'SRDG? B\r\n'
parse_response(raw: bytes) str[source]

Parse a raw Lakeshore response.

Decodes the bytes as ASCII and strips surrounding whitespace, including the CRLF terminator.

Args:
raw (bytes):

Raw bytes received from the instrument.

Returns:
(str):

Decoded response string with whitespace stripped.

Examples:
>>> from stoner_measurement.instruments.protocol import LakeshoreProtocol
>>> LakeshoreProtocol().parse_response(b'+77.350\r\n')
'+77.350'
>>> LakeshoreProtocol().parse_response(b'1,CONTROL,1\r\n')
'1,CONTROL,1'
class stoner_measurement.instruments.protocol.OxfordProtocol(terminator: bytes = b'\r')[source]

Bases: BaseProtocol

Oxford Instruments carriage-return-terminated ASCII protocol.

Oxford Instruments instruments echo the command letter as the first character of each response. An error is indicated when the response payload (after stripping the echo character) begins with "?". Because the error is carried in the normal response, this protocol sets errors_in_response to True; callers should invoke check_error() on every parsed query response.

Attributes:
terminator (bytes):

Byte sequence appended to outgoing messages. Defaults to b"\r".

Examples:
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> p = OxfordProtocol()
>>> p.format_command("S5")
b'S5\r'
>>> p.format_query("R1")
b'R1\r'
>>> p.parse_response(b'R1.234\r')
'1.234'
>>> p.errors_in_response
True
>>> p.error_query is None
True
check_error(response: str, *, command: str | None = None) None[source]

Raise InstrumentError if response indicates an Oxford error.

Oxford instruments respond with a payload starting with "?" when the instrument did not understand the command.

Args:
response (str):

Parsed response string (echo character already stripped by parse_response()).

Keyword Parameters:
command (str | None):

The original command that was sent.

Raises:
InstrumentError:

If the response starts with "?".

Examples:
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> OxfordProtocol().check_error("1.234")  # no exception
>>> try:
...     OxfordProtocol().check_error("?", command="R99")
... except Exception as exc:
...     print(type(exc).__name__, exc)
InstrumentError Oxford Instruments: unrecognised command (command: R99)
property errors_in_response: bool

True — Oxford errors are embedded in the response payload.

Returns:
(bool):

Always True for OxfordProtocol.

Examples:
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> OxfordProtocol().errors_in_response
True
format_command(command: str) bytes[source]

Format an Oxford Instruments command for transmission.

Args:
command (str):

Command string without terminator.

Returns:
(bytes):

ASCII-encoded command with carriage-return appended.

Examples:
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> OxfordProtocol().format_command("H1")
b'H1\r'
format_query(query: str) bytes[source]

Format an Oxford Instruments query for transmission.

Oxford Instruments does not formally distinguish commands from queries; both are formatted identically.

Args:
query (str):

Query string without terminator.

Returns:
(bytes):

ASCII-encoded query with carriage-return appended.

Examples:
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> OxfordProtocol().format_query("R1")
b'R1\r'
parse_response(raw: bytes) str[source]

Parse a raw Oxford Instruments response.

Oxford responses echo the command letter as their first character. This method strips the leading echo character, the trailing carriage return, and any surrounding whitespace.

Args:
raw (bytes):

Raw bytes received from the instrument.

Returns:
(str):

Response payload with the echo character and terminator removed.

Examples:
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> OxfordProtocol().parse_response(b'R1.234\r')
'1.234'
>>> OxfordProtocol().parse_response(b'S$A0C0H1L0R0B0\r')
'$A0C0H1L0R0B0'
class stoner_measurement.instruments.protocol.ScpiProtocol(terminator: bytes = b'\n')[source]

Bases: BaseProtocol

SCPI protocol — newline-terminated ASCII messages.

Attributes:
terminator (bytes):

Byte sequence appended to every outgoing message. Defaults to b"\n".

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> p = ScpiProtocol()
>>> p.format_command("OUTP ON")
b'OUTP ON\n'
>>> p.format_query("MEAS:CURR?")
b'MEAS:CURR?\n'
>>> p.parse_response(b'  +1.234E-03\n')
'+1.234E-03'
>>> p.errors_in_response
False
>>> p.error_query
'SYST:ERR?'
check_error(response: str, *, command: str | None = None) None[source]

Raise InstrumentError if response signals a SCPI error.

response should be the parsed reply to a SYST:ERR? query. SCPI instruments encode their error queue entries as "<code>,\"<message>\""; a code of +0 means No Error.

Args:
response (str):

A response string previously obtained from error_query.

Keyword Parameters:
command (str | None):

The original command that triggered the error check.

Raises:
InstrumentError:

If response does not start with "+0".

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> ScpiProtocol().check_error('+0,"No error"')  # no exception
>>> try:
...     ScpiProtocol().check_error('-113,"Undefined header"', command="*IDN")
... except Exception as exc:
...     print(type(exc).__name__, exc)
InstrumentError Undefined header (command: *IDN, code: -113)
property error_query: str

SCPI error-queue query string.

Returns:
(str):

"SYST:ERR?" — the standard SCPI command for reading the first entry from the instrument’s error queue.

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> ScpiProtocol().error_query
'SYST:ERR?'
format_command(command: str) bytes[source]

Format a SCPI command for transmission.

Args:
command (str):

SCPI command string (without terminator).

Returns:
(bytes):

UTF-8 encoded command with terminator appended.

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> ScpiProtocol().format_command("VOLT 5.0")
b'VOLT 5.0\n'
format_query(query: str) bytes[source]

Format a SCPI query for transmission.

Args:
query (str):

SCPI query string (without terminator).

Returns:
(bytes):

UTF-8 encoded query with terminator appended.

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> ScpiProtocol().format_query("*IDN?")
b'*IDN?\n'
parse_response(raw: bytes) str[source]

Parse a raw SCPI response.

Decodes raw as UTF-8 and strips surrounding whitespace.

Args:
raw (bytes):

Raw bytes received from the instrument.

Returns:
(str):

Decoded and stripped response string.

Examples:
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> ScpiProtocol().parse_response(b'  KEITHLEY,2400,12345,C32\r\n')
'KEITHLEY,2400,12345,C32'

Driver discovery process

InstrumentDriverManager uses two discovery paths:

  1. Built-in discovery scans modules in stoner_measurement.instruments (excluding the protocol and transport subpackages) and registers non-abstract BaseInstrument subclasses.

  2. Third-party discovery loads entry points in the stoner_measurement.instruments group and registers non-abstract BaseInstrument subclasses.

Use drivers_by_type() to filter the discovered registry by an instrument base type such as TemperatureController, MagnetController, or SourceMeter.

Instrument driver discovery and filtering utilities.

Provides InstrumentDriverManager for discovering concrete instrument driver classes from the local package and third-party entry-points.

class stoner_measurement.instruments.driver_manager.InstrumentDriverManager[source]

Bases: object

Discover and provide access to concrete instrument driver classes.

Discovery combines:

  • Built-in drivers found by scanning stoner_measurement.instruments modules.

  • Third-party drivers exposed via the stoner_measurement.instruments entry-point group.

discover() None[source]

Discover built-in and entry-point instrument drivers.

property driver_classes: dict[str, type[BaseInstrument]]

Return a copy of discovered driver classes.

property driver_names: list[str]

Return sorted discovered driver names.

drivers_by_type(instrument_type: type[BaseInstrument], *, include_abstract: bool = False) dict[str, type[BaseInstrument]][source]

Return discovered drivers that subclass a specific instrument type.

Args:
instrument_type (type[BaseInstrument]):

Base class/interface to filter by, for example MagnetController or TemperatureController.

Keyword Parameters:
include_abstract (bool):

If True, include abstract classes in results. Defaults to False.

Returns:
(dict[str, type[BaseInstrument]]):

Mapping of driver name to driver class.

Raises:
TypeError:

If instrument_type is not a BaseInstrument subclass.

get(name: str) type[BaseInstrument] | None[source]

Return a discovered driver class by name, or None.

register(name: str, driver_cls: type[BaseInstrument]) None[source]

Manually register a driver class.

Args:
name (str):

Unique identifier for the driver.

driver_cls (type[BaseInstrument]):

Driver class to register.

Raises:
TypeError:

If driver_cls is not a BaseInstrument subclass.

unregister(name: str) None[source]

Remove a driver class from the registry.

Args:
name (str):

Driver identifier to remove.

Instrument hierarchy and concrete classes

Hierarchy overview:

Concrete classes currently included in the package:

Instrument driver framework for stoner_measurement.

Provides a two-layer composition architecture for communicating with laboratory instruments:

Transport layer (physical byte-level I/O):

Protocol layer (command formatting and response parsing):

Instrument hierarchy:

class stoner_measurement.instruments.AlarmState(*values)[source]

Bases: Enum

Alarm status for a sensor channel.

Attributes:
DISABLED:

Alarm checking is not active for this channel.

OK:

Temperature is within the configured alarm limits.

LOW:

Temperature has fallen below the low-alarm threshold.

HIGH:

Temperature has risen above the high-alarm threshold.

DISABLED = 'disabled'
HIGH = 'high'
LOW = 'low'
OK = 'ok'
class stoner_measurement.instruments.BaseInstrument(transport: BaseTransport, protocol: BaseProtocol, *, auto_check_errors: bool = False)[source]

Bases: ABC

Base class for all instrument drivers.

Uses a composition pattern: each instrument instance holds a transport responsible for the physical byte-level communication, and a protocol responsible for formatting commands and parsing responses. This design allows any transport/protocol combination to be substituted without changing the instrument driver code.

Error checking is performed on demand via check_for_errors() and can be enabled automatically for every write() and query() call by setting auto_check_errors to True.

Two strategies are supported transparently, chosen based on the protocol:

  • Response-embedded (Oxford, Lakeshore) — check_for_errors() inspects the last query response for an inline error token.

  • Error-queue (SCPI) — check_for_errors() polls the instrument’s error queue. If the transport supports an out-of-band status byte (read_status_byte(), e.g. GPIB serial poll), only the ESB bit is checked first to avoid an unnecessary round-trip when no error is pending.

Attributes:
transport (BaseTransport):

Transport layer instance (serial, Ethernet, GPIB, …).

protocol (BaseProtocol):

Protocol instance (SCPI, Oxford, Lakeshore, …).

auto_check_errors (bool):

When True, write() and query() automatically call check_for_errors() after each operation. Defaults to False.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b"ACME,Model1,SN001,v1.0\n"])
>>> instr = BaseInstrument(transport=t, protocol=ScpiProtocol())
>>> instr.connect()
>>> instr.is_connected
True
>>> instr.query("*IDN?")
'ACME,Model1,SN001,v1.0'
>>> instr.disconnect()
>>> instr.is_connected
False
check_for_errors(*, command: str | None = None) None[source]

Poll the instrument for errors and raise if one is found.

The strategy depends on the protocol and transport combination:

  1. If the protocol uses response-embedded errors (errors_in_response is True), this method is a no-op — errors are detected inline by query().

  2. If the transport supports an out-of-band status byte (read_status_byte() returns a non-None value), the IEEE 488.2 Event Status Bit (ESB, bit 2) is checked. If it is clear, no error query is sent.

  3. Otherwise (or if the ESB bit is set) the protocol’s error_query command is sent and check_error() is called on the response.

Keyword Parameters:
command (str | None):

The command that preceded this check, used to populate the command field of any raised exception. Defaults to None.

Raises:
InstrumentError:

If the instrument reports an error.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b'+0,"No error"\n'])
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.check_for_errors()   # no exception — queue is clear
>>> instr.disconnect()
connect() None[source]

Open the transport connection to the instrument.

Raises:
ConnectionError:

If the underlying transport cannot be opened.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> instr = BaseInstrument(NullTransport(), ScpiProtocol())
>>> instr.connect()
>>> instr.is_connected
True
>>> instr.disconnect()
disconnect() None[source]

Close the transport connection to the instrument.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> instr = BaseInstrument(NullTransport(), ScpiProtocol())
>>> instr.connect()
>>> instr.disconnect()
>>> instr.is_connected
False
identify() str[source]

Return the instrument identification string (*IDN?).

Sends the standard IEEE 488.2 *IDN? query and returns the response. Instruments that do not support *IDN? should override this method.

Returns:
(str):

Identification string returned by the instrument.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b"ACME,Model1,SN001,v1.0\n"])
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.identify()
'ACME,Model1,SN001,v1.0'
>>> instr.disconnect()
property is_connected: bool

True if the underlying transport is currently open.

Returns:
(bool):

Connection state of the transport.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> instr = BaseInstrument(NullTransport(), ScpiProtocol())
>>> instr.is_connected
False
query(command: str) str[source]

Send a query and return the instrument’s response.

Combines write() and read() into a single call. The query is formatted by protocol before transmission.

If auto_check_errors is True:

  • For protocols with response-embedded errors (Oxford, Lakeshore), check_error() is called on the parsed response directly.

  • For error-queue protocols (SCPI), check_for_errors() is called after the response is returned.

Args:
command (str):

Query string in the instrument’s command language.

Returns:
(str):

Parsed response string.

Raises:
ConnectionError:

If the transport is not open.

TimeoutError:

If no response is received within the transport timeout.

InstrumentError:

If auto_check_errors is True and the instrument signals an error.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b"ACME,1,SN,v1\n"])
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.query("*IDN?")
'ACME,1,SN,v1'
>>> instr.disconnect()
read() str[source]

Read a response from the instrument.

Reads until the protocol’s terminator character is received and parses the raw bytes using protocol.

Returns:
(str):

Parsed response string.

Raises:
ConnectionError:

If the transport is not open.

TimeoutError:

If no data is received within the transport timeout.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport(responses=[b"+1.234\n"])
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.read()
'+1.234'
>>> instr.disconnect()
reset() None[source]

Send the standard IEEE 488.2 reset command (*RST).

Instruments that do not support *RST should override this method.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport()
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.reset()
>>> t.write_log
[b'*RST\n']
>>> instr.disconnect()
write(command: str) None[source]

Send a command to the instrument without expecting a response.

The command is formatted by protocol before being passed to transport. If auto_check_errors is True and the protocol uses an error queue (not response-embedded errors), check_for_errors() is called after the command is sent.

Args:
command (str):

Command string in the instrument’s command language.

Raises:
ConnectionError:

If the transport is not open.

InstrumentError:

If auto_check_errors is True and the instrument reports an error after processing the command.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.base_instrument import BaseInstrument
>>> t = NullTransport()
>>> instr = BaseInstrument(t, ScpiProtocol())
>>> instr.connect()
>>> instr.write("OUTP ON")
>>> t.write_log
[b'OUTP ON\n']
>>> instr.disconnect()
class stoner_measurement.instruments.ControlMode(*values)[source]

Bases: Enum

Operating mode of a PID control loop.

Attributes:
OFF:

Loop disabled; no control action.

CLOSED_LOOP:

Standard PID feedback to the assigned input sensor.

ZONE:

Automatic zone-table (scheduled PID) control.

OPEN_LOOP:

Manual heater output; sensor reading is not used for control.

MONITOR:

Sensor monitored but no heater driven (monitor-only input).

CLOSED_LOOP = 'closed_loop'
MONITOR = 'monitor'
OFF = 'off'
OPEN_LOOP = 'open_loop'
ZONE = 'zone'
class stoner_measurement.instruments.ControllerCapabilities(num_inputs: int, num_loops: int, input_channels: tuple[str, ...], loop_numbers: tuple[int, ...], has_ramp: bool = True, has_pid: bool = True, has_autotune: bool = False, has_alarm: bool = False, has_zone: bool = False, has_user_curves: bool = False, has_sensor_excitation: bool = False, has_cryogen_control: bool = False, min_temperature: float | None = None, max_temperature: float | None = None)[source]

Bases: object

Static capability descriptor for a temperature controller driver.

Attributes:
num_inputs (int):

Total number of sensor input channels.

num_loops (int):

Total number of PID control loops.

input_channels (tuple[str, …]):

Ordered tuple of channel identifiers (e.g. ("A", "B", "C")).

loop_numbers (tuple[int, …]):

Ordered tuple of loop numbers (e.g. (1, 2)).

has_ramp (bool):

True if the driver supports setpoint ramping.

has_pid (bool):

True if the driver supports PID parameter read/write.

has_autotune (bool):

True if the driver supports PID autotuning.

has_alarm (bool):

True if the driver supports sensor alarm limits.

has_zone (bool):

True if the driver supports zone/table (scheduled PID) control.

has_user_curves (bool):

True if the driver supports user-defined calibration curves.

has_sensor_excitation (bool):

True if the driver supports configuring sensor excitation.

has_cryogen_control (bool):

True if the driver supports cryogen-flow or needle-valve control (e.g. Oxford Instruments ITC503 or Mercury iTC).

min_temperature (float | None):

Minimum achievable temperature in Kelvin, or None if unknown.

max_temperature (float | None):

Maximum controllable temperature in Kelvin, or None if unknown.

has_alarm: bool = False
has_autotune: bool = False
has_cryogen_control: bool = False
has_pid: bool = True
has_ramp: bool = True
has_sensor_excitation: bool = False
has_user_curves: bool = False
has_zone: bool = False
input_channels: tuple[str, ...] = <dataclasses._MISSING_TYPE object>
loop_numbers: tuple[int, ...] = <dataclasses._MISSING_TYPE object>
max_temperature: float | None = None
min_temperature: float | None = None
num_inputs: int = <dataclasses._MISSING_TYPE object>
num_loops: int = <dataclasses._MISSING_TYPE object>
class stoner_measurement.instruments.CurrentSource(transport: BaseTransport, protocol: BaseProtocol)[source]

Bases: BaseInstrument

Abstract base class for precision current-source instruments.

Provides a shared API for DC current level, compliance voltage, and output enable control, with optional AC waveform and balanced-channel extensions advertised via get_capabilities().

configure_custom_sweep(values: tuple[float, ...], *, delay: float = 0.0, count: int = 1) None[source]

Configure a custom point-by-point current sweep.

Args:
values (tuple[float, …]):

Explicit current values in amps.

Keyword Parameters:
delay (float):

Source settling delay between points in seconds.

count (int):

Number of sweep repetitions.

Raises:
NotImplementedError:

If sweep is not supported. Check get_capabilities().has_sweep before calling.

configure_linear_sweep(start: float, stop: float, points: int, *, delay: float = 0.0, count: int = 1) None[source]

Configure a linearly spaced current sweep.

Args:
start (float):

Sweep start current in amps.

stop (float):

Sweep stop current in amps.

points (int):

Number of sweep points.

Keyword Parameters:
delay (float):

Source settling delay between sweep points in seconds.

count (int):

Number of sweep repetitions.

Raises:
NotImplementedError:

If sweep is not supported. Check get_capabilities().has_sweep before calling.

configure_log_sweep(start: float, stop: float, points: int, *, delay: float = 0.0, count: int = 1) None[source]

Configure a logarithmically spaced current sweep.

Args:
start (float):

Sweep start current in amps.

stop (float):

Sweep stop current in amps.

points (int):

Number of sweep points.

Keyword Parameters:
delay (float):

Source settling delay between sweep points in seconds.

count (int):

Number of sweep repetitions.

Raises:
NotImplementedError:

If sweep is not supported. Check get_capabilities().has_sweep before calling.

configure_pulsed_sweep(sweep: CurrentSweepConfiguration, pulse: PulsedSweepConfiguration) None[source]

Configure a pulsed current sweep.

Each point in sweep becomes a pulse of duration pulse.width seconds followed by pulse.off_time seconds at pulse.low_level before the next sweep point.

Args:
sweep (CurrentSweepConfiguration):

Sweep point configuration.

pulse (PulsedSweepConfiguration):

Pulse timing and baseline current configuration.

Raises:
NotImplementedError:

If pulsed sweep is not supported by the driver. Check get_capabilities().has_pulsed_sweep before calling.

configure_sweep(config: CurrentSweepConfiguration) None[source]

Configure a built-in current sweep.

Args:
config (CurrentSweepConfiguration):

Sweep configuration (spacing, start, stop, points, values, delay, count).

Raises:
NotImplementedError:

If sweep is not supported by the driver. Check get_capabilities().has_sweep before calling.

abstractmethod enable_output(state: bool) None[source]

Enable or disable source output.

abstractmethod get_capabilities() CurrentSourceCapabilities[source]

Return the static capability descriptor for this driver.

get_channel_level(channel: int) float[source]

Return programmed current for a specific output channel.

Raises:
NotImplementedError:

If per-channel current control is not supported by the driver.

abstractmethod get_compliance_voltage() float[source]

Return compliance voltage in volts.

get_frequency() float[source]

Return AC waveform frequency in Hz.

Raises:
NotImplementedError:

If AC frequency control is not supported by the driver.

get_offset_current() float[source]

Return AC waveform DC offset current in amps.

Raises:
NotImplementedError:

If offset-current control is not supported by the driver.

abstractmethod get_source_level() float[source]

Return programmed source current in amps.

get_waveform() CurrentWaveform[source]

Return the configured output waveform.

Raises:
NotImplementedError:

If waveform selection is not supported by the driver.

abstractmethod output_enabled() bool[source]

Return True if source output is enabled.

set_channel_level(channel: int, value: float) None[source]

Set current for a specific output channel.

Raises:
NotImplementedError:

If per-channel current control is not supported by the driver.

abstractmethod set_compliance_voltage(value: float) None[source]

Set compliance voltage in volts.

set_frequency(value: float) None[source]

Set AC waveform frequency in Hz.

Raises:
NotImplementedError:

If AC frequency control is not supported by the driver.

set_offset_current(value: float) None[source]

Set AC waveform DC offset current in amps.

Raises:
NotImplementedError:

If offset-current control is not supported by the driver.

abstractmethod set_source_level(value: float) None[source]

Set source current in amps.

set_waveform(waveform: CurrentWaveform) None[source]

Set output waveform mode.

Raises:
NotImplementedError:

If waveform selection is not supported by the driver.

sweep_abort() None[source]

Abort a running or armed sweep.

Raises:
NotImplementedError:

If sweep is not supported by the driver. Check get_capabilities().has_sweep before calling.

sweep_start() None[source]

Arm the configured sweep, making it ready for triggering.

Raises:
NotImplementedError:

If sweep is not supported by the driver. Check get_capabilities().has_sweep before calling.

class stoner_measurement.instruments.CurrentSourceCapabilities(has_waveform_selection: bool = False, has_frequency_control: bool = False, has_offset_current: bool = False, has_balanced_outputs: bool = False, has_sweep: bool = False, has_pulsed_sweep: bool = False, channel_count: int = 1)[source]

Bases: object

Static capability descriptor for a current-source driver.

Attributes:
has_waveform_selection (bool):

True if the source can select waveform mode.

has_frequency_control (bool):

True if AC waveform frequency can be configured.

has_offset_current (bool):

True if waveform DC offset current can be configured.

has_balanced_outputs (bool):

True if the source supports balanced multi-channel output pairs.

has_sweep (bool):

True if the source supports built-in source sweeps via configure_sweep(), sweep_start(), and sweep_abort().

has_pulsed_sweep (bool):

True if the source supports pulsed sweeps via configure_pulsed_sweep().

channel_count (int):

Number of independently addressable output channels.

channel_count: int = 1
has_balanced_outputs: bool = False
has_frequency_control: bool = False
has_offset_current: bool = False
has_pulsed_sweep: bool = False
has_sweep: bool = False
has_waveform_selection: bool = False
class stoner_measurement.instruments.CurrentSweepConfiguration(start: float = 0.0, stop: float = 0.0, points: int = 0, spacing: CurrentSweepSpacing = CurrentSweepSpacing.LIN, values: tuple[float, ...] | None = None, delay: float = 0.0, count: int = 1)[source]

Bases: object

Configuration for a current-source sweep.

Attributes:
start (float):

Sweep start value in amps. Ignored for list sweeps.

stop (float):

Sweep stop value in amps. Ignored for list sweeps.

points (int):

Number of sweep points. For list sweeps this is inferred from len(values) by the driver; ignored when values is provided.

spacing (CurrentSweepSpacing):

Point-spacing mode. Defaults to LIN.

values (tuple[float, …] | None):

Explicit source values for LIST sweeps. Ignored for linear and logarithmic sweeps.

delay (float):

Source settling delay between sweep points in seconds.

count (int):

Number of sweep repetitions. Defaults to 1.

Notes:

Default start, stop, and points values are placeholders. For list sweeps supply values and omit start/stop/points.

count: int = 1
delay: float = 0.0
points: int = 0
spacing: CurrentSweepSpacing = 'LIN'
start: float = 0.0
stop: float = 0.0
values: tuple[float, ...] | None = None
class stoner_measurement.instruments.CurrentSweepSpacing(*values)[source]

Bases: Enum

Point-spacing mode for a built-in current sweep.

Attributes:
LIN:

Linearly spaced sweep points from start to stop.

LOG:

Logarithmically spaced sweep points from start to stop.

LIST:

Arbitrary point list supplied in CurrentSweepConfiguration.values.

LIN = 'LIN'
LIST = 'LIST'
LOG = 'LOG'
class stoner_measurement.instruments.CurrentWaveform(*values)[source]

Bases: Enum

Output waveform mode of a current-source instrument.

DC = 'DC'
SINE = 'SINE'
class stoner_measurement.instruments.DigitalMultimeter(transport: BaseTransport, protocol: BaseProtocol)[source]

Bases: BaseInstrument

Abstract base class for digital multimeter instruments.

Attributes:
transport (BaseTransport):

Transport layer instance.

protocol (BaseProtocol):

Protocol instance.

abort() None[source]

Abort a running measurement sequence.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

clear_buffer() None[source]

Clear the instrument reading buffer.

Raises:
NotImplementedError:

If buffer operations are not supported by the instrument.

abstractmethod get_autorange() bool[source]

Return True if autorange is enabled.

Returns:
(bool):

True when autorange is enabled.

get_buffer_count() int[source]

Return the number of readings currently stored in the buffer.

Raises:
NotImplementedError:

If buffer operations are not supported by the instrument.

abstractmethod get_capabilities() DmmCapabilities[source]

Return static capability metadata.

Returns:
(DmmCapabilities):

Capability descriptor.

get_filter_count() int[source]

Return the configured filter averaging count.

Raises:
NotImplementedError:

If filtering is not supported by the instrument.

get_filter_enabled() bool[source]

Return whether filtering is enabled.

Raises:
NotImplementedError:

If filtering is not supported by the instrument.

abstractmethod get_measure_function() DmmFunction[source]

Return the active measurement function.

Returns:
(DmmFunction):

Active measurement function.

abstractmethod get_nplc() float[source]

Return the integration time in line cycles.

Returns:
(float):

Integration time in power-line cycles.

abstractmethod get_range() float[source]

Return the active measurement range.

Returns:
(float):

Range value in units of the active function.

get_trigger_count() int[source]

Return the configured trigger count.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

get_trigger_source() DmmTriggerSource[source]

Return the trigger source selection.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

initiate() None[source]

Arm the trigger system and begin measurements.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

abstractmethod measure() float[source]

Trigger a measurement and return its value.

Returns:
(float):

Measured scalar value in units of the active function.

read_buffer(count: int | None = None) tuple[float, ...][source]

Read values from the instrument buffer.

Keyword Parameters:
count (int | None):

Optional number of points to read from the start of the buffer. If None, read all available points.

Returns:
(tuple[float, …]):

Parsed buffer values.

Raises:
NotImplementedError:

If buffer operations are not supported by the instrument.

abstractmethod set_autorange(state: bool) None[source]

Enable or disable autorange.

Args:
state (bool):

True to enable autorange.

set_filter_count(count: int) None[source]

Set the filter averaging count.

Raises:
NotImplementedError:

If filtering is not supported by the instrument.

set_filter_enabled(state: bool) None[source]

Enable or disable measurement filtering.

Raises:
NotImplementedError:

If filtering is not supported by the instrument.

abstractmethod set_measure_function(function: DmmFunction) None[source]

Set the active measurement function.

Args:
function (DmmFunction):

Function to select.

abstractmethod set_nplc(value: float) None[source]

Set the integration time in line cycles.

Args:
value (float):

Integration time in power-line cycles.

abstractmethod set_range(value: float) None[source]

Set the active measurement range.

Args:
value (float):

Range value in units of the active function.

set_trigger_count(count: int) None[source]

Set the trigger count.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

set_trigger_source(source: DmmTriggerSource) None[source]

Set the trigger source selection.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

class stoner_measurement.instruments.DmmCapabilities(has_function_selection: bool = True, has_filter: bool = False, has_trigger: bool = False, has_buffer: bool = False, supported_functions: tuple[DmmFunction, ...] = (DmmFunction.VOLT_DC,))[source]

Bases: object

Static capability descriptor for a DMM driver.

has_buffer: bool = False
has_filter: bool = False
has_function_selection: bool = True
has_trigger: bool = False
supported_functions: tuple[DmmFunction, ...] = (DmmFunction.VOLT_DC,)
class stoner_measurement.instruments.DmmFunction(*values)[source]

Bases: Enum

Measurement functions for digital multimeters.

CURR_AC = 'CURR:AC'
CURR_DC = 'CURR:DC'
FREQ = 'FREQ'
FRES = 'FRES'
PER = 'PER'
RES = 'RES'
TEMP = 'TEMP'
VOLT_AC = 'VOLT:AC'
VOLT_DC = 'VOLT:DC'
class stoner_measurement.instruments.DmmTriggerSource(*values)[source]

Bases: Enum

Trigger-source selection for digital multimeters.

BUS = 'BUS'
EXT = 'EXT'
IMM = 'IMM'
MAN = 'MAN'
TIM = 'TIM'
class stoner_measurement.instruments.Electrometer(transport: BaseTransport, protocol: BaseProtocol)[source]

Bases: BaseInstrument

Abstract base class for electrometer and picoammeter instruments.

abort() None[source]

Abort trigger execution.

clear_buffer() None[source]

Clear trace buffer data.

configure_trigger_model(config: ElectrometerTriggerConfiguration) None[source]

Configure trigger and arm model settings.

abstractmethod get_autorange() bool[source]

Return True if autorange is enabled.

get_buffer_size() int[source]

Return trace buffer size.

abstractmethod get_capabilities() ElectrometerCapabilities[source]

Return static feature capabilities for the driver.

get_data_format() ElectrometerDataFormat[source]

Return configured response data format.

get_filter_count() int[source]

Return averaging filter count.

get_filter_enabled() bool[source]

Return True if digital averaging filter is enabled.

get_measure_functions() tuple[ElectrometerFunction, ...][source]

Return enabled measurement functions.

abstractmethod get_nplc() float[source]

Return integration time in power-line cycles.

abstractmethod get_range() float[source]

Return the active current range in amps.

initiate() None[source]

Start trigger execution.

abstractmethod measure_current() float[source]

Trigger a current measurement and return the value in amps.

read_buffer(count: int | None = None) tuple[float, ...][source]

Return trace buffer readings.

abstractmethod set_autorange(state: bool) None[source]

Enable or disable autorange.

set_buffer_size(size: int) None[source]

Set trace buffer size.

set_data_format(data_format: ElectrometerDataFormat) None[source]

Set response data format.

set_filter_count(count: int) None[source]

Set averaging filter count.

set_filter_enabled(state: bool) None[source]

Enable or disable digital averaging filter.

set_measure_functions(functions: tuple[ElectrometerFunction, ...]) None[source]

Enable one or more measurement functions.

abstractmethod set_nplc(value: float) None[source]

Set integration time in power-line cycles.

abstractmethod set_range(value: float) None[source]

Set the current range in amps.

class stoner_measurement.instruments.ElectrometerCapabilities(has_function_selection: bool = False, has_filter: bool = False, has_trigger_model: bool = False, has_buffer: bool = False, has_data_format: bool = False)[source]

Bases: object

Static capability descriptor for an electrometer driver.

has_buffer: bool = False
has_data_format: bool = False
has_filter: bool = False
has_function_selection: bool = False
has_trigger_model: bool = False
class stoner_measurement.instruments.ElectrometerDataFormat(*values)[source]

Bases: Enum

Data transfer format used for query responses.

ASCII = 'ASC'
DREAL = 'DRE'
SREAL = 'SRE'
class stoner_measurement.instruments.ElectrometerFunction(*values)[source]

Bases: Enum

Measurement function selected on an electrometer.

CHARGE = 'CHAR'
CURR = 'CURR'
RES = 'RES'
VOLT = 'VOLT'
class stoner_measurement.instruments.ElectrometerTriggerConfiguration(trigger_source: ElectrometerTriggerSource = ElectrometerTriggerSource.IMM, trigger_count: int = 1, trigger_delay: float = 0.0, arm_source: ElectrometerTriggerSource = ElectrometerTriggerSource.IMM, arm_count: int = 1)[source]

Bases: object

Configuration for trigger and arm model settings.

arm_count: int = 1
arm_source: ElectrometerTriggerSource = 'IMM'
trigger_count: int = 1
trigger_delay: float = 0.0
trigger_source: ElectrometerTriggerSource = 'IMM'
class stoner_measurement.instruments.ElectrometerTriggerSource(*values)[source]

Bases: Enum

Trigger source for a simple trigger model.

BUS = 'BUS'
EXT = 'EXT'
IMM = 'IMM'
TIM = 'TIM'
TLIN = 'TLIN'
class stoner_measurement.instruments.InstrumentDriverManager[source]

Bases: object

Discover and provide access to concrete instrument driver classes.

Discovery combines:

  • Built-in drivers found by scanning stoner_measurement.instruments modules.

  • Third-party drivers exposed via the stoner_measurement.instruments entry-point group.

discover() None[source]

Discover built-in and entry-point instrument drivers.

property driver_classes: dict[str, type[BaseInstrument]]

Return a copy of discovered driver classes.

property driver_names: list[str]

Return sorted discovered driver names.

drivers_by_type(instrument_type: type[BaseInstrument], *, include_abstract: bool = False) dict[str, type[BaseInstrument]][source]

Return discovered drivers that subclass a specific instrument type.

Args:
instrument_type (type[BaseInstrument]):

Base class/interface to filter by, for example MagnetController or TemperatureController.

Keyword Parameters:
include_abstract (bool):

If True, include abstract classes in results. Defaults to False.

Returns:
(dict[str, type[BaseInstrument]]):

Mapping of driver name to driver class.

Raises:
TypeError:

If instrument_type is not a BaseInstrument subclass.

get(name: str) type[BaseInstrument] | None[source]

Return a discovered driver class by name, or None.

register(name: str, driver_cls: type[BaseInstrument]) None[source]

Manually register a driver class.

Args:
name (str):

Unique identifier for the driver.

driver_cls (type[BaseInstrument]):

Driver class to register.

Raises:
TypeError:

If driver_cls is not a BaseInstrument subclass.

unregister(name: str) None[source]

Remove a driver class from the registry.

Args:
name (str):

Driver identifier to remove.

exception stoner_measurement.instruments.InstrumentError(message: str, *, command: str | None = None, error_code: int | None = None)[source]

Bases: Exception

Raised when an instrument reports a command or execution error.

Carries structured information about the failure so that calling code can log or display a meaningful diagnostic without parsing the exception message string.

Attributes:
command (str | None):

The command string that triggered the error, or None if the failing command is not known.

error_code (int | None):

Numeric error code returned by the instrument (e.g. the SCPI integer before the comma in a SYST:ERR? response), or None for protocols that do not use numeric codes.

message (str):

Human-readable description of the error as reported by the instrument.

Examples:
>>> from stoner_measurement.instruments.errors import InstrumentError
>>> exc = InstrumentError(
...     "Undefined header",
...     command="*IDN",
...     error_code=-113,
... )
>>> exc.command
'*IDN'
>>> exc.error_code
-113
>>> str(exc)
'Undefined header (command: *IDN, code: -113)'
class stoner_measurement.instruments.LockInAmplifier(transport: BaseTransport, protocol: BaseProtocol)[source]

Bases: BaseInstrument

Abstract base class for lock-in amplifier instruments.

Provides a common interface for dual-output lock-in amplifiers where the measured signal is available as in-phase/quadrature components (X, Y) and polar components (magnitude R, angle theta).

Attributes:
transport (BaseTransport):

Transport layer instance.

protocol (BaseProtocol):

Protocol instance.

auto_gain() None[source]

Run instrument auto-gain adjustment.

Raises:
NotImplementedError:

If auto-gain is not supported by the instrument.

auto_phase() None[source]

Run instrument auto-phase adjustment.

Raises:
NotImplementedError:

If auto-phase is not supported by the instrument.

auto_reserve() None[source]

Run instrument auto-reserve adjustment.

Raises:
NotImplementedError:

If auto-reserve is not supported by the instrument.

abstractmethod get_capabilities() LockInAmplifierCapabilities[source]

Return static capability metadata.

Returns:
(LockInAmplifierCapabilities):

Capability descriptor.

get_dynamic_reserve_db() float[source]

Return the dynamic reserve (signal stability) in decibels.

A higher value indicates that the instrument can tolerate larger interfering signals relative to the signal of interest without introducing significant errors.

Returns:
(float):

Dynamic reserve in dB.

Raises:
NotImplementedError:

If numeric dynamic reserve control is not supported by the instrument.

get_filter_slope() int[source]

Return the output filter roll-off slope in dB/octave.

Raises:
NotImplementedError:

If filter-slope control is not supported by the instrument.

get_harmonic() int[source]

Return the selected detection harmonic.

Raises:
NotImplementedError:

If harmonic selection is not supported by the instrument.

get_input_coupling() LockInInputCoupling[source]

Return the input coupling mode.

Raises:
NotImplementedError:

If input-coupling control is not supported by the instrument.

get_input_shielding() LockInInputShielding[source]

Return the input shield grounding mode.

Returns:
(LockInInputShielding):

Active input shield grounding mode.

Raises:
NotImplementedError:

If input shielding control is not supported by the instrument.

get_input_source() LockInInputSource[source]

Return the active input source.

Returns:
(LockInInputSource):

Active input source selection.

Raises:
NotImplementedError:

If input source selection is not supported by the instrument.

get_line_filter() LockInLineFilter[source]

Return the line-frequency notch filter configuration.

Returns:
(LockInLineFilter):

Active notch filter configuration.

Raises:
NotImplementedError:

If line filter control is not supported by the instrument.

get_oscillator_amplitude() float[source]

Return the internal oscillator sine output amplitude in volts.

Returns:
(float):

Oscillator amplitude in volts.

Raises:
NotImplementedError:

If the instrument does not have a controllable internal oscillator.

get_output_offset(channel: LockInOutputChannel) tuple[float, LockInExpandFactor][source]

Return the output offset percentage and expand factor for a channel.

Args:
channel (LockInOutputChannel):

Output channel to query.

Returns:
(tuple[float, LockInExpandFactor]):

(offset_pct, expand_factor) where offset_pct is the offset as a percentage and expand_factor is the expand multiplier.

Raises:
NotImplementedError:

If output offset and expand are not supported by the instrument.

abstractmethod get_reference_frequency() float[source]

Return the reference frequency in hertz.

Returns:
(float):

Reference frequency in hertz.

abstractmethod get_reference_phase() float[source]

Return the reference phase in degrees.

Returns:
(float):

Reference phase in degrees.

abstractmethod get_reference_source() LockInReferenceSource[source]

Return the active reference source.

Returns:
(LockInReferenceSource):

Active reference source.

get_reserve_mode() LockInReserveMode[source]

Return the dynamic reserve mode.

Raises:
NotImplementedError:

If reserve-mode control is not supported by the instrument.

abstractmethod get_sensitivity() float[source]

Return the active sensitivity scale.

Returns:
(float):

Sensitivity in volts.

get_sync_filter_enabled() bool[source]

Return whether the synchronous output filter is enabled.

Returns:
(bool):

True when the synchronous filter is enabled.

Raises:
NotImplementedError:

If synchronous filter control is not supported by the instrument.

abstractmethod get_time_constant() float[source]

Return the active output filter time constant.

Returns:
(float):

Time constant in seconds.

abstractmethod measure_rt() tuple[float, float][source]

Measure and return magnitude and phase outputs.

Returns:
(tuple[float, float]):

(magnitude, theta) where magnitude is in volts and theta is in degrees.

abstractmethod measure_xy() tuple[float, float][source]

Measure and return the in-phase and quadrature outputs.

Returns:
(tuple[float, float]):

(x, y) values in volts, where x is the in-phase component and y is the quadrature component.

set_dynamic_reserve_db(value_db: float) None[source]

Set the dynamic reserve (signal stability) in decibels.

Args:
value_db (float):

Desired dynamic reserve in dB.

Raises:
NotImplementedError:

If numeric dynamic reserve control is not supported by the instrument.

set_filter_slope(slope: int) None[source]

Set the output filter roll-off slope in dB/octave.

Raises:
NotImplementedError:

If filter-slope control is not supported by the instrument.

set_harmonic(harmonic: int) None[source]

Set the selected detection harmonic.

Raises:
NotImplementedError:

If harmonic selection is not supported by the instrument.

set_input_coupling(coupling: LockInInputCoupling) None[source]

Set the input coupling mode.

Raises:
NotImplementedError:

If input-coupling control is not supported by the instrument.

set_input_shielding(shielding: LockInInputShielding) None[source]

Set the input shield grounding mode.

Args:
shielding (LockInInputShielding):

Shielding mode to select.

Raises:
NotImplementedError:

If input shielding control is not supported by the instrument.

set_input_source(source: LockInInputSource) None[source]

Set the active input source.

Args:
source (LockInInputSource):

Input source to select.

Raises:
NotImplementedError:

If input source selection is not supported by the instrument.

set_line_filter(filter_config: LockInLineFilter) None[source]

Set the line-frequency notch filter configuration.

Args:
filter_config (LockInLineFilter):

Notch filter configuration to apply.

Raises:
NotImplementedError:

If line filter control is not supported by the instrument.

set_oscillator_amplitude(value: float) None[source]

Set the internal oscillator sine output amplitude in volts.

Args:
value (float):

Amplitude in volts.

Raises:
NotImplementedError:

If the instrument does not have a controllable internal oscillator.

set_output_offset(channel: LockInOutputChannel, offset_pct: float, expand_factor: LockInExpandFactor) None[source]

Set the output offset and expand factor for a channel.

Args:
channel (LockInOutputChannel):

Output channel to configure.

offset_pct (float):

Offset as a percentage (typically −105 % to +105 %).

expand_factor (LockInExpandFactor):

Expand multiplier to apply.

Raises:
NotImplementedError:

If output offset and expand are not supported by the instrument.

abstractmethod set_reference_frequency(value: float) None[source]

Set the reference frequency in hertz.

Args:
value (float):

Frequency in hertz.

abstractmethod set_reference_phase(value: float) None[source]

Set the reference phase in degrees.

Args:
value (float):

Phase in degrees.

abstractmethod set_reference_source(source: LockInReferenceSource) None[source]

Set the active reference source.

Args:
source (LockInReferenceSource):

Source to select.

set_reserve_mode(mode: LockInReserveMode) None[source]

Set the dynamic reserve mode.

Raises:
NotImplementedError:

If reserve-mode control is not supported by the instrument.

abstractmethod set_sensitivity(value: float) None[source]

Set the active sensitivity scale.

Args:
value (float):

Sensitivity in volts.

set_sync_filter_enabled(state: bool) None[source]

Enable or disable the synchronous output filter.

Args:
state (bool):

True to enable the synchronous filter.

Raises:
NotImplementedError:

If synchronous filter control is not supported by the instrument.

abstractmethod set_time_constant(value: float) None[source]

Set the output filter time constant.

Args:
value (float):

Time constant in seconds.

class stoner_measurement.instruments.LockInAmplifierCapabilities(has_reference_source_selection: bool = True, has_reference_frequency_control: bool = True, has_reference_phase_control: bool = True, has_harmonic_selection: bool = False, has_filter_slope_control: bool = False, has_input_coupling_control: bool = False, has_reserve_mode_control: bool = False, has_auto_gain: bool = False, has_auto_phase: bool = False, has_auto_reserve: bool = False, has_output_offset: bool = False, has_internal_oscillator: bool = False, has_input_source_selection: bool = False, has_input_shielding_control: bool = False, has_line_filter_control: bool = False, has_sync_filter: bool = False, has_dynamic_reserve_db: bool = False, max_harmonic: int = 0)[source]

Bases: object

Static capability descriptor for a lock-in amplifier driver.

Attributes:
max_harmonic (int):

Highest harmonic supported for lock-in detection. A value of 0 means harmonic selection is not available or the limit is not known; when has_harmonic_selection is True and max_harmonic is 0 the driver imposes no upper bound.

has_auto_gain: bool = False
has_auto_phase: bool = False
has_auto_reserve: bool = False
has_dynamic_reserve_db: bool = False
has_filter_slope_control: bool = False
has_harmonic_selection: bool = False
has_input_coupling_control: bool = False
has_input_shielding_control: bool = False
has_input_source_selection: bool = False
has_internal_oscillator: bool = False
has_line_filter_control: bool = False
has_output_offset: bool = False
has_reference_frequency_control: bool = True
has_reference_phase_control: bool = True
has_reference_source_selection: bool = True
has_reserve_mode_control: bool = False
has_sync_filter: bool = False
max_harmonic: int = 0
class stoner_measurement.instruments.LockInExpandFactor(*values)[source]

Bases: Enum

Expand factor for output offset operations.

X1 = 1
X10 = 10
X100 = 100
class stoner_measurement.instruments.LockInInputCoupling(*values)[source]

Bases: Enum

Input-coupling selection for lock-in amplifiers.

AC = 'AC'
DC = 'DC'
class stoner_measurement.instruments.LockInInputShielding(*values)[source]

Bases: Enum

Input shield grounding selection for lock-in amplifiers.

FLOAT = 'FLOAT'
GROUND = 'GROUND'
class stoner_measurement.instruments.LockInInputSource(*values)[source]

Bases: Enum

Input source selection for lock-in amplifiers.

A = 'A'
A_MINUS_B = 'A_MINUS_B'
I_100MOHM = 'I_100MOHM'
I_1MOHM = 'I_1MOHM'
class stoner_measurement.instruments.LockInLineFilter(*values)[source]

Bases: Enum

Line-frequency notch filter configuration for lock-in amplifiers.

BOTH = 'BOTH'
LINE = 'LINE'
LINE_2X = 'LINE_2X'
NONE = 'NONE'
class stoner_measurement.instruments.LockInOutputChannel(*values)[source]

Bases: Enum

Output channel selection for offset and expand operations.

R = 'R'
X = 'X'
Y = 'Y'
class stoner_measurement.instruments.LockInReferenceSource(*values)[source]

Bases: Enum

Reference-source selection for lock-in amplifiers.

EXTERNAL = 'EXTERNAL'
INTERNAL = 'INTERNAL'
class stoner_measurement.instruments.LockInReserveMode(*values)[source]

Bases: Enum

Dynamic-reserve operating modes.

HIGH_RESERVE = 'HIGH_RESERVE'
LOW_NOISE = 'LOW_NOISE'
NORMAL = 'NORMAL'
class stoner_measurement.instruments.LoopStatus(setpoint: float, process_value: float, mode: ControlMode, heater_output: float, ramp_enabled: bool, ramp_rate: float, ramp_state: RampState, p: float, i: float, d: float, input_channel: str)[source]

Bases: object

A snapshot of one PID control loop’s complete state.

Attributes:
setpoint (float):

Current setpoint temperature in Kelvin.

process_value (float):

Temperature reading at the loop’s input sensor in Kelvin.

mode (ControlMode):

Active control mode of the loop.

heater_output (float):

Heater output as a percentage (0–100 %).

ramp_enabled (bool):

True when automatic setpoint ramping is enabled.

ramp_rate (float):

Setpoint ramp rate in Kelvin per minute.

ramp_state (RampState):

Whether the loop is currently executing a ramp.

p (float):

Proportional gain.

i (float):

Integral gain.

d (float):

Derivative gain.

input_channel (str):

Identifier of the sensor channel driving this loop.

d: float = <dataclasses._MISSING_TYPE object>
heater_output: float = <dataclasses._MISSING_TYPE object>
i: float = <dataclasses._MISSING_TYPE object>
input_channel: str = <dataclasses._MISSING_TYPE object>
mode: ControlMode = <dataclasses._MISSING_TYPE object>
p: float = <dataclasses._MISSING_TYPE object>
process_value: float = <dataclasses._MISSING_TYPE object>
ramp_enabled: bool = <dataclasses._MISSING_TYPE object>
ramp_rate: float = <dataclasses._MISSING_TYPE object>
ramp_state: RampState = <dataclasses._MISSING_TYPE object>
setpoint: float = <dataclasses._MISSING_TYPE object>
class stoner_measurement.instruments.MagnetController(transport: BaseTransport, protocol: BaseProtocol)[source]

Bases: BaseInstrument

Abstract base class for superconducting magnet power supply drivers.

Provides a uniform interface for controlling superconducting magnet power supplies such as the Oxford Instruments IPS120-10. All field values are in tesla and ramp rates in tesla per minute unless otherwise stated.

Subclasses must implement all abstract methods.

Attributes:
transport (BaseTransport):

Transport layer instance.

protocol (BaseProtocol):

Protocol layer instance.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import OxfordProtocol
>>> from stoner_measurement.instruments.magnet_controller import (
...     MagnetController, MagnetState, MagnetStatus, MagnetLimits,
... )
>>> class _MC(MagnetController):
...     def get_model(self): return "TestMagnet"
...     def get_firmware_version(self): return "1.0"
...     @property
...     def current(self): return 0.0
...     @property
...     def field(self): return 0.0
...     @property
...     def voltage(self): return 0.0
...     @property
...     def status(self):
...         return MagnetStatus(
...             state=MagnetState.STANDBY, current=0.0, field=0.0,
...             voltage=0.0, persistent=False, heater_on=False,
...             at_target=True,
...         )
...     @property
...     def magnet_constant(self): return 0.1
...     @property
...     def limits(self): return MagnetLimits(max_current=100.0)
...     @property
...     def heater(self): return False
...     def set_target_current(self, current): pass
...     def set_target_field(self, field): pass
...     def set_ramp_rate_current(self, rate): pass
...     def set_ramp_rate_field(self, rate): pass
...     def set_magnet_constant(self, tesla_per_amp): pass
...     def set_limits(self, limits): pass
...     def ramp_to_target(self): pass
...     def ramp_to_current(self, current, *, wait=False): pass
...     def ramp_to_field(self, field, *, wait=False): pass
...     def pause_ramp(self): pass
...     def abort_ramp(self): pass
...     def heater_on(self): pass
...     def heater_off(self): pass
>>> mc = _MC(NullTransport(), OxfordProtocol())
>>> mc.get_model()
'TestMagnet'
>>> mc.status.state
<MagnetState.STANDBY: 'standby'>
abstractmethod abort_ramp() None[source]

Abort ramping immediately and hold the output at its current value.

Raises:
ConnectionError:

If the transport is not open.

abstract property current: float

Return the current output in amps.

Returns:
(float):

Instantaneous output current in amps.

Raises:
ConnectionError:

If the transport is not open.

abstract property field: float

Return the magnetic field output in tesla.

Returns:
(float):

Estimated magnetic field in tesla derived from the output current and the configured magnet constant.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod get_firmware_version() str[source]

Return the firmware version string.

Returns:
(str):

Firmware version as reported by the device.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> mc.get_firmware_version()
'1.0'
abstractmethod get_model() str[source]

Return the instrument model identifier string.

Returns:
(str):

Instrument model identifier as reported by the device.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> mc.get_model()
'TestMagnet'
abstract property heater: bool

Return the persistent switch heater state.

Returns:
(bool):

True when the persistent switch heater is energised, False when it is off.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod heater_off() None[source]

De-energise the persistent switch heater.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod heater_on() None[source]

Energise the persistent switch heater.

Raises:
ConnectionError:

If the transport is not open.

abstract property limits: MagnetLimits

Return the configured operating limits.

Returns:
(MagnetLimits):

Maximum permitted current, field, and ramp rate.

Raises:
ConnectionError:

If the transport is not open.

abstract property magnet_constant: float

Return the magnet constant in tesla per amp.

Returns:
(float):

Field-to-current conversion factor in T A⁻¹.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod pause_ramp() None[source]

Pause an active ramp, holding the output at its current value.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod ramp_to_current(current: float, *, wait: bool = False) None[source]

Set a new target current and begin ramping.

Args:
current (float):

Desired target current in amps.

Keyword Parameters:
wait (bool):

If True, block until the target is reached. Defaults to False.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If current exceeds the configured maximum.

abstractmethod ramp_to_field(field: float, *, wait: bool = False) None[source]

Set a new target field and begin ramping.

Args:
field (float):

Desired target field in tesla.

Keyword Parameters:
wait (bool):

If True, block until the target is reached. Defaults to False.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If field exceeds the configured maximum.

abstractmethod ramp_to_target() None[source]

Start ramping the output towards the currently programmed target.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod set_limits(limits: MagnetLimits) None[source]

Set operating limits for the controller.

Args:
limits (MagnetLimits):

Maximum current, field, and ramp rate limits to apply.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod set_magnet_constant(tesla_per_amp: float) None[source]

Set the magnet constant used for field calculations.

Args:
tesla_per_amp (float):

Field-to-current conversion factor in T A⁻¹.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If tesla_per_amp is not positive.

abstractmethod set_ramp_rate_current(rate: float) None[source]

Set the current ramp rate.

Args:
rate (float):

Ramp rate in amps per second.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If rate exceeds the configured maximum or is negative.

abstractmethod set_ramp_rate_field(rate: float) None[source]

Set the field ramp rate.

Args:
rate (float):

Ramp rate in tesla per minute.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If rate exceeds the configured maximum or is negative.

abstractmethod set_target_current(current: float) None[source]

Set the target output current.

Args:
current (float):

Desired target current in amps.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If current exceeds the configured maximum.

abstractmethod set_target_field(field: float) None[source]

Set the target magnetic field.

Args:
field (float):

Desired target field in tesla.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If field exceeds the configured maximum.

abstract property status: MagnetStatus

Return a consolidated status snapshot.

Returns:
(MagnetStatus):

Current operational state, output readings, and heater status.

Raises:
ConnectionError:

If the transport is not open.

abstract property voltage: float

Return the output voltage in volts.

Returns:
(float):

Instantaneous output voltage in volts.

Raises:
ConnectionError:

If the transport is not open.

class stoner_measurement.instruments.MagnetLimits(max_current: float, max_field: float | None = None, max_ramp_rate: float | None = None)[source]

Bases: object

Operating limits for a superconducting magnet power supply.

Attributes:
max_current (float):

Maximum permitted output current in amps.

max_field (float | None):

Maximum permitted field in tesla, or None if not configured.

max_ramp_rate (float | None):

Maximum permitted ramp rate in amps per second or tesla per minute (instrument-specific units), or None if not configured.

max_current: float = <dataclasses._MISSING_TYPE object>
max_field: float | None = None
max_ramp_rate: float | None = None
class stoner_measurement.instruments.MagnetState(*values)[source]

Bases: Enum

Operational state of a superconducting magnet power supply.

Attributes:
STANDBY:

The supply is powered but not actively ramping.

RAMPING:

The output is being ramped towards the programmed target.

AT_TARGET:

The output has reached the programmed target field or current.

PERSISTENT:

The magnet is in persistent mode (heater off, leads de-energised).

QUIESCENT:

The supply is in a low-power idle state.

FAULT:

A recoverable fault condition has been detected.

QUENCH:

A quench has been detected; the magnet protection circuit has discharged the stored energy.

UNKNOWN:

The state cannot be determined from the instrument response.

AT_TARGET = 'at_target'
FAULT = 'fault'
PERSISTENT = 'persistent'
QUENCH = 'quench'
QUIESCENT = 'quiescent'
RAMPING = 'ramping'
STANDBY = 'standby'
UNKNOWN = 'unknown'
class stoner_measurement.instruments.MagnetStatus(state: MagnetState, current: float, field: float | None, voltage: float | None, persistent: bool, heater_on: bool | None, at_target: bool, message: str | None = None)[source]

Bases: object

Consolidated status snapshot of a magnet power supply.

Attributes:
state (MagnetState):

Current operational state of the supply.

current (float):

Output current in amps.

field (float | None):

Estimated magnetic field in tesla, or None if the magnet constant is not configured.

voltage (float | None):

Output voltage in volts, or None if not reported by the instrument.

persistent (bool):

True when the supply is operating in persistent mode.

heater_on (bool | None):

True when the persistent switch heater is energised, False when it is off, or None if the state is unknown.

at_target (bool):

True when the output has reached the programmed target.

message (str | None):

Optional human-readable status or error message from the instrument, or None if no message is available.

at_target: bool = <dataclasses._MISSING_TYPE object>
current: float = <dataclasses._MISSING_TYPE object>
field: float | None = <dataclasses._MISSING_TYPE object>
heater_on: bool | None = <dataclasses._MISSING_TYPE object>
message: str | None = None
persistent: bool = <dataclasses._MISSING_TYPE object>
state: MagnetState = <dataclasses._MISSING_TYPE object>
voltage: float | None = <dataclasses._MISSING_TYPE object>
class stoner_measurement.instruments.MeasureFunction(*values)[source]

Bases: Enum

Measurement function selected on an SMU.

Attributes:
VOLT:

Voltage measurement.

CURR:

Current measurement.

RES:

Resistance measurement (4-wire or 2-wire depending on driver configuration).

POW:

Power (derived from simultaneous voltage and current readings).

CURR = 'CURR'
POW = 'POW'
RES = 'RES'
VOLT = 'VOLT'
class stoner_measurement.instruments.Nanovoltmeter(transport: BaseTransport, protocol: BaseProtocol)[source]

Bases: BaseInstrument

Abstract base class for nanovoltmeter instruments.

Provides a uniform interface for high-sensitivity DC voltage measurements. Nanovoltmeters are commonly used in low-resistance measurements, Hall-effect measurements, and thermoelectric studies.

Attributes:
transport (BaseTransport):

Transport layer instance.

protocol (BaseProtocol):

Protocol instance.

Examples:
>>> # Demonstrate interface using a minimal concrete implementation
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.nanovoltmeter import Nanovoltmeter
>>> class _NVM(Nanovoltmeter):
...     def measure_voltage(self): return 1.23e-6
...     def get_range(self): return 0.1
...     def set_range(self, value): pass
...     def get_autorange(self): return True
...     def set_autorange(self, state): pass
...     def get_nplc(self): return 5.0
...     def set_nplc(self, value): pass
>>> nvm = _NVM(NullTransport(), ScpiProtocol())
>>> nvm.measure_voltage()
1.23e-06
abort() None[source]

Abort a running measurement sequence.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

clear_buffer() None[source]

Clear the instrument reading buffer.

Raises:
NotImplementedError:

If buffer operations are not supported by the instrument.

abstractmethod get_autorange() bool[source]

Return True if autorange is currently enabled.

Returns:
(bool):

True when the instrument selects the range automatically.

Raises:
ConnectionError:

If the transport is not open.

get_buffer_count() int[source]

Return the number of readings currently stored in the buffer.

Raises:
NotImplementedError:

If buffer operations are not supported by the instrument.

abstractmethod get_capabilities() NanovoltmeterCapabilities[source]

Return static capability metadata.

Returns:
(NanovoltmeterCapabilities):

Capability descriptor.

get_filter_count() int[source]

Return the configured filter averaging count.

Raises:
NotImplementedError:

If filtering is not supported by the instrument.

get_filter_enabled() bool[source]

Return whether filtering is enabled.

Raises:
NotImplementedError:

If filtering is not supported by the instrument.

abstractmethod get_measure_function() NanovoltmeterFunction[source]

Return the active measurement function.

Returns:
(NanovoltmeterFunction):

Active measurement function.

abstractmethod get_nplc() float[source]

Return the integration time in power-line cycles.

Returns:
(float):

Integration time in power-line cycles.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod get_range() float[source]

Return the current voltage measurement range in volts.

Returns:
(float):

Active measurement range in volts. 0.0 typically indicates autorange is active.

Raises:
ConnectionError:

If the transport is not open.

get_trigger_count() int[source]

Return the configured trigger count.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

get_trigger_source() NanovoltmeterTriggerSource[source]

Return the trigger source selection.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

initiate() None[source]

Arm the trigger system and begin measurements.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

abstractmethod measure_voltage() float[source]

Trigger a voltage measurement and return the result in volts.

Returns:
(float):

Measured voltage in volts.

Raises:
ConnectionError:

If the transport is not open.

read_buffer(count: int | None = None) tuple[float, ...][source]

Read values from the instrument buffer.

Keyword Parameters:
count (int | None):

Optional number of points to read from the start of the buffer. If None, read all available points.

Returns:
(tuple[float, …]):

Parsed buffer values.

Raises:
NotImplementedError:

If buffer operations are not supported by the instrument.

abstractmethod set_autorange(state: bool) None[source]

Enable or disable automatic range selection.

Args:
state (bool):

True to enable autorange, False to use the manually set range.

Raises:
ConnectionError:

If the transport is not open.

set_filter_count(count: int) None[source]

Set the filter averaging count.

Raises:
NotImplementedError:

If filtering is not supported by the instrument.

set_filter_enabled(state: bool) None[source]

Enable or disable measurement filtering.

Raises:
NotImplementedError:

If filtering is not supported by the instrument.

abstractmethod set_measure_function(function: NanovoltmeterFunction) None[source]

Set the active measurement function.

Args:
function (NanovoltmeterFunction):

Function to select.

abstractmethod set_nplc(value: float) None[source]

Set the integration time in power-line cycles.

Args:
value (float):

Integration time in power-line cycles. Longer integration times improve resolution but reduce throughput.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If value is outside the valid range for this instrument.

abstractmethod set_range(value: float) None[source]

Set the voltage measurement range.

Args:
value (float):

Measurement range in volts. Pass 0.0 to enable autorange on instruments that conflate the two settings.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If value is not a valid range for this instrument.

set_trigger_count(count: int) None[source]

Set the trigger count.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

set_trigger_source(source: NanovoltmeterTriggerSource) None[source]

Set the trigger source selection.

Raises:
NotImplementedError:

If trigger configuration is not supported by the instrument.

class stoner_measurement.instruments.NanovoltmeterCapabilities(has_function_selection: bool = False, has_filter: bool = False, has_trigger: bool = False, has_buffer: bool = False, supported_functions: tuple[NanovoltmeterFunction, ...] = (NanovoltmeterFunction.VOLT,))[source]

Bases: object

Static capability descriptor for a nanovoltmeter driver.

has_buffer: bool = False
has_filter: bool = False
has_function_selection: bool = False
has_trigger: bool = False
supported_functions: tuple[NanovoltmeterFunction, ...] = (NanovoltmeterFunction.VOLT,)
class stoner_measurement.instruments.NanovoltmeterFunction(*values)[source]

Bases: Enum

Measurement functions for nanovoltmeters.

TEMP = 'TEMP'
VOLT = 'VOLT'
class stoner_measurement.instruments.NanovoltmeterTriggerSource(*values)[source]

Bases: Enum

Trigger-source selection for nanovoltmeters.

BUS = 'BUS'
EXT = 'EXT'
IMM = 'IMM'
MAN = 'MAN'
TIM = 'TIM'
class stoner_measurement.instruments.PIDParameters(p: float, i: float, d: float)[source]

Bases: object

PID gain parameters for one control loop.

Attributes:
p (float):

Proportional gain (dimensionless; instrument-specific range).

i (float):

Integral gain, sometimes labelled reset or Ti (units instrument-specific; commonly seconds or repeats/minute).

d (float):

Derivative gain, sometimes labelled rate or Td (units instrument-specific; commonly seconds).

d: float = <dataclasses._MISSING_TYPE object>
i: float = <dataclasses._MISSING_TYPE object>
p: float = <dataclasses._MISSING_TYPE object>
class stoner_measurement.instruments.PulsedSweepConfiguration(width: float, off_time: float, low_level: float = 0.0)[source]

Bases: object

Pulse parameters for a pulsed current sweep.

When used alongside CurrentSweepConfiguration, each point in the sweep becomes a pulse: the output ramps to the programmed current for width seconds, then falls to low_level for off_time seconds before the next point.

Attributes:
width (float):

Pulse-on duration in seconds.

off_time (float):

Duration at low_level between pulses, in seconds.

low_level (float):

Output current during the off phase, in amps. Defaults to 0.0.

low_level: float = 0.0
off_time: float = <dataclasses._MISSING_TYPE object>
width: float = <dataclasses._MISSING_TYPE object>
class stoner_measurement.instruments.RampState(*values)[source]

Bases: Enum

Whether a control loop’s setpoint is currently being ramped.

Attributes:
IDLE:

No active ramp; setpoint is at the programmed value.

RAMPING:

Setpoint is being ramped towards the target at the programmed rate.

IDLE = 'idle'
RAMPING = 'ramping'
class stoner_measurement.instruments.SensorStatus(*values)[source]

Bases: Enum

Validity and range status of a temperature sensor reading.

Attributes:
OK:

Reading is within the calibrated range and appears valid.

INVALID:

Reading cannot be trusted (e.g. sensor disconnected or failed).

OVERRANGE:

Measured signal exceeds the upper calibration limit.

UNDERRANGE:

Measured signal is below the lower calibration limit.

FAULT:

Hardware fault detected on the sensor channel.

FAULT = 'fault'
INVALID = 'invalid'
OK = 'ok'
OVERRANGE = 'overrange'
UNDERRANGE = 'underrange'
class stoner_measurement.instruments.SourceMeter(transport: BaseTransport, protocol: BaseProtocol)[source]

Bases: BaseInstrument

Abstract base class for source-measure unit (SMU) instruments.

A source-meter can source voltage or current while simultaneously measuring the complementary quantity, making it suitable for current-voltage (I–V) characterisation.

Subclasses must implement the core abstract methods. Optional capability methods raise NotImplementedError by default; drivers override only those methods that their hardware supports. Callers should consult get_capabilities() to determine which optional features are available before invoking them.

Attributes:
transport (BaseTransport):

Transport layer instance.

protocol (BaseProtocol):

Protocol instance.

Examples:
>>> # Demonstrate interface using a minimal concrete implementation
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.source_meter import (
...     SourceMeter, SourceMode, SourceMeterCapabilities,
... )
>>> class _SM(SourceMeter):
...     def get_source_mode(self): return SourceMode.VOLT
...     def set_source_mode(self, mode): pass
...     def get_source_level(self): return 1.0
...     def set_source_level(self, value): pass
...     def get_compliance(self): return 0.1
...     def set_compliance(self, value): pass
...     def get_nplc(self): return 1.0
...     def set_nplc(self, value): pass
...     def measure_voltage(self): return 1.0
...     def measure_current(self): return 0.001
...     def output_enabled(self): return False
...     def enable_output(self, state): pass
...     def get_capabilities(self): return SourceMeterCapabilities()
>>> sm = _SM(NullTransport(), ScpiProtocol())
>>> sm.get_source_mode()
<SourceMode.VOLT: 'VOLT'>
>>> sm.get_capabilities().has_sweep
False
abort() None[source]

Abort trigger execution.

Raises:
NotImplementedError:

If the driver does not support trigger abort. Check SourceMeterCapabilities.has_trigger_model before calling.

clear_buffer() None[source]

Clear the instrument reading buffer.

Raises:
NotImplementedError:

If the driver does not support reading buffer control. Check SourceMeterCapabilities.has_buffer before calling.

configure_custom_sweep(values: tuple[float, ...], *, delay: float = 0.0) None[source]

Configure a custom point-by-point source sweep.

Args:
values (tuple[float, …]):

Explicit source values to program.

Keyword Parameters:
delay (float):

Source settling delay between points in seconds.

Raises:
NotImplementedError:

If the driver does not support source sweep configuration. Check SourceMeterCapabilities.has_sweep before calling.

configure_linear_sweep(start: float, stop: float, points: int, *, delay: float = 0.0) None[source]

Configure a linear source sweep.

Args:
start (float):

Sweep start value in source units.

stop (float):

Sweep stop value in source units.

points (int):

Number of points in the sweep.

Keyword Parameters:
delay (float):

Source settling delay between sweep points in seconds.

Raises:
NotImplementedError:

If the driver does not support source sweep configuration. Check SourceMeterCapabilities.has_sweep before calling.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.source_meter import (
...     SourceMeter, SourceMode, SourceMeterCapabilities,
... )
>>> class _SM(SourceMeter):
...     def get_source_mode(self): return SourceMode.VOLT
...     def set_source_mode(self, mode): pass
...     def get_source_level(self): return 0.0
...     def set_source_level(self, value): pass
...     def get_compliance(self): return 0.0
...     def set_compliance(self, value): pass
...     def get_nplc(self): return 1.0
...     def set_nplc(self, value): pass
...     def measure_voltage(self): return 0.0
...     def measure_current(self): return 0.0
...     def output_enabled(self): return False
...     def enable_output(self, state): pass
...     def get_capabilities(self): return SourceMeterCapabilities()
>>> _SM(NullTransport(), ScpiProtocol()).configure_linear_sweep(0.0, 1.0, 11, delay=0.01)
Traceback (most recent call last):
...
NotImplementedError: _SM does not support source sweep configuration. ...
configure_log_sweep(start: float, stop: float, points: int, *, delay: float = 0.0) None[source]

Configure a logarithmic source sweep.

Args:
start (float):

Sweep start value in source units.

stop (float):

Sweep stop value in source units.

points (int):

Number of points in the sweep.

Keyword Parameters:
delay (float):

Source settling delay between sweep points in seconds.

Raises:
NotImplementedError:

If the driver does not support source sweep configuration. Check SourceMeterCapabilities.has_sweep before calling.

configure_source_sweep(config: SourceSweepConfiguration) None[source]

Configure a source sweep.

Args:
config (SourceSweepConfiguration):

Source sweep configuration.

Raises:
NotImplementedError:

If the driver does not support source sweep configuration. Check SourceMeterCapabilities.has_sweep before calling.

configure_trigger_model(config: TriggerModelConfiguration) None[source]

Configure trigger and arm behaviour.

Args:
config (TriggerModelConfiguration):

Trigger and arm model configuration.

Raises:
NotImplementedError:

If the driver does not support trigger model configuration. Check SourceMeterCapabilities.has_trigger_model before calling.

abstractmethod enable_output(state: bool) None[source]

Enable or disable the source output.

Args:
state (bool):

True to enable the output, False to disable it.

Raises:
ConnectionError:

If the transport is not open.

get_buffer_size() int[source]

Return reading buffer capacity.

Returns:
(int):

Number of readings that the buffer can store.

Raises:
NotImplementedError:

If the driver does not support reading buffer control. Check SourceMeterCapabilities.has_buffer before calling.

abstractmethod get_capabilities() SourceMeterCapabilities[source]

Return the static capability descriptor for this SMU driver.

Returns:
(SourceMeterCapabilities):

Descriptor advertising which optional feature groups are supported by this driver.

Examples:
>>> caps = sm.get_capabilities()
>>> caps.has_sweep
False
abstractmethod get_compliance() float[source]

Return the compliance limit (A in voltage mode, V in current mode).

Returns:
(float):

Compliance value in amps or volts.

Raises:
ConnectionError:

If the transport is not open.

get_measure_functions() tuple[MeasureFunction, ...][source]

Return enabled measurement functions.

Returns:
(tuple[MeasureFunction, …]):

Enabled measurement functions.

Raises:
NotImplementedError:

If the driver does not support function selection. Check SourceMeterCapabilities.has_function_selection before calling.

abstractmethod get_nplc() float[source]

Return the integration time in power-line cycles (NPLC).

Returns:
(float):

Integration time in power-line cycles.

Raises:
ConnectionError:

If the transport is not open.

get_source_delay() float[source]

Return source delay in seconds.

Returns:
(float):

Source delay in seconds.

Raises:
NotImplementedError:

If the driver does not support source delay control. Check SourceMeterCapabilities.has_source_delay before calling.

abstractmethod get_source_level() float[source]

Return the programmed source level (V or A).

Returns:
(float):

Source amplitude in volts or amps depending on get_source_mode().

Raises:
ConnectionError:

If the transport is not open.

abstractmethod get_source_mode() SourceMode[source]

Return the current source mode.

Returns:
(SourceMode):

VOLT if the instrument is sourcing voltage, CURR if it is sourcing current.

Raises:
ConnectionError:

If the transport is not open.

initiate() None[source]

Arm and initiate acquisition.

Raises:
NotImplementedError:

If the driver does not support trigger initiation. Check SourceMeterCapabilities.has_trigger_model before calling.

abstractmethod measure_current() float[source]

Trigger a current measurement and return the result in amps.

Returns:
(float):

Measured current in amps.

Raises:
ConnectionError:

If the transport is not open.

measure_power() float[source]

Return power calculated from measured voltage and current.

Returns:
(float):

Electrical power in watts.

measure_resistance() float[source]

Return resistance calculated from measured voltage and current.

Returns:
(float):

Resistance in ohms.

Raises:
ZeroDivisionError:

If the measured current magnitude is below 1e-12 A.

Notes:

Currents with absolute magnitude smaller than _MIN_CURRENT_FOR_RESISTANCE_CALCULATION are treated as zero to avoid numerically unstable resistance values.

abstractmethod measure_voltage() float[source]

Trigger a voltage measurement and return the result in volts.

Returns:
(float):

Measured voltage in volts.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod output_enabled() bool[source]

Return True if the source output is currently enabled.

Returns:
(bool):

True when the output is on, False when off.

Raises:
ConnectionError:

If the transport is not open.

read_buffer(count: int | None = None) tuple[float, ...][source]

Return readings from the instrument buffer.

Args:
count (int | None):

Optional count of readings to request.

Returns:
(tuple[float, …]):

Flat tuple of numeric readings from the instrument buffer.

Raises:
NotImplementedError:

If the driver does not support reading buffer control. Check SourceMeterCapabilities.has_buffer before calling.

set_buffer_size(size: int) None[source]

Set reading buffer capacity.

Args:
size (int):

Number of readings the instrument should retain.

Raises:
NotImplementedError:

If the driver does not support reading buffer control. Check SourceMeterCapabilities.has_buffer before calling.

abstractmethod set_compliance(value: float) None[source]

Set the compliance limit.

Args:
value (float):

Compliance in amps (voltage mode) or volts (current mode).

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If value exceeds the instrument’s maximum compliance.

set_measure_functions(functions: tuple[MeasureFunction, ...]) None[source]

Enable one or more measurement functions.

Args:
functions (tuple[MeasureFunction, …]):

Sequence of measurement functions to enable.

Raises:
NotImplementedError:

If the driver does not support function selection. Check SourceMeterCapabilities.has_function_selection before calling.

abstractmethod set_nplc(value: float) None[source]

Set the integration time in power-line cycles.

Args:
value (float):

Integration time in power-line cycles. Typical range is 0.01–10.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If value is outside the valid range.

set_source_delay(delay: float) None[source]

Set source delay in seconds before each measurement trigger.

Args:
delay (float):

Source delay in seconds.

Raises:
NotImplementedError:

If the driver does not support source delay control. Check SourceMeterCapabilities.has_source_delay before calling.

abstractmethod set_source_level(value: float) None[source]

Set the source output level (V or A).

Args:
value (float):

Source amplitude in volts (voltage mode) or amps (current mode).

Raises:
ConnectionError:

If the transport is not open.

abstractmethod set_source_mode(mode: SourceMode) None[source]

Set the source mode.

Args:
mode (SourceMode):

VOLT for voltage source or CURR for current source.

Raises:
ConnectionError:

If the transport is not open.

class stoner_measurement.instruments.SourceMeterCapabilities(has_function_selection: bool = False, has_sweep: bool = False, has_source_delay: bool = False, has_trigger_model: bool = False, has_buffer: bool = False)[source]

Bases: object

Static capability descriptor for a source-meter driver.

Attributes:
has_function_selection (bool):

True if the driver supports selecting measurement functions via get_measure_functions() and set_measure_functions().

has_sweep (bool):

True if the driver supports built-in source sweeps via configure_source_sweep().

has_source_delay (bool):

True if the driver supports a programmable source delay via get_source_delay() and set_source_delay().

has_trigger_model (bool):

True if the driver supports trigger and arm model configuration via configure_trigger_model(), initiate(), and abort().

has_buffer (bool):

True if the driver supports a reading buffer via set_buffer_size(), get_buffer_size(), clear_buffer(), and read_buffer().

has_buffer: bool = False
has_function_selection: bool = False
has_source_delay: bool = False
has_sweep: bool = False
has_trigger_model: bool = False
class stoner_measurement.instruments.SourceMode(*values)[source]

Bases: Enum

Source output mode of an SMU.

Attributes:
VOLT:

Voltage source mode; the instrument outputs a programmed voltage and measures current (or resistance).

CURR:

Current source mode; the instrument outputs a programmed current and measures voltage (or resistance).

CURR = 'CURR'
VOLT = 'VOLT'
class stoner_measurement.instruments.SourceSweepConfiguration(start: float = 0.0, stop: float = 0.0, points: int = 0, spacing: SweepSpacing = SweepSpacing.LIN, values: tuple[float, ...] | None = None, delay: float = 0.0)[source]

Bases: object

Configuration for a source sweep.

Attributes:
start (float):

Sweep start value in source units. Ignored for list sweeps.

stop (float):

Sweep stop value in source units. Ignored for list sweeps.

points (int):

Number of sweep points. For list sweeps this is inferred from len(values) by convenience wrappers but must be supplied explicitly when constructing this dataclass directly.

spacing (SweepSpacing):

Point-spacing mode. Defaults to LIN.

values (tuple[float, …] | None):

Explicit source values for LIST sweeps. Ignored for linear and logarithmic sweeps.

delay (float):

Source settling delay between sweep points in seconds. A value of 0.0 disables the added delay.

Notes:

Default start, stop, and points values are placeholders. Callers should provide values appropriate for the selected spacing mode. For list sweeps, use values and set points to len(values). The default delay of 0.0 seconds disables added settling time.

delay: float = 0.0
points: int = 0
spacing: SweepSpacing = 'LIN'
start: float = 0.0
stop: float = 0.0
values: tuple[float, ...] | None = None
class stoner_measurement.instruments.SweepSpacing(*values)[source]

Bases: Enum

Point-spacing mode for a built-in source sweep.

Attributes:
LIN:

Linearly spaced sweep points from start to stop.

LOG:

Logarithmically spaced sweep points from start to stop.

LIST:

Arbitrary point list supplied in SourceSweepConfiguration.values.

LIN = 'LIN'
LIST = 'LIST'
LOG = 'LOG'
class stoner_measurement.instruments.TemperatureController(transport: BaseTransport, protocol: BaseProtocol)[source]

Bases: BaseInstrument

Abstract base class for temperature controller instruments.

Provides a uniform interface for reading temperatures, managing setpoints, and controlling heater output across a range of cryogenic and laboratory temperature controllers including the Lakeshore 335, 336, 340 and 350, the Oxford Instruments ITC503, and the Oxford Instruments Mercury iTC. All temperature values are in Kelvin unless otherwise stated.

Subclasses must implement the seventeen core abstract methods. Optional capability methods raise NotImplementedError by default; drivers override only those methods that their hardware supports. Callers should consult get_capabilities() to determine which optional features are available before invoking them.

Attributes:
transport (BaseTransport):

Transport layer instance.

protocol (BaseProtocol):

Protocol instance.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import LakeshoreProtocol
>>> from stoner_measurement.instruments.temperature_controller import (
...     TemperatureController, ControlMode, SensorStatus,
...     ControllerCapabilities, PIDParameters,
... )
>>> class _TC(TemperatureController):
...     def get_temperature(self, channel): return 300.0
...     def get_sensor_status(self, channel): return SensorStatus.OK
...     def get_input_channel(self, loop): return "A"
...     def set_input_channel(self, loop, channel): pass
...     def get_setpoint(self, loop): return 300.0
...     def set_setpoint(self, loop, value): pass
...     def get_loop_mode(self, loop): return ControlMode.CLOSED_LOOP
...     def set_loop_mode(self, loop, mode): pass
...     def get_heater_output(self, loop): return 50.0
...     def set_heater_range(self, loop, range_): pass
...     def get_pid(self, loop): return PIDParameters(50.0, 1.0, 0.0)
...     def set_pid(self, loop, p, i, d): pass
...     def get_ramp_rate(self, loop): return 5.0
...     def set_ramp_rate(self, loop, rate): pass
...     def get_ramp_enabled(self, loop): return False
...     def set_ramp_enabled(self, loop, enabled): pass
...     def get_capabilities(self):
...         return ControllerCapabilities(
...             num_inputs=2, num_loops=1,
...             input_channels=("A", "B"), loop_numbers=(1,),
...         )
>>> tc = _TC(NullTransport(), LakeshoreProtocol())
>>> tc.get_temperature("A")
300.0
>>> tc.get_capabilities().num_loops
1
get_alarm_limits(channel: str) tuple[float, float][source]

Return the (low, high) alarm limits for sensor channel in Kelvin.

Args:
channel (str):

Sensor channel identifier.

Returns:
(tuple[float, float]):

(low_limit, high_limit) in Kelvin.

Raises:
NotImplementedError:

If the driver does not support alarm limits. Check ControllerCapabilities.has_alarm before calling.

ConnectionError:

If the transport is not open.

get_alarm_state(channel: str) AlarmState[source]

Return the current alarm state for sensor channel.

Args:
channel (str):

Sensor channel identifier.

Returns:
(AlarmState):

Current alarm condition.

Raises:
NotImplementedError:

If the driver does not support alarm monitoring. Check ControllerCapabilities.has_alarm before calling.

ConnectionError:

If the transport is not open.

get_autotune_status(loop: int) str[source]

Return the current autotune status for loop as a string.

The returned string is instrument-specific; typical values include "idle", "running", "complete", and "failed".

Args:
loop (int):

Control loop number (1-based).

Returns:
(str):

Human-readable autotune status.

Raises:
NotImplementedError:

If the driver does not support autotuning. Check ControllerCapabilities.has_autotune before calling.

ConnectionError:

If the transport is not open.

abstractmethod get_capabilities() ControllerCapabilities[source]

Return the static capability descriptor for this controller driver.

Returns:
(ControllerCapabilities):

Descriptor advertising the number of inputs, loops, and which optional feature groups are supported.

Examples:
>>> caps = tc.get_capabilities()
>>> caps.num_loops
1
>>> caps.has_ramp
True
get_controller_status() TemperatureStatus[source]

Return a full snapshot of all channels and loops.

Uses get_capabilities() to discover channels and loops, then assembles a TemperatureStatus from get_temperature_reading() and get_loop_status().

Returns:
(TemperatureStatus):

Snapshot of all sensor readings and control-loop states.

Raises:
ConnectionError:

If the transport is not open.

get_excitation(channel: str) float[source]

Return the excitation level applied to sensor channel.

The excitation value and its units (voltage, current, or power) are instrument-specific. Common examples: Lakeshore resistive-excitation in μV, or Oxford Mercury excitation in μA.

Args:
channel (str):

Sensor channel identifier.

Returns:
(float):

Excitation level in instrument-native units.

Raises:
NotImplementedError:

If the driver does not support excitation configuration. Check ControllerCapabilities.has_sensor_excitation before calling.

ConnectionError:

If the transport is not open.

get_filter(channel: str) dict[str, object][source]

Return the digital filter settings for sensor channel.

The returned dictionary contains at minimum the keys "enabled" (bool), "points" (int, number of readings averaged), and "window" (float, percentage window for filter reset).

Args:
channel (str):

Sensor channel identifier.

Returns:
(dict[str, object]):

Filter settings keyed by name.

Raises:
NotImplementedError:

If the driver does not support filter configuration. Check ControllerCapabilities.has_sensor_excitation before calling.

ConnectionError:

If the transport is not open.

get_gas_flow() float[source]

Return the cryogen gas-flow valve position as a percentage.

Returns:
(float):

Gas-flow valve opening as a percentage (0–100 %).

Raises:
NotImplementedError:

If the driver does not support cryogen flow control. Check ControllerCapabilities.has_cryogen_control before calling.

ConnectionError:

If the transport is not open.

abstractmethod get_heater_output(loop: int) float[source]

Return the heater output percentage for control loop.

Args:
loop (int):

Control loop number (1-based).

Returns:
(float):

Heater output as a percentage (0–100 %).

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_heater_output(1)
50.0
abstractmethod get_input_channel(loop: int) str[source]

Return the sensor channel currently assigned to control loop.

Args:
loop (int):

Control loop number (1-based).

Returns:
(str):

Channel identifier of the sensor driving this loop.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_input_channel(1)
'A'
abstractmethod get_loop_mode(loop: int) ControlMode[source]

Return the active control mode for loop.

Args:
loop (int):

Control loop number (1-based).

Returns:
(ControlMode):

Current control mode of the loop.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_loop_mode(1)
<ControlMode.CLOSED_LOOP: 'closed_loop'>
get_loop_status(loop: int) LoopStatus[source]

Return a comprehensive status snapshot for control loop.

Calls all relevant core abstract methods and assembles a LoopStatus dataclass. Drivers that can retrieve all loop data in a single instrument query should override this method.

Args:
loop (int):

Control loop number (1-based).

Returns:
(LoopStatus):

Complete current state of the control loop.

Raises:
ConnectionError:

If the transport is not open.

get_needle_valve() float[source]

Return the needle-valve (gas-flow restrictor) position as a percentage.

Returns:
(float):

Needle-valve position as a percentage (0 = fully closed, 100 = fully open).

Raises:
NotImplementedError:

If the driver does not support needle-valve control. Check ControllerCapabilities.has_cryogen_control before calling.

ConnectionError:

If the transport is not open.

get_num_zones(loop: int) int[source]

Return the number of configured zones for loop.

Args:
loop (int):

Control loop number (1-based).

Returns:
(int):

Number of zones configured in the zone table.

Raises:
NotImplementedError:

If the driver does not support zone control. Check ControllerCapabilities.has_zone before calling.

ConnectionError:

If the transport is not open.

abstractmethod get_pid(loop: int) PIDParameters[source]

Return the PID parameters for control loop.

Args:
loop (int):

Control loop number (1-based).

Returns:
(PIDParameters):

Current proportional, integral and derivative gain values.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_pid(1)
PIDParameters(p=50.0, i=1.0, d=0.0)
abstractmethod get_ramp_enabled(loop: int) bool[source]

Return True if setpoint ramping is enabled for loop.

Args:
loop (int):

Control loop number (1-based).

Returns:
(bool):

True when the ramp function is active.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_ramp_enabled(1)
False
abstractmethod get_ramp_rate(loop: int) float[source]

Return the setpoint ramp rate for loop in Kelvin per minute.

Args:
loop (int):

Control loop number (1-based).

Returns:
(float):

Ramp rate in Kelvin per minute.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_ramp_rate(1)
5.0
get_ramp_state(loop: int) RampState[source]

Return the current ramp state for loop.

The default implementation infers the state from get_ramp_enabled(); override in subclasses that can query the actual hardware ramp state directly.

Args:
loop (int):

Control loop number (1-based).

Returns:
(RampState):

RAMPING when the ramp function is active, IDLE otherwise.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_ramp_state(1)
<RampState.IDLE: 'idle'>
get_sensor_curve(channel: str) int[source]

Return the calibration curve number assigned to sensor channel.

Args:
channel (str):

Sensor channel identifier.

Returns:
(int):

Curve number (instrument-specific; 0 typically means no curve assigned).

Raises:
NotImplementedError:

If the driver does not support user calibration curves. Check ControllerCapabilities.has_user_curves before calling.

ConnectionError:

If the transport is not open.

abstractmethod get_sensor_status(channel: str) SensorStatus[source]

Return the validity/range status of the sensor on channel.

Args:
channel (str):

Sensor channel identifier (instrument-specific, e.g. "A").

Returns:
(SensorStatus):

Current validity and range status of the sensor reading.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_sensor_status("A")
<SensorStatus.OK: 'ok'>
abstractmethod get_setpoint(loop: int) float[source]

Return the current setpoint for control loop in Kelvin.

Args:
loop (int):

Control loop number (1-based).

Returns:
(float):

Setpoint temperature in Kelvin.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_setpoint(1)
300.0
abstractmethod get_temperature(channel: str) float[source]

Return the current temperature for channel in Kelvin.

Args:
channel (str):

Sensor channel identifier (instrument-specific, e.g. "A").

Returns:
(float):

Current temperature in Kelvin.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> tc.get_temperature("A")
300.0
get_temperature_reading(channel: str) TemperatureReading[source]

Return a combined temperature value and sensor status for channel.

Calls get_temperature() and get_sensor_status() and packages the results into a TemperatureReading. Drivers that can fetch both in a single query should override this method.

Args:
channel (str):

Sensor channel identifier.

Returns:
(TemperatureReading):

Current temperature in Kelvin with validity status.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> reading = tc.get_temperature_reading("A")
>>> reading.value
300.0
>>> reading.status
<SensorStatus.OK: 'ok'>
get_zone(loop: int, zone_index: int) ZoneEntry[source]

Return the parameters of zone entry zone_index for loop.

Args:
loop (int):

Control loop number (1-based).

zone_index (int):

Zone entry index (1-based).

Returns:
(ZoneEntry):

Zone parameters including PID gains, ramp rate, heater range and heater output power for this table entry.

Raises:
NotImplementedError:

If the driver does not support zone control. Check ControllerCapabilities.has_zone before calling.

ConnectionError:

If the transport is not open.

ramp_to_setpoint(loop: int, target: float, *, rate: float | None = None) None[source]

Set a new target setpoint for loop, optionally configuring the ramp rate.

This convenience method performs the typical “ramp to a new temperature” sequence:

  1. If rate is given, set the ramp rate via set_ramp_rate().

  2. Enable ramping via set_ramp_enabled() (only when the driver advertises has_ramp).

  3. Write the new setpoint via set_setpoint().

Callers that need to wait for the temperature to arrive should follow this call with wait_for_setpoint().

Args:
loop (int):

Control loop number (1-based).

target (float):

Desired setpoint in Kelvin.

Keyword Parameters:
rate (float | None):

Ramp rate in Kelvin per minute. When provided, the rate is written to the instrument before the setpoint is updated. When None the currently programmed ramp rate is unchanged. Ignored if the driver does not advertise has_ramp.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If target or rate are outside the instrument’s valid range.

Examples:
>>> tc.ramp_to_setpoint(1, 150.0, rate=5.0)
set_alarm_enabled(channel: str, enabled: bool) None[source]

Enable or disable the alarm for sensor channel.

Args:
channel (str):

Sensor channel identifier.

enabled (bool):

True to activate alarm checking, False to disable it.

Raises:
NotImplementedError:

If the driver does not support alarms. Check ControllerCapabilities.has_alarm before calling.

ConnectionError:

If the transport is not open.

set_alarm_limits(channel: str, low: float, high: float) None[source]

Set the low and high alarm thresholds for sensor channel.

Args:
channel (str):

Sensor channel identifier.

low (float):

Low-alarm temperature threshold in Kelvin.

high (float):

High-alarm temperature threshold in Kelvin.

Raises:
NotImplementedError:

If the driver does not support alarm limits. Check ControllerCapabilities.has_alarm before calling.

ConnectionError:

If the transport is not open.

set_excitation(channel: str, value: float) None[source]

Set the excitation level for sensor channel.

Args:
channel (str):

Sensor channel identifier.

value (float):

Excitation level in instrument-native units.

Raises:
NotImplementedError:

If the driver does not support excitation configuration. Check ControllerCapabilities.has_sensor_excitation before calling.

ConnectionError:

If the transport is not open.

set_filter(channel: str, *, enabled: bool, points: int, window: float) None[source]

Configure the digital filter for sensor channel.

Args:
channel (str):

Sensor channel identifier.

Keyword Parameters:
enabled (bool):

True to activate the filter, False to disable it.

points (int):

Number of readings to average (1 effectively disables averaging).

window (float):

Percentage deviation window that triggers a filter reset (0 disables window filtering).

Raises:
NotImplementedError:

If the driver does not support filter configuration. Check ControllerCapabilities.has_sensor_excitation before calling.

ConnectionError:

If the transport is not open.

set_gas_flow(percent: float) None[source]

Set the cryogen gas-flow valve to percent open.

Args:
percent (float):

Desired valve opening as a percentage (0–100 %).

Raises:
NotImplementedError:

If the driver does not support cryogen flow control. Check ControllerCapabilities.has_cryogen_control before calling.

ConnectionError:

If the transport is not open.

abstractmethod set_heater_range(loop: int, range_: int) None[source]

Set the heater range for control loop.

The meaning of range_ is instrument-specific. A value of 0 conventionally means “heater off”.

Args:
loop (int):

Control loop number (1-based).

range_ (int):

Heater range index (instrument-specific).

Raises:
ConnectionError:

If the transport is not open.

abstractmethod set_input_channel(loop: int, channel: str) None[source]

Assign sensor channel as the input for control loop.

Args:
loop (int):

Control loop number (1-based).

channel (str):

Sensor channel identifier to assign.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If channel is not a valid sensor input for this instrument.

abstractmethod set_loop_mode(loop: int, mode: ControlMode) None[source]

Set the control mode for loop.

Args:
loop (int):

Control loop number (1-based).

mode (ControlMode):

Desired control mode.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If mode is not supported by this loop on this instrument.

set_needle_valve(position: float) None[source]

Set the needle-valve position.

Args:
position (float):

Desired position as a percentage (0 = fully closed, 100 = fully open).

Raises:
NotImplementedError:

If the driver does not support needle-valve control. Check ControllerCapabilities.has_cryogen_control before calling.

ConnectionError:

If the transport is not open.

abstractmethod set_pid(loop: int, p: float, i: float, d: float) None[source]

Set the PID parameters for control loop.

Args:
loop (int):

Control loop number (1-based).

p (float):

Proportional gain.

i (float):

Integral gain (reset).

d (float):

Derivative gain (rate).

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If any gain value is outside the instrument’s valid range.

abstractmethod set_ramp_enabled(loop: int, enabled: bool) None[source]

Enable or disable setpoint ramping for loop.

Args:
loop (int):

Control loop number (1-based).

enabled (bool):

True to activate the ramp function, False to disable it.

Raises:
ConnectionError:

If the transport is not open.

abstractmethod set_ramp_rate(loop: int, rate: float) None[source]

Set the setpoint ramp rate for loop.

Args:
loop (int):

Control loop number (1-based).

rate (float):

Ramp rate in Kelvin per minute. A value of 0 typically disables ramping or sets an unlimited rate (instrument-specific).

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If rate is negative or exceeds the instrument’s maximum.

set_sensor_curve(channel: str, curve_num: int) None[source]

Assign calibration curve curve_num to sensor channel.

Args:
channel (str):

Sensor channel identifier.

curve_num (int):

Curve number to assign (instrument-specific).

Raises:
NotImplementedError:

If the driver does not support user calibration curves. Check ControllerCapabilities.has_user_curves before calling.

ConnectionError:

If the transport is not open.

abstractmethod set_setpoint(loop: int, value: float) None[source]

Set the target temperature for control loop.

Args:
loop (int):

Control loop number (1-based).

value (float):

Desired setpoint in Kelvin.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If value is outside the instrument’s valid range.

set_zone(loop: int, zone_index: int, entry: ZoneEntry) None[source]

Write a zone table entry for loop.

Args:
loop (int):

Control loop number (1-based).

zone_index (int):

Zone entry index (1-based).

entry (ZoneEntry):

Zone parameters to write, including PID gains, ramp rate, heater range and heater output power.

Raises:
NotImplementedError:

If the driver does not support zone control. Check ControllerCapabilities.has_zone before calling.

ConnectionError:

If the transport is not open.

start_autotune(loop: int, mode: int = 0) None[source]

Start the PID autotuning sequence for loop.

Args:
loop (int):

Control loop number (1-based).

Keyword Parameters:
mode (int):

Autotune mode (instrument-specific; commonly 0 = P only, 1 = P+I, 2 = P+I+D). Defaults to 0.

Raises:
NotImplementedError:

If the driver does not support autotuning. Check ControllerCapabilities.has_autotune before calling.

ConnectionError:

If the transport is not open.

wait_for_setpoint(loop: int, channel: str, *, tolerance: float = 0.5, timeout: float = 300.0, poll_period: float = 1.0) None[source]

Block until the temperature on channel is within tolerance of the setpoint.

Polls get_temperature() and get_setpoint() at intervals of poll_period seconds until the absolute deviation falls within tolerance, or until timeout seconds have elapsed.

Args:
loop (int):

Control loop whose setpoint is used as the target.

channel (str):

Sensor channel to monitor.

Keyword Parameters:
tolerance (float):

Acceptable deviation from the setpoint in Kelvin. Defaults to 0.5.

timeout (float):

Maximum time to wait in seconds. Defaults to 300.0.

poll_period (float):

Interval between temperature polls in seconds. Defaults to 1.0.

Raises:
TimeoutError:

If the temperature does not reach the setpoint within timeout seconds.

ConnectionError:

If the transport is not open.

class stoner_measurement.instruments.TemperatureReading(value: float, status: SensorStatus, units: str = 'K')[source]

Bases: object

A snapshot of a single sensor channel reading.

Attributes:
value (float):

Numeric sensor reading expressed in the units given by the units field. Drivers should convert to Kelvin and set units="K" wherever possible; raw resistance or voltage readings should set the appropriate unit string instead.

status (SensorStatus):

Validity / range status of the reading.

units (str):

Native units reported by the instrument (e.g. "K", "C", "V", "Ohm"). Defaults to "K".

status: SensorStatus = <dataclasses._MISSING_TYPE object>
units: str = 'K'
value: float = <dataclasses._MISSING_TYPE object>
class stoner_measurement.instruments.TemperatureStatus(temperatures: dict[str, TemperatureReading], loops: dict[int, LoopStatus], error_state: str | None = None)[source]

Bases: object

A full controller status snapshot.

Attributes:
temperatures (dict[str, TemperatureReading]):

Mapping of channel identifier to sensor reading.

loops (dict[int, LoopStatus]):

Mapping of loop number to loop status snapshot.

error_state (str | None):

Human-readable error/fault description, or None if the controller reports no fault.

error_state: str | None = None
loops: dict[int, LoopStatus] = <dataclasses._MISSING_TYPE object>
temperatures: dict[str, TemperatureReading] = <dataclasses._MISSING_TYPE object>
class stoner_measurement.instruments.TriggerModelConfiguration(trigger_source: TriggerSource = TriggerSource.IMM, trigger_count: int = 1, trigger_delay: float = 0.0, arm_source: TriggerSource = TriggerSource.IMM, arm_count: int = 1)[source]

Bases: object

Configuration for simple trigger and arm models.

Attributes:
trigger_source (TriggerSource):

Source that advances the trigger layer. Defaults to IMM.

trigger_count (int):

Number of times the trigger layer executes per arm. Defaults to 1.

trigger_delay (float):

Delay in seconds inserted before each measurement trigger. Defaults to 0.0.

arm_source (TriggerSource):

Source that advances the arm layer. Defaults to IMM.

arm_count (int):

Number of times the arm layer executes. Defaults to 1.

arm_count: int = 1
arm_source: TriggerSource = 'IMM'
trigger_count: int = 1
trigger_delay: float = 0.0
trigger_source: TriggerSource = 'IMM'
class stoner_measurement.instruments.TriggerSource(*values)[source]

Bases: Enum

Trigger or arm source for a trigger-model configuration.

Attributes:
IMM:

Immediate (internal) — the layer completes as soon as it is entered.

BUS:

IEEE-488 / GPIB bus trigger (*TRG or GET).

EXT:

External hardware trigger input.

TLIN:

Trigger link (LAN or digital I/O trigger bus, instrument-specific).

TIM:

Internal timer-based trigger.

BUS = 'BUS'
EXT = 'EXT'
IMM = 'IMM'
TIM = 'TIM'
TLIN = 'TLIN'
class stoner_measurement.instruments.ZoneEntry(upper_bound: float, p: float, i: float, d: float, ramp_rate: float, heater_range: int, heater_output: float)[source]

Bases: object

One entry in a temperature controller’s zone / table control table.

Zone control divides the operating temperature range into consecutive segments. Each segment has its own PID gains, setpoint ramp rate, heater range, and manual heater output power. When the active setpoint crosses upper_bound the controller automatically loads the next zone’s parameters.

Attributes:
upper_bound (float):

Upper setpoint boundary for this zone in Kelvin. When the setpoint is at or below this value the zone’s PID and heater settings are applied.

p (float):

Proportional gain for this zone.

i (float):

Integral gain (reset) for this zone.

d (float):

Derivative gain (rate) for this zone.

ramp_rate (float):

Setpoint ramp rate for this zone in Kelvin per minute. A value of 0 means the setpoint changes immediately (no ramp).

heater_range (int):

Heater range index for this zone (instrument-specific; 0 conventionally means heater off).

heater_output (float):

Manual heater output power percentage for this zone (0–100 %). Used when the loop is in open-loop mode or as the initial value when entering the zone.

d: float = <dataclasses._MISSING_TYPE object>
heater_output: float = <dataclasses._MISSING_TYPE object>
heater_range: int = <dataclasses._MISSING_TYPE object>
i: float = <dataclasses._MISSING_TYPE object>
p: float = <dataclasses._MISSING_TYPE object>
ramp_rate: float = <dataclasses._MISSING_TYPE object>
upper_bound: float = <dataclasses._MISSING_TYPE object>

Keithley instrument drivers.

Contains concrete drivers for Keithley source-measure units (Keithley2400, Keithley2410, Keithley2450), precision current sources (Keithley6221), digital multimeters (Keithley2000, Keithley2700), nanovoltmeters (Keithley2182A, Keithley182), and electrometers/picoammeters (Keithley6845, Keithley6514, Keithley6517).

class stoner_measurement.instruments.keithley.Keithley182(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: Keithley2182A

Driver for the Keithley 182 nanovoltmeter.

class stoner_measurement.instruments.keithley.Keithley2000(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: DigitalMultimeter

Driver for the Keithley 2000 digital multimeter.

Attributes:
transport (BaseTransport):

Transport layer (serial, GPIB, or Ethernet).

protocol (BaseProtocol):

Protocol instance (defaults to ScpiProtocol).

abort() None[source]

Abort a running measurement sequence and return to idle.

clear_buffer() None[source]

Clear all readings from the instrument trace buffer.

get_autorange() bool[source]

Return True if autorange is enabled.

Returns:
(bool):

True when autorange is active.

get_buffer_count() int[source]

Return the number of readings currently stored in the buffer.

Returns:
(int):

Number of readings in the trace buffer.

get_capabilities() DmmCapabilities[source]

Return static capability metadata for the Keithley 2000.

Returns:
(DmmCapabilities):

Capability descriptor.

get_filter_count() int[source]

Return the averaging filter count.

Returns:
(int):

Configured number of readings averaged per sample.

get_filter_enabled() bool[source]

Return True if digital averaging filter is enabled.

Returns:
(bool):

True when the averaging filter is active.

get_measure_function() DmmFunction[source]

Return the active measurement function.

Returns:
(DmmFunction):

Active measurement function.

get_nplc() float[source]

Return the integration time in power-line cycles.

Returns:
(float):

Integration time in power-line cycles.

get_range() float[source]

Return the active measurement range in units of the active function.

Returns:
(float):

Active measurement range.

get_trigger_count() int[source]

Return the configured trigger count.

Returns:
(int):

Number of triggers configured.

get_trigger_source() DmmTriggerSource[source]

Return the trigger source selection.

Returns:
(DmmTriggerSource):

Active trigger source.

initiate() None[source]

Arm the trigger system and begin a measurement sequence.

measure() float[source]

Trigger a measurement and return its value.

Returns:
(float):

Measured scalar value in units of the active function.

read_buffer(count: int | None = None) tuple[float, ...][source]

Read values from the instrument trace buffer.

Keyword Parameters:
count (int | None):

Number of points to read from the start of the buffer. If None, read all available points.

Returns:
(tuple[float, …]):

Parsed buffer readings.

Raises:
ValueError:

If count is not positive.

set_autorange(state: bool) None[source]

Enable or disable autorange.

Args:
state (bool):

True to enable autorange.

set_filter_count(count: int) None[source]

Set the averaging filter count.

Args:
count (int):

Number of readings to average. Must be positive.

Raises:
ValueError:

If count is not positive.

set_filter_enabled(state: bool) None[source]

Enable or disable the digital averaging filter.

Args:
state (bool):

True to enable filtering.

set_measure_function(function: DmmFunction) None[source]

Set the active measurement function.

Args:
function (DmmFunction):

Function to select.

set_nplc(value: float) None[source]

Set the integration time in power-line cycles.

Args:
value (float):

Integration time in power-line cycles. Must be positive.

Raises:
ValueError:

If value is not positive.

set_range(value: float) None[source]

Set the measurement range.

Args:
value (float):

Range value in units of the active function. Must be positive.

Raises:
ValueError:

If value is not positive.

set_trigger_count(count: int) None[source]

Set the trigger count.

Args:
count (int):

Number of triggers. Must be positive.

Raises:
ValueError:

If count is not positive.

set_trigger_source(source: DmmTriggerSource) None[source]

Set the trigger source.

Args:
source (DmmTriggerSource):

Trigger source to select.

class stoner_measurement.instruments.keithley.Keithley2182A(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: Nanovoltmeter

Driver for the Keithley 2182A nanovoltmeter.

Attributes:
transport (BaseTransport):

Transport layer (serial, GPIB, or Ethernet).

protocol (BaseProtocol):

Protocol instance (defaults to ScpiProtocol).

abort() None[source]

Abort a running measurement sequence and return to idle.

clear_buffer() None[source]

Clear all readings from the instrument trace buffer.

get_autorange() bool[source]

Return True if autorange is enabled.

Returns:
(bool):

True when autorange is active.

get_buffer_count() int[source]

Return the number of readings currently stored in the buffer.

Returns:
(int):

Number of readings in the trace buffer.

get_capabilities() NanovoltmeterCapabilities[source]

Return static capability metadata for the Keithley 2182A.

Returns:
(NanovoltmeterCapabilities):

Capability descriptor.

get_filter_count() int[source]

Return the digital filter averaging count.

Returns:
(int):

Number of readings averaged per sample.

get_filter_enabled() bool[source]

Return True if the digital filter is enabled.

Returns:
(bool):

True when the digital filter is active.

get_measure_function() NanovoltmeterFunction[source]

Return the active measurement function.

Returns:
(NanovoltmeterFunction):

Active measurement function.

get_nplc() float[source]

Return the integration time in power-line cycles.

Returns:
(float):

Integration time in power-line cycles.

get_range() float[source]

Return the active voltage measurement range in volts.

Returns:
(float):

Active measurement range in volts.

get_trigger_count() int[source]

Return the configured trigger count.

Returns:
(int):

Number of triggers configured.

get_trigger_source() NanovoltmeterTriggerSource[source]

Return the trigger source selection.

Returns:
(NanovoltmeterTriggerSource):

Active trigger source.

initiate() None[source]

Arm the trigger system and begin a measurement sequence.

measure_voltage() float[source]

Trigger a voltage measurement and return the result in volts.

Returns:
(float):

Measured voltage in volts.

read_buffer(count: int | None = None) tuple[float, ...][source]

Read values from the instrument trace buffer.

Keyword Parameters:
count (int | None):

Number of points to read from the start of the buffer. If None, read all available points.

Returns:
(tuple[float, …]):

Parsed buffer readings.

Raises:
ValueError:

If count is not positive.

set_autorange(state: bool) None[source]

Enable or disable autorange.

Args:
state (bool):

True to enable autorange.

set_filter_count(count: int) None[source]

Set the digital filter averaging count.

Args:
count (int):

Number of readings to average. Must be positive.

Raises:
ValueError:

If count is not positive.

set_filter_enabled(state: bool) None[source]

Enable or disable the digital filter.

Args:
state (bool):

True to enable the filter.

set_measure_function(function: NanovoltmeterFunction) None[source]

Set the active measurement function.

Args:
function (NanovoltmeterFunction):

Function to select.

set_nplc(value: float) None[source]

Set the integration time in power-line cycles.

Args:
value (float):

Integration time in power-line cycles. Must be positive.

Raises:
ValueError:

If value is not positive.

set_range(value: float) None[source]

Set the voltage measurement range in volts.

Args:
value (float):

Measurement range in volts. Must be positive.

Raises:
ValueError:

If value is not positive.

set_trigger_count(count: int) None[source]

Set the trigger count.

Args:
count (int):

Number of triggers. Must be positive.

Raises:
ValueError:

If count is not positive.

set_trigger_source(source: NanovoltmeterTriggerSource) None[source]

Set the trigger source.

Args:
source (NanovoltmeterTriggerSource):

Trigger source to select.

class stoner_measurement.instruments.keithley.Keithley2400(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: SourceMeter

Driver for the Keithley 2400 Series SourceMeter.

Implements SourceMeter using SCPI commands specific to the Keithley 2400 instrument family. A ScpiProtocol instance is used by default; the transport must be supplied by the caller.

Attributes:
transport (BaseTransport):

Transport layer (serial, GPIB, or Ethernet).

protocol (BaseProtocol):

Protocol instance (defaults to ScpiProtocol).

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.protocol import ScpiProtocol
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[
...     b"KEITHLEY INSTRUMENTS INC.,MODEL 2400,1234567,C32 Mar  4 2011\n",
... ])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.identify()
'KEITHLEY INSTRUMENTS INC.,MODEL 2400,1234567,C32 Mar  4 2011'
>>> k.disconnect()
abort() None[source]

Abort trigger execution.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.abort()
>>> t.write_log[-1]
b':ABOR\n'
>>> k.disconnect()
clear_buffer() None[source]

Clear the reading buffer.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.clear_buffer()
>>> t.write_log[-1]
b':TRAC:CLE\n'
>>> k.disconnect()
configure_source_sweep(config: SourceSweepConfiguration) None[source]

Configure linear, logarithmic, or list source sweeps.

Args:
config (SourceSweepConfiguration):

Sweep configuration payload.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If the delay, point count, or list values are invalid.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> from stoner_measurement.instruments.source_meter import (
...     SourceSweepConfiguration, SweepSpacing,
... )
>>> t = NullTransport(responses=[b"VOLT\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.configure_source_sweep(
...     SourceSweepConfiguration(
...         start=0.0, stop=1.0, points=5,
...         spacing=SweepSpacing.LIN, delay=0.01,
...     )
... )
>>> t.write_log[-1]
b':SOUR:DEL 0.01\n'
>>> k.disconnect()
configure_trigger_model(config: TriggerModelConfiguration) None[source]

Configure trigger and arm model settings.

Args:
config (TriggerModelConfiguration):

Trigger and arm model configuration.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If trigger or arm counts are not positive, or trigger delay is negative.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> from stoner_measurement.instruments.source_meter import (
...     TriggerModelConfiguration, TriggerSource,
... )
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.configure_trigger_model(
...     TriggerModelConfiguration(
...         trigger_source=TriggerSource.BUS,
...         trigger_count=2,
...         trigger_delay=0.1,
...     )
... )
>>> t.write_log[0]
b':TRIG:SOUR BUS\n'
>>> k.disconnect()
enable_output(state: bool) None[source]

Enable or disable the source output.

Args:
state (bool):

True to turn the output on, False to turn it off.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.enable_output(True)
>>> t.write_log[-1]
b':OUTP:STAT 1\n'
>>> k.enable_output(False)
>>> t.write_log[-1]
b':OUTP:STAT 0\n'
>>> k.disconnect()
get_buffer_size() int[source]

Return reading buffer capacity.

Returns:
(int):

Number of readings that can be stored.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"250\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.get_buffer_size()
250
>>> k.disconnect()
Notes:

Some instruments may return the buffer size in exponential format. The response is therefore parsed as float before conversion to int.

get_capabilities() SourceMeterCapabilities[source]

Return the capability descriptor for the Keithley 2400 driver.

Returns:
(SourceMeterCapabilities):

Descriptor indicating that this driver supports measurement function selection, source sweeps, source delay, trigger/arm model configuration, and reading buffer control.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> caps = Keithley2400(transport=NullTransport()).get_capabilities()
>>> caps.has_sweep
True
>>> caps.has_buffer
True
get_compliance() float[source]

Return the compliance limit in amps (voltage mode) or volts (current mode).

Returns:
(float):

Compliance value.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"1.000000E-01\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.get_compliance()
0.1
>>> k.disconnect()
get_measure_functions() tuple[MeasureFunction, ...][source]

Return enabled measurement functions.

get_nplc() float[source]

Return the integration time in power-line cycles.

Returns:
(float):

Integration time in power-line cycles.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"1.000000E+00\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.get_nplc()
1.0
>>> k.disconnect()
get_source_delay() float[source]

Return source delay before each measurement trigger.

Returns:
(float):

Source delay in seconds.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"1.000000E-02\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.get_source_delay()
0.01
>>> k.disconnect()
get_source_level() float[source]

Return the programmed source level in volts or amps.

Returns:
(float):

Source amplitude in the unit corresponding to the active source mode.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"1.000000E+00\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.get_source_level()
1.0
>>> k.disconnect()
get_source_mode() SourceMode[source]

Return the active source mode.

Returns:
(SourceMode):

VOLT if the instrument is sourcing voltage, CURR if it is sourcing current.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> from stoner_measurement.instruments.source_meter import SourceMode
>>> t = NullTransport(responses=[b"VOLT\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.get_source_mode()
<SourceMode.VOLT: 'VOLT'>
>>> k.disconnect()
initiate() None[source]

Arm and start the trigger model.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.initiate()
>>> t.write_log[-1]
b':INIT\n'
>>> k.disconnect()
measure_current() float[source]

Trigger a current measurement and return the result in amps.

Configures the sense function to current, triggers a single reading, and returns the parsed floating-point result.

Returns:
(float):

Measured current in amps.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"+1.000000E-03\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.measure_current()
0.001
>>> k.disconnect()
measure_power() float[source]

Trigger simultaneous voltage/current measurements and return power in watts.

Returns:
(float):

Measured power in watts.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If the instrument response does not include both voltage and current.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"+2.000000E+00,+5.000000E-01\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.measure_power()
1.0
>>> k.disconnect()
measure_resistance() float[source]

Trigger a resistance measurement and return the result in ohms.

Returns:
(float):

Measured resistance in ohms.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"+1.200000E+03\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.measure_resistance()
1200.0
>>> k.disconnect()
measure_voltage() float[source]

Trigger a voltage measurement and return the result in volts.

Configures the sense function to voltage, triggers a single reading, and returns the parsed floating-point result.

Returns:
(float):

Measured voltage in volts.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"+1.234567E+00\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.measure_voltage()
1.234567
>>> k.disconnect()
output_enabled() bool[source]

Return True if the source output is currently enabled.

Returns:
(bool):

True when the output is active.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"0\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.output_enabled()
False
>>> k.disconnect()
read_buffer(count: int | None = None) tuple[float, ...][source]

Read buffered readings from the internal trace buffer.

Args:
count (int | None):

Optional number of readings to return from the start of the buffer.

Returns:
(tuple[float, …]):

Flat tuple of numeric readings.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If count is provided and is not positive.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport(responses=[b"1.0,2.0\n"])
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.read_buffer()
(1.0, 2.0)
>>> k.disconnect()
set_buffer_size(size: int) None[source]

Set reading buffer capacity.

Args:
size (int):

Number of readings for the buffer to retain.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If size is not positive.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.set_buffer_size(100)
>>> t.write_log[-1]
b':TRAC:POIN 100\n'
>>> k.disconnect()
set_compliance(value: float) None[source]

Set the compliance limit.

Args:
value (float):

Compliance in amps (voltage mode) or volts (current mode).

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.set_compliance(0.05)
>>> t.write_log[-1]
b':SENS:CURR:PROT 0.05\n'
>>> k.disconnect()
set_measure_functions(functions: tuple[MeasureFunction, ...]) None[source]

Enable one or more measurement functions.

set_nplc(value: float) None[source]

Set the integration time in power-line cycles.

Args:
value (float):

Integration time (0.01–10 PLC).

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If value is outside [0.01, 10].

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.set_nplc(5.0)
>>> t.write_log  # two writes: voltage sense then current sense
[b':SENS:VOLT:NPLC 5.0\n', b':SENS:CURR:NPLC 5.0\n']
>>> k.disconnect()
set_source_delay(delay: float) None[source]

Set source delay before each measurement trigger.

Args:
delay (float):

Source delay in seconds.

Raises:
ConnectionError:

If the transport is not open.

ValueError:

If delay is negative.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.set_source_delay(0.01)
>>> t.write_log[-1]
b':SOUR:DEL 0.01\n'
>>> k.disconnect()
set_source_level(value: float) None[source]

Set the source output level.

Args:
value (float):

Output amplitude in volts (voltage mode) or amps (current mode).

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.set_source_level(1.5)
>>> t.write_log[-1]
b':SOUR:AMPL 1.5\n'
>>> k.disconnect()
set_source_mode(mode: SourceMode) None[source]

Set the source mode.

Args:
mode (SourceMode):

VOLT for voltage source or CURR for current source.

Raises:
ConnectionError:

If the transport is not open.

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> from stoner_measurement.instruments.keithley import Keithley2400
>>> from stoner_measurement.instruments.source_meter import SourceMode
>>> t = NullTransport()
>>> k = Keithley2400(transport=t)
>>> k.connect()
>>> k.set_source_mode(SourceMode.VOLT)
>>> t.write_log[-1]
b':SOUR:FUNC:MODE VOLT\n'
>>> k.disconnect()
class stoner_measurement.instruments.keithley.Keithley2410(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: Keithley2400

Driver for the Keithley 2410 SourceMeter.

This model uses command-level compatibility with the Keithley 2400 implementation for core source/measure, sweep, trigger, and buffer features.

class stoner_measurement.instruments.keithley.Keithley2450(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: Keithley2400

Driver for the Keithley 2450 SourceMeter.

This model supports a SCPI command subset that is compatible with the Keithley 2400 implementation exposed by this class hierarchy.

class stoner_measurement.instruments.keithley.Keithley2700(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: Keithley2000

Driver for the Keithley 2700 multimeter/data-acquisition system.

class stoner_measurement.instruments.keithley.Keithley6221(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: CurrentSource

Driver for the Keithley 6221 precision AC/DC current source.

Provides DC source-level and compliance control plus AC waveform controls (waveform shape, frequency, and offset current) using SCPI commands. Built-in staircase sweeps (linear, logarithmic, custom list) and pulsed sweeps are also supported.

configure_pulsed_sweep(sweep: CurrentSweepConfiguration, pulse: PulsedSweepConfiguration) None[source]

Configure a pulsed current sweep.

Calls configure_sweep() to programme the sweep points, then enables pulsed mode and sets pulse timing parameters.

Args:
sweep (CurrentSweepConfiguration):

Sweep point configuration.

pulse (PulsedSweepConfiguration):

Pulse timing and baseline current. pulse.width and pulse.off_time must both be positive.

Raises:
ValueError:

If pulse.width or pulse.off_time is not positive.

configure_sweep(config: CurrentSweepConfiguration) None[source]

Configure a built-in current sweep.

Args:
config (CurrentSweepConfiguration):

Sweep configuration. For LIST spacing, config.values must be non-empty.

Raises:
ValueError:

If config.spacing is LIST and config.values is empty or None.

enable_output(state: bool) None[source]

Enable or disable source output.

get_capabilities() CurrentSourceCapabilities[source]

Return static capabilities for Keithley 6221.

get_compliance_voltage() float[source]

Return compliance voltage in volts.

get_frequency() float[source]

Return AC waveform frequency in Hz.

get_offset_current() float[source]

Return AC waveform DC offset current in amps.

get_source_level() float[source]

Return programmed source current in amps.

get_waveform() CurrentWaveform[source]

Return waveform mode.

output_enabled() bool[source]

Return True if source output is enabled.

set_compliance_voltage(value: float) None[source]

Set compliance voltage in volts.

set_frequency(value: float) None[source]

Set AC waveform frequency in Hz.

set_offset_current(value: float) None[source]

Set AC waveform DC offset current in amps.

set_source_level(value: float) None[source]

Set source current in amps.

set_waveform(waveform: CurrentWaveform) None[source]

Set waveform mode.

sweep_abort() None[source]

Abort a running or armed sweep.

sweep_start() None[source]

Arm the configured sweep, making it ready for triggering.

class stoner_measurement.instruments.keithley.Keithley6514(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: _KeithleyElectrometerBase

Driver for the Keithley 6514 electrometer.

class stoner_measurement.instruments.keithley.Keithley6517(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: _KeithleyElectrometerBase

Driver for the Keithley 6517 electrometer.

class stoner_measurement.instruments.keithley.Keithley6845(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: _KeithleyElectrometerBase

Driver for the Keithley 6845 picoammeter/electrometer command set.

Lakeshore instrument drivers.

class stoner_measurement.instruments.lakeshore.Lakeshore335(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: _LakeshoreTemperatureControllerBase

Concrete driver for the Lakeshore 335 temperature controller.

class stoner_measurement.instruments.lakeshore.Lakeshore336(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: _LakeshoreTemperatureControllerBase

Concrete driver for the Lakeshore 336 temperature controller.

class stoner_measurement.instruments.lakeshore.Lakeshore340(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: _LakeshoreTemperatureControllerBase

Concrete driver for the Lakeshore 340 temperature controller.

class stoner_measurement.instruments.lakeshore.Lakeshore525(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: MagnetController, MagnetSupply

Driver for a Lakeshore 525 superconducting magnet power supply.

The driver composes BaseInstrument communication behaviour with the MagnetSupply interface for magnet supplies.

Attributes:
transport (BaseTransport):

Transport used for instrument I/O.

protocol (BaseProtocol):

Protocol used to format and parse instrument messages.

Examples:
>>> from stoner_measurement.instruments.lakeshore import Lakeshore525
>>> from stoner_measurement.instruments.transport import NullTransport
>>> t = NullTransport(responses=[b"LAKESHORE,MODEL525,SN001,1.0\r\n"])
>>> mps = Lakeshore525(transport=t)
>>> mps.connect()
>>> mps.identify()
'LAKESHORE,MODEL525,SN001,1.0'
>>> mps.disconnect()
abort_ramp() None[source]

Abort ramping immediately.

property current: float

Return output current in amps.

Returns:
(float):

Measured magnet supply current in amps.

property field: float

Return output field in tesla.

Returns:
(float):

Measured magnetic field in tesla.

get_firmware_version() str[source]

Return the firmware version from the identification string.

Returns:
(str):

Firmware version token when available, otherwise an empty string.

get_model() str[source]

Return the model name from the identification string.

Returns:
(str):

Instrument model token when available, otherwise an empty string.

property heater: bool

Return persistent switch heater state.

Returns:
(bool):

True when the heater is enabled.

heater_off() None[source]

Disable the persistent switch heater.

heater_on() None[source]

Enable the persistent switch heater.

property limits: MagnetLimits

Return configured software limits for this driver instance.

Returns:
(MagnetLimits):

Cached configured current/field/ramp limits.

property magnet_constant: float

Return the magnet constant in tesla per amp.

Returns:
(float):

Magnet constant in tesla per amp.

pause_ramp() None[source]

Pause an active ramp.

ramp_to_current(current: float, *, wait: bool = False) None[source]

Program current target and ramp.

Args:
current (float):

Target current in amps.

Keyword Parameters:
wait (bool):

When True, block until ramp completes.

ramp_to_field(field: float, *, wait: bool = False) None[source]

Program field target and ramp.

Args:
field (float):

Target field in tesla.

Keyword Parameters:
wait (bool):

When True, block until ramp completes.

ramp_to_target() None[source]

Start ramping to the current programmed target.

set_limits(limits: MagnetLimits) None[source]

Set software limits used by higher-level sequence logic.

Args:
limits (MagnetLimits):

Limit configuration to cache for runtime checks.

set_magnet_constant(tesla_per_amp: float) None[source]

Set the software magnet constant used for conversion.

Args:
tesla_per_amp (float):

Magnet constant in tesla per amp.

set_ramp_rate_current(rate: float) None[source]

Set the current ramp rate in amps per minute.

Args:
rate (float):

Ramp rate in amps per minute.

set_ramp_rate_field(rate: float) None[source]

Set the field ramp rate in tesla per minute.

Args:
rate (float):

Ramp rate in tesla per minute.

set_target_current(current: float) None[source]

Set the target current in amps.

Args:
current (float):

Target current in amps.

set_target_field(field: float) None[source]

Set the target field in tesla.

Args:
field (float):

Target magnetic field in tesla.

property status: MagnetStatus

Return consolidated magnet status.

Returns:
(MagnetStatus):

Snapshot of controller state and key readings.

property voltage: float

Return output voltage in volts.

Returns:
(float):

Measured output voltage in volts.

class stoner_measurement.instruments.lakeshore.LakeshoreM81CurrentSource(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: CurrentSource

Driver for the Lakeshore M81 balanced current-source outputs.

Models the M81 as a differential current-source backed by two matched output channels. The public source-level API uses channel 1 amplitude and mirrors channel 2 with opposite polarity for balanced drive. Built-in sweeps (linear, logarithmic, custom list) are also supported; the sweep is mirrored across both channels so the differential current follows the programmed profile.

configure_sweep(config: CurrentSweepConfiguration) None[source]

Configure a built-in balanced current sweep.

Channel 1 is programmed with the positive sense of the sweep; channel 2 is programmed with the mirrored (negated) profile so that the differential output follows the intended sweep.

Args:
config (CurrentSweepConfiguration):

Sweep configuration. For LIST spacing, config.values must be non-empty.

Raises:
ValueError:

If config.spacing is LIST and config.values is empty or None.

enable_output(state: bool) None[source]

Enable or disable both balanced outputs.

get_capabilities() CurrentSourceCapabilities[source]

Return static capabilities for Lakeshore M81 current source.

get_channel_level(channel: int) float[source]

Return programmed current for a specific channel in amps.

get_compliance_voltage() float[source]

Return compliance voltage in volts.

get_frequency() float[source]

Return waveform frequency in Hz.

get_offset_current() float[source]

Return waveform offset current in amps.

get_source_level() float[source]

Return differential source current amplitude in amps.

get_waveform() CurrentWaveform[source]

Return waveform mode.

output_enabled() bool[source]

Return True if both balanced outputs are enabled.

set_channel_level(channel: int, value: float) None[source]

Set programmed current for a specific channel in amps.

set_compliance_voltage(value: float) None[source]

Set compliance voltage in volts for both channels.

set_frequency(value: float) None[source]

Set waveform frequency in Hz for both channels.

set_offset_current(value: float) None[source]

Set waveform offset current in amps for both channels.

set_source_level(value: float) None[source]

Set differential source current amplitude in amps.

set_waveform(waveform: CurrentWaveform) None[source]

Set waveform mode for both channels.

sweep_abort() None[source]

Abort a running or armed balanced sweep.

sweep_start() None[source]

Arm the configured balanced sweep, making it ready for triggering.

class stoner_measurement.instruments.lakeshore.LakeshoreM81LockIn(transport: BaseTransport, protocol: BaseProtocol | None = None, *, sense_slot: int = 1, source_slot: int | None = None)[source]

Bases: LockInAmplifier

Driver for the Lakeshore M81 SSM voltage-measurement module in LIA mode.

The Lakeshore M81 Synchronous Source Measure (SSM) system performs lock-in detection via its VM (voltage-measurement) module. Each module occupies a numbered slot on the mainframe. When operating in LIA mode the VM module demodulates against a reference that may come from either an internal AC source module occupying another slot (source_slot) or an external TTL/sine reference routed to the rear panel.

Notes:

Setting the reference frequency programmatically requires an AC source module connected to the same mainframe. Pass its slot number as source_slot at construction time. Without source_slot the reference frequency can still be read (the instrument reports the detected frequency) but cannot be set via this driver.

Keyword Parameters:
sense_slot (int):

Mainframe slot of the VM measurement module. Defaults to 1.

source_slot (int | None):

Mainframe slot of the AC source module used as the internal reference. When None (default) the internal reference frequency cannot be set by this driver.

Attributes:
transport (BaseTransport):

Transport layer (GPIB, Ethernet, or USB).

protocol (BaseProtocol):

Protocol instance (defaults to ScpiProtocol).

Examples:
>>> from stoner_measurement.instruments.transport import NullTransport
>>> lia = LakeshoreM81LockIn(NullTransport(), sense_slot=1, source_slot=2)
>>> lia.set_time_constant(100e-3)
>>> x, y = lia.measure_xy()
auto_phase() None[source]

Execute the M81 auto-phase routine for the sense slot.

get_capabilities() LockInAmplifierCapabilities[source]

Return static capability metadata for the M81 LIA driver.

Returns:
(LockInAmplifierCapabilities):

Capability descriptor. Reference-frequency control is only available when a source_slot was supplied at construction.

get_filter_slope() int[source]

Return the output low-pass filter roll-off slope in dB/octave.

Returns:
(int):

Filter slope in dB/octave, one of (6, 12, 18, 24).

Raises:
ValueError:

If the instrument returns an unrecognised filter-poles value.

get_harmonic() int[source]

Return the detection harmonic.

Returns:
(int):

Active detection harmonic (1 to _MAX_HARMONIC).

get_input_coupling() LockInInputCoupling[source]

Return the input coupling mode.

Returns:
(LockInInputCoupling):

AC or DC.

get_reference_frequency() float[source]

Return the reference frequency in hertz.

When a source slot is configured the frequency is queried from the AC source module; otherwise the locally cached value is returned.

Returns:
(float):

Reference frequency in hertz.

Notes:

The cached value is initialised from _DEFAULT_INTERNAL_REFERENCE_FREQUENCY_HZ and updated by set_reference_frequency() and source-slot queries made via this driver.

get_reference_phase() float[source]

Return the reference phase offset in degrees.

Returns:
(float):

Reference phase in degrees.

get_reference_source() LockInReferenceSource[source]

Return the active reference source.

Returns:
(LockInReferenceSource):

INTERNAL or EXTERNAL.

get_sensitivity() float[source]

Return the active input range (sensitivity) in volts.

Returns:
(float):

Sensitivity in volts as reported by the instrument.

get_time_constant() float[source]

Return the output filter time constant in seconds.

Returns:
(float):

Time constant in seconds.

measure_rt() tuple[float, float][source]

Measure and return the magnitude (R) and phase (theta) outputs.

Returns:
(tuple[float, float]):

(r, theta) where r is in volts and theta is in degrees.

measure_xy() tuple[float, float][source]

Measure and return the in-phase (X) and quadrature (Y) outputs.

Returns:
(tuple[float, float]):

(x, y) values in volts.

set_filter_slope(slope: int) None[source]

Set the output low-pass filter roll-off slope.

Args:
slope (int):

Slope in dB/octave. Must be one of (6, 12, 18, 24).

Raises:
ValueError:

If slope is not a valid filter-slope value.

set_harmonic(harmonic: int) None[source]

Set the detection harmonic.

Args:
harmonic (int):

Harmonic number between 1 and _MAX_HARMONIC.

Raises:
ValueError:

If harmonic is outside the permitted range.

set_input_coupling(coupling: LockInInputCoupling) None[source]

Set the input coupling mode.

Args:
coupling (LockInInputCoupling):

Coupling mode to select.

set_reference_frequency(value: float) None[source]

Set the reference frequency via the M81 AC source module.

Args:
value (float):

Frequency in hertz. Must be positive.

Raises:
NotImplementedError:

If no source_slot was provided at construction time.

ValueError:

If value is not positive.

set_reference_phase(value: float) None[source]

Set the reference phase offset in degrees.

Args:
value (float):

Phase offset in degrees.

set_reference_source(source: LockInReferenceSource) None[source]

Set the reference source.

Args:
source (LockInReferenceSource):

Reference source to select.

set_sensitivity(value: float) None[source]

Set the input range (sensitivity) in volts.

Args:
value (float):

Sensitivity in volts. Must be positive.

Raises:
ValueError:

If value is not positive.

set_time_constant(value: float) None[source]

Set the output filter time constant in seconds.

Args:
value (float):

Time constant in seconds. Must be positive.

Raises:
ValueError:

If value is not positive.

Oxford Instruments drivers.

class stoner_measurement.instruments.oxford.OxfordIPS120(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: MagnetController, MagnetSupply

Driver for an Oxford Instruments IPS120 magnet power supply.

Attributes:
transport (BaseTransport):

Transport used for instrument I/O.

protocol (BaseProtocol):

Protocol used to format and parse instrument messages.

Examples:
>>> from stoner_measurement.instruments.oxford import OxfordIPS120
>>> from stoner_measurement.instruments.transport import NullTransport
>>> t = NullTransport(
...     responses=[b"VIPS120-10 3.07\r"],
... )
>>> mps = OxfordIPS120(transport=t)
>>> mps.connect()
>>> mps.identify()
'IPS120-10 3.07'
>>> mps.disconnect()
abort_ramp() None[source]

Abort ramping immediately.

property current: float

Return output current in amps.

Returns:
(float):

Measured magnet supply current in amps.

property field: float

Return output field in tesla.

Returns:
(float):

Measured magnetic field in tesla.

get_firmware_version() str[source]

Return the firmware version from the identity string.

Returns:
(str):

Firmware token when available, otherwise an empty string.

get_model() str[source]

Return the model name from the identity string.

Returns:
(str):

Instrument model token when available, otherwise an empty string.

property heater: bool

Return persistent switch heater state.

Returns:
(bool):

True when the heater is enabled.

heater_off() None[source]

Disable the persistent switch heater.

heater_on() None[source]

Enable the persistent switch heater.

identify() str[source]

Return the instrument identity string.

Returns:
(str):

Identity payload from the instrument with the leading Oxford command-echo prefix removed by the protocol parser.

property limits: MagnetLimits

Return configured software limits for this driver instance.

Returns:
(MagnetLimits):

Cached configured current/field/ramp limits.

property magnet_constant: float

Return the magnet constant in tesla per amp.

Returns:
(float):

Magnet constant in tesla per amp.

pause_ramp() None[source]

Pause an active ramp.

ramp_to_current(current: float, *, wait: bool = False) None[source]

Program current target and ramp.

Args:
current (float):

Target current in amps.

Keyword Parameters:
wait (bool):

When True, block until ramp completes.

ramp_to_field(field: float, *, wait: bool = False) None[source]

Program field target and ramp.

Args:
field (float):

Target field in tesla.

Keyword Parameters:
wait (bool):

When True, block until ramp completes.

ramp_to_target() None[source]

Start ramping to the current programmed target.

set_limits(limits: MagnetLimits) None[source]

Set software limits used by higher-level sequence logic.

Args:
limits (MagnetLimits):

Limit configuration to cache for runtime checks.

set_magnet_constant(tesla_per_amp: float) None[source]

Set the software magnet constant used for conversion.

Args:
tesla_per_amp (float):

Magnet constant in tesla per amp.

set_ramp_rate_current(rate: float) None[source]

Set the current ramp rate in amps per minute.

Args:
rate (float):

Ramp rate in amps per minute.

set_ramp_rate_field(rate: float) None[source]

Set the field ramp rate in tesla per minute.

Args:
rate (float):

Ramp rate in tesla per minute.

set_target_current(current: float) None[source]

Set the target current in amps.

Args:
current (float):

Target current in amps.

set_target_field(field: float) None[source]

Set the target field in tesla.

Args:
field (float):

Target magnetic field in tesla.

property status: MagnetStatus

Return consolidated magnet status.

Returns:
(MagnetStatus):

Snapshot of controller state and key readings.

property voltage: float

Return output voltage in volts.

Returns:
(float):

Measured output voltage in volts.

class stoner_measurement.instruments.oxford.OxfordITC503(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: _OxfordTemperatureControllerBase

Concrete driver for the Oxford Instruments ITC503 temperature controller.

identify() str[source]

Return identity string.

class stoner_measurement.instruments.oxford.OxfordMercuryTemperatureController(transport: BaseTransport, protocol: BaseProtocol | None = None)[source]

Bases: _OxfordTemperatureControllerBase

Concrete driver for the Oxford Instruments Mercury Temperature Controller.