> ## Documentation Index
> Fetch the complete documentation index at: https://nominal-issue-234-simulated-eload.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# E-Load simulator

> Run and program the bundled SCPI electronic-load simulator

# E-Load simulator

The E-Load simulator is a local SCPI server for development, tests, and demos. It listens on a TCP socket, exposes a programmable electronic-load command set, and includes a terminal UI for editing the simulated source and channel limits while your code is running.

Each channel sinks current from a simulated Thevenin source: an open-circuit voltage behind a series resistance. The channel regulates in constant current (CC), constant voltage (CV), constant power (CP), or constant resistance (CR) mode. When the source cannot satisfy the setpoint, the channel reports the `UNREG` state.

## Quickstart

Start the simulator in one terminal:

```bash theme={null}
python -m instro.eload.scpi_sim_server
```

It binds to `127.0.0.1:5026` by default. If you are working from a source checkout, run the same command through `uv`:

```bash theme={null}
uv run python -m instro.eload.scpi_sim_server
```

Connect from another terminal or script with the `SimulatedELoad` driver. This small Python client opens an `InstroELoad`, configures a 2 A constant-current draw, and reads voltage and current measurements:

```python theme={null}
from instro.eload import InstroELoad, LoadMode
from instro.eload.drivers import SimulatedELoad


with InstroELoad(
    name="bench_load",
    driver=SimulatedELoad("TCPIP0::127.0.0.1::5026::SOCKET"),
) as eload:
    eload.set_mode(LoadMode.CC, channel=1)
    eload.set_level(2.0, channel=1)
    eload.output_enable(True, channel=1)

    voltage = eload.get_voltage(channel=1)
    current = eload.get_current(channel=1)
    print(f"V: {voltage.latest:.3f} V")
    print(f"I: {current.latest:.3f} A")

    eload.output_enable(False, channel=1)
```

Use CLI flags to change the bind address, port, or initial channel count:

```bash theme={null}
python -m instro.eload.scpi_sim_server --host 127.0.0.1 --port 5026 --channels 1
```

## Usage

The simulator runs a terminal UI while the TCP server listens in the background. The UI is organized by panel header.

<img alt="E-Load simulator terminal UI showing the E-Load panel, channel panels, and SCPI log" src="https://mintcdn.com/nominal-issue-234-simulated-eload/2-hI7SHr07IoIkzR/images/eload-simulator-overview.svg?fit=max&auto=format&n=2-hI7SHr07IoIkzR&q=85&s=9e3f0979aa16f64d5fb06be64a3c10ec" width="1751" height="1124" data-path="images/eload-simulator-overview.svg" />

Filled labels are selectable actions. Use the arrow keys to move between controls. Focused actions use a light highlight. Press Enter to select an action, Escape to close an edit prompt, and `q` to quit.

### E-Load

The **⟢ NOMINAL E-LOAD** panel shows the simulator identity and VISA resource string. Use the resource string when driver code connects to the simulator.

### Channel

Each **CHANNEL** panel shows the live state for one input. The panel is split into status, setpoint, and measurement sections.

| Header        | Functionality                                                                                                                                                                                             |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **STATUS**    | Shows the operating state (`OFF`, `CC`, `CV`, `CP`, `CR`, `UNREG`, `SHORT`), active function, input state, short state, and CV current limit. Select **REMOVE** to delete the channel from the simulator. |
| **SETPOINTS** | Shows the CC, CV, CP, and CR setpoints and the active function's range. Select **I MAX** to edit the channel current ceiling.                                                                             |
| **MEASURE**   | Shows terminal voltage, input current, input power, and the rise and fall slew rates. Select **V MAX** to edit the channel voltage ceiling.                                                               |

Changing **I MAX** or **V MAX** runs `*RST`. The input turns off, setpoints return to zero, and ranges and the CV current limit return to the configured ceilings.

### Source

The **SOURCE** panel shows the simulated Thevenin source feeding the channel. Select **V** to edit the open-circuit voltage in volts. Select **R** to edit the series resistance in ohms.

### + channel

