InstroPSU
InstroPSU is a hardware abstraction layer (HAL) that provides a unified interface for programmable power supplies. The category class defines the vendor-independent API (set_voltage, get_voltage, output_enable, …). A vendor-specific driver (e.g. BK9115, RigolDP800) owns its connection details and translates those calls into vendor commands.
Supported Vendors
- B&K Precision: 9115-series single-channel via SCPI/VISA (
BK9115) - B&K Precision: 9140-series multi-channel via SCPI/VISA (
BK9140) - Rigol: DP-series via SCPI/VISA (
RigolDP800) - Siglent: SPD-series via SCPI/VISA (
SiglentSPD3303) - TDK Lambda: Genesys family via SCPI/VISA (
TDKLambdaGenesys) - Simulated:
SimulatedPSUfor use with the bundled SCPI simulator
Key Concepts
Driver Composition
AInstroPSU is built from a concrete driver:
- The vendor driver (e.g.
BK9115) owns the connection setup and vendor-specific command mapping. InstroPSUowns the category-level workflow: measurements, commands, publishers, the background daemon.
Lifecycle
The typical InstroPSU workflow:- Construct: instantiate the vendor driver and pass it to
InstroPSU. open(): establishes the VISA connection.- Configure and measure: set voltage/current limits, enable outputs, read measurements.
start(): begins a periodic background daemon. (Optional)stop(): ends the background daemon (if started).close(): disconnects from hardware.
Creating a InstroPSU Instance
Parameters
name: A name for this PSU instance. Used as a prefix for channel names when publishing.driver: A concretePSUDriverBaseinstance (e.g.SiglentSPD3303) configured with the connection details for that model.num_channels: Number of output channels on the power supply.publishers: Optional list of publishers to attach.**kwargs: Additional keyword arguments become default tags when using a publisher that supports tags (likeNominalCorePublisher).
Choosing a Driver
Choose the concrete driver that matches the power supply model, then pass the instrument connection settings to that driver. For example, useBK9140 for the B&K 9140-series, or RigolDP800 for Rigol DP-series.
To inspect a VISA instrument’s identity before choosing a driver:
Examples
All measurement methods returnMeasurement objects. This is common amongst all Instrument objects.
Basic Usage
Background Daemon for Continuous Monitoring
start()begins a background daemon, executing a function or list of functions periodically.stop()ends the background daemon.
Default PSU Background DaemonFor each :
- Output voltage (via
get_voltage()) - Output current (via
get_current()) - Output enable status (via
get_output_status())
background_interval property.- To define your own background daemon, call
define_background_daemon(method, *args, **kwargs), which replaces the registered daemon functions. - To add a method to the background daemon stack, call
add_background_daemon_function().
Important Note about PublishersData is published as a direct result of an instrument method being called.For example, when you call
get_voltage(), this not only queries the instrument for the voltage but also causes all attached Publishers to publish the measurement response automatically.Therefore the background daemon, when calling these instrument methods, is publishing data in the background as well!Published channels
Every measurement/command call produces a channel keyed under{name}.{descriptor}, where {name} is the constructor argument and {descriptor} is the row below. Substitute {N} with the actual channel number (1, 2, …). If you need the pre-v1.0 channel names instead, pass legacy_naming=True to the constructor.
| Method | Descriptor | Type |
|---|---|---|
set_voltage(channel=N) | ch{N}.voltage.cmd | command |
get_voltage(channel=N) | ch{N}.voltage | telemetry |
set_current_limit(channel=N) | ch{N}.current.cmd | command |
get_current(channel=N) | ch{N}.current | telemetry |
output_enable(channel=N) | ch{N}.enabled.cmd | command |
get_output_status(channel=N) | ch{N}.enabled | telemetry |
set_overvoltage_protection_level(channel=N) | ch{N}.ovp.cmd | command |
get_overvoltage_protection_level(channel=N) | ch{N}.ovp | telemetry |
set_overvoltage_protection_enabled(channel=N) | ch{N}.ovp.enabled.cmd | command |
get_overvoltage_protection_enabled(channel=N) | ch{N}.ovp.enabled | telemetry |
set_overvoltage_protection_delay(channel=N) | ch{N}.ovp.delay.cmd | command |
get_overvoltage_protection_delay(channel=N) | ch{N}.ovp.delay | telemetry |
set_overcurrent_protection_level(channel=N) | ch{N}.ocp.cmd | command |
get_overcurrent_protection_level(channel=N) | ch{N}.ocp | telemetry |
set_overcurrent_protection_enabled(channel=N) | ch{N}.ocp.enabled.cmd | command |
get_overcurrent_protection_enabled(channel=N) | ch{N}.ocp.enabled | telemetry |
set_remote_sense_enabled(channel=N) | ch{N}.remote_sense.cmd | command |
get_remote_sense_enabled(channel=N) | ch{N}.remote_sense | telemetry |
Method Reference
| Method | Purpose |
|---|---|
InstroPSU(name, driver, num_channels, publishers=None, legacy_naming=False, **kwargs) | Construct a InstroPSU with a vendor driver |
open() | Establish the connection to the PSU |
close() | Disconnect from PSU and close all publishers |
set_voltage(voltage, channel=1) | Set voltage setpoint in volts |
get_voltage(channel=1) | Read measured output voltage in volts |
set_current_limit(current_limit, channel=1) | Set current limit in amperes |
get_current(channel=1) | Read measured output current in amperes |
output_enable(enable, channel=1) | Enable (True) or disable (False) the output |
get_output_status(channel=1) | Query output enable state (returns Measurement with bool value) |
set_overvoltage_protection_level(voltage, channel=1) | Set the overvoltage protection threshold in volts |
get_overvoltage_protection_level(channel=1) | Read the overvoltage protection threshold in volts |
set_overvoltage_protection_enabled(enabled, channel=1) | Enable or disable overvoltage protection |
get_overvoltage_protection_enabled(channel=1) | Query whether overvoltage protection is enabled |
set_overvoltage_protection_delay(delay, channel=1) | Set the overvoltage protection trip delay in seconds |
get_overvoltage_protection_delay(channel=1) | Read the overvoltage protection trip delay in seconds |
set_overcurrent_protection_level(current, channel=1) | Set the overcurrent protection threshold in amperes |
get_overcurrent_protection_level(channel=1) | Read the overcurrent protection threshold in amperes |
set_overcurrent_protection_enabled(enabled, channel=1) | Enable or disable overcurrent protection |
get_overcurrent_protection_enabled(channel=1) | Query whether overcurrent protection is enabled |
set_remote_sense_enabled(enabled, channel=1) | Enable or disable remote sense |
get_remote_sense_enabled(channel=1) | Query whether remote sense is enabled |
start() | Begin background telemetry daemon |
stop() | End background telemetry daemon |
Simulated Power Supply
For development and testing without physical hardware, Nominal Instrumentation includes a simulated SCPI power supply server and a matchingSimulatedPSU. This allows you to test your code and workflows before connecting to real instruments.
Starting the Simulation Server
Run the simulation server from the command line:127.0.0.1:5025 and simulate a basic SCPI-compatible power supply.
The simulation server runs indefinitely until you stop it with
Ctrl+C. Leave it running in a terminal while you develop and test your code.Using the Simulated PSU
Connect to the simulated power supply withSimulatedPSU and a socket-based VISA resource string:
Use Cases
The simulated PSU is useful for:- Development: Test your code without requiring physical hardware
- CI/CD: Run automated tests in continuous integration environments
- Training: Learn the InstroPSU API without risk to equipment
- Prototyping: Design test sequences before hardware arrives
The simulated PSU provides realistic responses but does not model physical behavior like voltage ripple, load regulation, or thermal effects. Always validate your code with real hardware before production use.
Custom Driver Development
This section is for developers implementingInstroPSU support for power supplies that aren’t supported out of the box.
Overview
Driver developers subclassPSUDriverBase and own whatever transport their instrument needs. The caller chooses a concrete driver, and that concrete driver exposes connection parameters that make sense for its protocol:
InstroPSU’s vendor-independent API (set_voltage, get_voltage, output_enable, …) into vendor-specific commands.
Driver Responsibilities
A PSU driver must:- Expose a protocol-native constructor: accept inputs like
visa_resource,host,port,unit_id,interface, ornode_id, depending on the instrument. - Own transport setup: create and store the transport internally. Do not require users to pass a
VisaDriver, socket client, Modbus client, or other transport object. - Own lifecycle: implement
open()andclose()by opening and closing the underlying transport. - Map commands: translate each abstract method into vendor-specific commands.
- Parse responses: convert instrument responses to the expected Python types (
float,bool, etc.).
PSUDriverBase Interface
All PSU drivers subclassPSUDriverBase and implement these abstract methods:
PSUDriverBase are declared with @abc.abstractmethod. Optional protection and sense methods default to NotImplementedError; override the ones the driver implements. See Exceptions for unsupported-feature handling.
Talking to the Instrument
Concrete drivers should hide transport details behind private attributes. For VISA-backed drivers, create aVisaDriver internally and use it for all I/O:
self._visa.write(command): Send a SCPI command (no response expected).self._visa.query(command): Send a SCPI query and receive the response string.
VisaDriver owns the resource lock. Concurrent write / query calls against the same driver are serialized automatically. Use with self._visa.lock(): when a write and its error check need to execute atomically.
See the VisaDriver guide for the full transport reference, covering configuration, terminators, timeouts, serial settings, and the raw-byte I/O path.
For non-VISA instruments, follow the same shape with the protocol client your driver needs. The important part is that the public constructor describes the instrument connection, while the transport object remains an implementation detail.
Implementation Example: B&K Precision Single-Channel Driver
Here’s a representative driver implementation for the B&K Precision 9115-series single-channel supplies:Using a Custom Driver
For drivers that aren’t shipped in the library, constructInstroPSU with your own driver instance. The driver should accept connection settings directly and create its transport internally:
Summary
Driver development requires careful mapping of vendor-specific behavior to the unifiedInstroPSU interface. Focus on:
- Subclassing
PSUDriverBase - Designing a constructor around natural connection parameters for the instrument
- Hiding transport construction inside the driver
- Implementing all abstract methods on
PSUDriverBase - Using the correct vendor protocol or command syntax
- Converting instrument responses to the expected Python types
- Querying and reporting errors from the instrument’s error queue where one exists
- Testing with actual hardware to ensure commands work as expected