InstroELoad
InstroELoad is a hardware abstraction layer (HAL) that provides a unified interface for programmable electronic loads. The category class defines the vendor-independent API (set_mode, set_level, get_voltage, …). A vendor-specific driver (e.g. BK85XXB) owns its connection details and translates those calls into vendor commands.
Supported Vendors
- B&K Precision: 85xx Series via SCPI/VISA (
BK85XXB)
Key Concepts
Driver Composition
AnInstroELoad is built from a concrete driver:
BK85XXBowns the connection setup and vendor-specific command mapping.InstroELoadowns the category-level workflow: measurements, commands, publishers, the background daemon.
Lifecycle
The typical InstroELoad workflow:- Construct: instantiate the vendor driver and pass it to
InstroELoad. open(): establishes the VISA connection.- Configure and measure: set operating mode, level, range, and read measurements.
start(): begins a periodic background daemon. (Optional)stop(): ends the background daemon (if started).close(): disconnects from hardware.
Operating Modes
Electronic loads can operate in four modes:- CC (Constant Current): the load draws a constant current regardless of voltage
- CV (Constant Voltage): the load maintains a constant voltage across its terminals
- CR (Constant Resistance): the load simulates a constant resistance
- CP (Constant Power): the load draws constant power
Mode Configuration RequiredThe operating mode must be set using
set_mode() before you can configure the level or range. Not all electronic loads support all four modes. Consult your instrument’s manual.set_range() applies to CC and CV modes only; CP and CR are auto-ranged from the level value (on the BK85XXB driver, calling set_range() in CP or CR mode raises NotImplementedError).Creating an InstroELoad Instance
Parameters
name: A name for this electronic load instance. Used as a prefix for channel names when publishing. Falls back toconfig.device.namewhenconfigis given.driver: A concreteELoadDriverBaseinstance (e.g.BK85XXB) configured with the connection details for that model. Mutually exclusive withconfig.config: AnELoadConfig, a dict, or a path to a JSON config file, as an alternative todriver. See From a JSON Config File below.publishers: Optional list of publishers to attach. Combined with any publishers declared inconfig.autostart: WhenTrue, opens the connection and starts background polling immediately.**kwargs: Additional keyword arguments become default tags when using a publisher that supports tags (likeNominalCorePublisher).
From a JSON Config File
InstroELoad can also be constructed directly from a JSON config file, which removes the need to write any Python setup code:
device.name is required and used as the channel-name prefix when publishing; description, manufacturer, and model are optional descriptive metadata about the physical instrument.
driver.name must match one of the registered vendor/model keys (currently BK85XXB). The visa block accepts every VisaConfig field, so a non-default backend, timeout, or serial setting can be set from JSON too.
The optional load block declares the initial load state. It is applied through the public setters (set_mode, set_level, …) when open() runs, so it publishes the same .cmd channels the equivalent manual calls would:
mode(required within the block): one ofCC,CV,CP,CR. Required because level and range cannot be set before a mode.level: operating level in the mode’s units (CC: A, CV: V, CP: W, CR: Ω). Omit to keep the instrument default.curr_limit: current limit applied with the level. Only valid whenmodeisCV, mirroring theset_levelsignature.range: operating range in the mode’s units. On theBK85XXBdriver, range applies to CC and CV only (CP and CR are auto-ranged), so a config that setsrangewithmodeCPorCRraisesNotImplementedErrorwhen applied.slew_rate:{"direction": "RISE" | "FALL" | "BOTH", "rate": <A/µs>}, mapping toset_slewrate.
The config never enables the inputLoading a config file never causes the load to start sinking current or short its input. The
load block pre-arms the setpoint; enabling the input stays an explicit runtime call (eload.output_enable(True)).publishers is optional and accepts a list of NominalCorePublisher and/or FilePublisher entries, each tagged by type, as in the example above.
An optional top-level timing section ({"poll_interval": 1.0}) sets the background daemon’s polling interval. The polled measurements (get_voltage, get_current) work regardless of mode, so timing is valid without a load block for passive monitoring. Pass autostart=True to open the connection and start polling immediately:
config also accepts a plain dict or an already-built ELoadConfig, so a config built or received elsewhere in code can construct an InstroELoad directly, without a JSON round-trip.
JSON configs are validated strictly: ELoadConfig forbids any field not listed above (version, instrument, device, driver, load, timing, publishers), so there is no **kwargs-style escape hatch from JSON. Set default tags via the direct Python constructor instead.
Choosing a Driver
Choose the concrete driver that matches the electronic load model, then pass the instrument connection settings to that driver. For B&K Precision 85xx Series loads, useBK85XXB with the VISA resource string for the instrument.
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 ELoad Background DaemonFor each :
- Output voltage (via
get_voltage()) - Output current (via
get_current())
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 Reference
Custom Driver Development
This section is for developers implementingInstroELoad support for electronic loads that aren’t supported out of the box.
Overview
Driver developers subclassELoadDriverBase 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:
InstroELoad’s vendor-independent API (set_mode, set_level, get_voltage, …) into vendor-specific commands.
Driver Responsibilities
An electronic load 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.).
ELoadDriverBase Interface
All electronic load drivers subclassELoadDriverBase and implement these abstract methods:
_check_errors() helper and call it from your write/query paths (see the representative driver below).
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.
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:
Implementation Example: B&K Precision Driver
Here’s the complete driver implementation for B&K Precision 85xx Series electronic loads:Using a Custom Driver
For drivers that aren’t shipped in the library, constructInstroELoad 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 unifiedInstroELoad interface. Focus on:
- Subclassing
ELoadDriverBase - Designing a constructor around natural connection parameters for the instrument
- Hiding transport construction inside the driver
- Implementing all abstract methods on
ELoadDriverBase - Using the correct vendor protocol or command syntax
- Converting instrument responses to the expected Python types
- Adding a private
_check_errors()helper if your vendor exposes an error queue or status register - Testing with actual hardware to ensure commands work as expected