The **+ CHANNEL** panel adds a new input channel with the next available channel ID.

### SCPI log

The **SCPI LOG** panel records incoming commands, responses, and queued errors. It is read-only.

## Programming guide

Brackets mark optional SCPI path components. For example, `[SOURce:]CURRent[:LEVel]` accepts `CURR`, `CURR:LEV`, `SOUR:CURR`, and `SOUR:CURR:LEV`.

Angle brackets mark command arguments. They are notation only; do not include the angle brackets in the command.

* <code>\<NRf+></code> is a positive flexible numeric value, such as `0`, `1.25`, or `1e-3`.
* <code>\<literal|literal></code> is one literal value from the listed options.

The pipe character (`|`) means OR. For example, <code>\<0|1|OFF|ON></code> accepts `0`, `1`, `OFF`, or `ON`, and <code>\<NRf+|MIN|MAX></code> accepts one numeric value, `MIN`, or `MAX`. Do not send the pipe character in the command.

For level commands, `MIN` maps to `0` and `MAX` maps to the active range for that function. For range and current-limit commands, `MAX` maps to the channel ceiling (**I MAX**, **V MAX**, and the fixed power and resistance ceilings). For slew commands, `MIN` and `MAX` map to the slew-rate bounds. Do not send `MIN` or `MAX` to queries.

Setting a range below the current setpoint clamps the setpoint down to the new range.

### Defaults and reset

On startup and after `*RST`, each channel uses the reset operating state:

| Setting                  | Reset value |
| ------------------------ | ----------- |
| Function                 | `CURR` (CC) |
| CC, CV, CP, CR setpoints | `0`         |
| Ranges                   | `MAX`       |
| CV current limit         | `MAX`       |
| Slew rise and fall       | `MAX`       |
| Input state              | `OFF`       |
| Short state              | `OFF`       |

`*RST` preserves simulator configuration, including channel count, channel IDs, source voltage, source resistance, and configured channel ceilings.

### Error queue

Errors are queued and returned one at a time by `SYSTem:ERRor?`. The simulator can emit these error codes:

| Code   | Message                      | Cause                                               |
| ------ | ---------------------------- | --------------------------------------------------- |
| `0`    | `No error`                   | The queue is empty                                  |
| `-108` | `Parameter not allowed`      | A query received an argument                        |
| `-109` | `Missing parameter`          | A command was sent without a required argument      |
| `-113` | `Undefined header`           | The command header is not supported                 |
| `-114` | `Header suffix out of range` | The command targets a channel that does not exist   |
| `-141` | `Invalid character data`     | A numeric argument could not be parsed              |
| `-222` | `Data out of range`          | A numeric input is outside its accepted range       |
| `-224` | `Illegal parameter value`    | A literal argument is not accepted for that command |

### Common commands

<div style={{ overflowX: "auto" }}>
  <table>
    <tbody>
      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\*IDN?</code></td>
      </tr>

      <tr>
        <td>Query simulator identity</td>
        <td>Response: String</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\*RST</code></td>
      </tr>

      <tr>
        <td>Reset channel operating state and clear errors</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\*CLS</code></td>
      </tr>

      <tr>
        <td>Clear the error queue</td>
        <td>Response: None</td>
      </tr>
    </tbody>
  </table>
</div>

### MEASure subsystem

<div style={{ overflowX: "auto" }}>
  <table>
    <tbody>
      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>MEASure:CURRent?</code></td>
      </tr>

      <tr>
        <td>Measure input current</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>MEASure:VOLTage?</code></td>
      </tr>

      <tr>
        <td>Measure terminal voltage</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>MEASure:POWer?</code></td>
      </tr>

      <tr>
        <td>Measure input power</td>
        <td>Response: Number</td>
      </tr>
    </tbody>
  </table>
</div>

With the input off, `MEASure:VOLTage?` returns the source open-circuit voltage and `MEASure:CURRent?` returns `0`.

### INPut subsystem

<div style={{ overflowX: "auto" }}>
  <table>
    <tbody>
      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>INPut\[:STATe] \<0|1|OFF|ON></code></td>
      </tr>

      <tr>
        <td>Enable or disable the input</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>INPut\[:STATe]?</code></td>
      </tr>

      <tr>
        <td>Query input state</td>
        <td>Response: <code>0</code> or <code>1</code></td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>INPut:SHORt\[:STATe] \<0|1|OFF|ON></code></td>
      </tr>

      <tr>
        <td>Apply or release the input short</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>INPut:SHORt\[:STATe]?</code></td>
      </tr>

      <tr>
        <td>Query short state</td>
        <td>Response: <code>0</code> or <code>1</code></td>
      </tr>
    </tbody>
  </table>
</div>

### SOURce subsystem

<div style={{ overflowX: "auto" }}>
  <table>
    <tbody>
      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]FUNCtion \<CURRent|VOLTage|POWer|RESistance></code></td>
      </tr>

      <tr>
        <td>Select the regulation function (CC, CV, CP, CR)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]FUNCtion?</code></td>
      </tr>

      <tr>
        <td>Query the regulation function</td>
        <td>Response: <code>CURR</code>, <code>VOLT</code>, <code>POW</code>, or <code>RES</code></td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent\[:LEVel]\[:IMMediate] \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CC setpoint (amperes)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent\[:LEVel]\[:IMMediate]?</code></td>
      </tr>

      <tr>
        <td>Query the CC setpoint</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]VOLTage\[:LEVel]\[:IMMediate] \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CV setpoint (volts)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]VOLTage\[:LEVel]\[:IMMediate]?</code></td>
      </tr>

      <tr>
        <td>Query the CV setpoint</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]POWer\[:LEVel]\[:IMMediate] \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CP setpoint (watts)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]POWer\[:LEVel]\[:IMMediate]?</code></td>
      </tr>

      <tr>
        <td>Query the CP setpoint</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]RESistance\[:LEVel]\[:IMMediate] \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CR setpoint (ohms)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]RESistance\[:LEVel]\[:IMMediate]?</code></td>
      </tr>

      <tr>
        <td>Query the CR setpoint</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:RANGe \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CC range (amperes)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:RANGe?</code></td>
      </tr>

      <tr>
        <td>Query the CC range</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]VOLTage:RANGe \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CV range (volts)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]VOLTage:RANGe?</code></td>
      </tr>

      <tr>
        <td>Query the CV range</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]POWer:RANGe \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CP range (watts)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]POWer:RANGe?</code></td>
      </tr>

      <tr>
        <td>Query the CP range</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]RESistance:RANGe \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CR range (ohms)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]RESistance:RANGe?</code></td>
      </tr>

      <tr>
        <td>Query the CR range</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:LIMit \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the CV-mode current limit (amperes)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:LIMit?</code></td>
      </tr>

      <tr>
        <td>Query the CV-mode current limit</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:SLEW\[:BOTH] \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the rise and fall slew rates together (A/μs)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:SLEW:RISE \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the rising-edge slew rate (A/μs)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:SLEW:RISE?</code></td>
      </tr>

      <tr>
        <td>Query the rising-edge slew rate</td>
        <td>Response: Number</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:SLEW:FALL \<NRf+|MIN|MAX></code></td>
      </tr>

      <tr>
        <td>Set the falling-edge slew rate (A/μs)</td>
        <td>Response: None</td>
      </tr>

      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>\[SOURce:]CURRent:SLEW:FALL?</code></td>
      </tr>

      <tr>
        <td>Query the falling-edge slew rate</td>
        <td>Response: Number</td>
      </tr>
    </tbody>
  </table>
</div>

### SYSTem subsystem

<div style={{ overflowX: "auto" }}>
  <table>
    <tbody>
      <tr>
        <td colSpan={2} style={{ whiteSpace: "nowrap" }}><code>SYSTem:ERRor?</code></td>
      </tr>

      <tr>
        <td>Pop one error from the error queue</td>
        <td>Response: <code>code,"message"</code></td>
      </tr>
    </tbody>
  </table>
</div>
