A reader asked where FrameIO gets its list of known frame names from —
because looking at `fio.send("ALM_Req_A", ...)` it seems like the class
must hold a registry somewhere. It doesn't: FrameIO is a broker that
forwards an incoming string to the LDF object it was constructed with,
and the string lives either in the test source (Path A) or in the
generated wrapper class (Path B).
Adds section 2 "How frame names reach FrameIO" to
docs/19_frame_io_and_alm_helpers.md, between the "Three layers of
access" overview (section 1) and the API reference (formerly section 2,
now section 3). The new section contains:
- A table of where the names actually live: LDF file on disk,
LdfDatabase after parsing, caller source code. FrameIO is explicitly
NOT in that table.
- The FrameIO class skeleton showing the empty _frames cache.
- A concrete ASCII call trace of `fio.send("ALM_Req_A", ...)` from
test source -> FrameIO -> LdfDatabase -> ldfparser -> byte layout.
- Path A (stringly-typed) vs Path B (typed wrapper from gen_lin_api),
with the trade-off (typo caught at runtime vs at import time).
- The cache lifecycle (starts empty, fills lazily, one entry per
unique frame name passed in).
- A "mental model" summary calling FrameIO a generic glue layer.
Sections 3-9 renumbered to make room (3->4, 4->5, ..., 8->9). The 7.x
sub-sections under "Writing a new test" become 8.x. Updates the
stale anchor link in 14_power_supply.md
(#72-the-four-phase-test-pattern -> #82-the-four-phase-test-pattern).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
487 lines
19 KiB
Markdown
487 lines
19 KiB
Markdown
# Power Supply (Owon) — control, configuration, tests, and quick demo
|
|
|
|
This guide covers driving the Owon bench power supply via SCPI over a
|
|
serial link, plus the cross-platform port resolver and the safety
|
|
guarantees the controller class provides.
|
|
|
|
> **MUM users**: the Melexis Universal Master has its own power output
|
|
> on `power_out0` and the MUM adapter calls `power_up()` /
|
|
> `power_down()` in `connect()` / `disconnect()` automatically. The
|
|
> Owon PSU is **not required** for the standard MUM flow — leave
|
|
> `power_supply.enabled: false`. The Owon remains useful for
|
|
> over/under-voltage scenarios, separate-rail tests, or when running
|
|
> with the deprecated BabyLIN adapter (which has no built-in power).
|
|
|
|
| Artifact | Path |
|
|
|---|---|
|
|
| Controller library | [`ecu_framework/power/owon_psu.py`](../ecu_framework/power/owon_psu.py) |
|
|
| Hardware test | [`tests/hardware/psu/test_owon_psu.py`](../tests/hardware/psu/test_owon_psu.py) |
|
|
| Quick demo script | [`vendor/Owon/owon_psu_quick_demo.py`](../vendor/Owon/owon_psu_quick_demo.py) |
|
|
| Central config | [`config/test_config.yaml`](../config/test_config.yaml) → `power_supply` |
|
|
| Per-machine override | `config/owon_psu.yaml` or env `OWON_PSU_CONFIG` |
|
|
|
|
---
|
|
|
|
## 1. Install dependencies
|
|
|
|
```powershell
|
|
pip install -r .\requirements.txt
|
|
```
|
|
|
|
`pyserial` is the only non-stdlib dep used by the controller.
|
|
|
|
---
|
|
|
|
## 2. Configure
|
|
|
|
Settings can live centrally in `config/test_config.yaml` or be peeled
|
|
out into a machine-specific `config/owon_psu.yaml` (or any path set
|
|
via `OWON_PSU_CONFIG`). The loader merges the per-machine file into
|
|
the central `power_supply` section.
|
|
|
|
```yaml
|
|
power_supply:
|
|
enabled: true
|
|
port: COM7 # see §3 for cross-platform behaviour
|
|
baudrate: 115200
|
|
timeout: 1.0
|
|
eol: "\n" # or "\r\n" if your device requires CRLF
|
|
parity: N # N|E|O
|
|
stopbits: 1 # 1|1.5|2
|
|
xonxoff: false
|
|
rtscts: false
|
|
dsrdtr: false
|
|
idn_substr: OWON # optional — see §4 (auto-detection)
|
|
do_set: false
|
|
set_voltage: 5.0
|
|
set_current: 0.1
|
|
```
|
|
|
|
### Field reference
|
|
|
|
| Field | Default | Meaning |
|
|
|---|---|---|
|
|
| `enabled` | `false` | Master gate. Tests/utilities skip when `false`. |
|
|
| `port` | `null` | Bench port name. See §3 — works for `COM7` *or* `/dev/ttyUSB0` and translates between them. |
|
|
| `baudrate` | `115200` | Serial bit rate. |
|
|
| `timeout` | `1.0` | Read timeout in seconds. |
|
|
| `eol` | `"\n"` | Line terminator appended to every command and expected on every response. |
|
|
| `parity` | `"N"` | One of `N`, `E`, `O`. Translated to `pyserial` constants by `SerialParams.from_config()`. |
|
|
| `stopbits` | `1` | One of `1`, `1.5`, `2`. |
|
|
| `xonxoff` / `rtscts` / `dsrdtr` | `false` | Flow control flags. |
|
|
| `idn_substr` | `null` | Optional substring (case-insensitive) the device's `*IDN?` must contain to be accepted. Used as the filter when scanning ports for auto-detection. |
|
|
| `do_set` | `false` | If `true`, the hardware test runs the set/measure cycle (sets V/I, enables output briefly, measures, disables). |
|
|
| `set_voltage` / `set_current` | `5.0` / `0.1` | Setpoints used when `do_set: true`. |
|
|
|
|
---
|
|
|
|
## 3. Cross-platform port resolution
|
|
|
|
A bench config typically names the port the way Windows sees it
|
|
(`COM7`). The resolver lets the **same config** work on Windows,
|
|
Linux, and WSL by trying multiple candidates in priority order.
|
|
|
|
### What the resolver does
|
|
|
|
`resolve_port(configured, *, idn_substr, params)` walks four phases
|
|
and returns the first port whose `*IDN?` response is non-empty
|
|
(filtered by `idn_substr` if given):
|
|
|
|
| Phase | What's tried | Use case |
|
|
|---|---|---|
|
|
| 1 | `configured` verbatim | Windows native — `COM7` opens directly. |
|
|
| 2 | Cross-platform translation | `COM7` ↔ `/dev/ttyS6` on WSL1; `/dev/ttyS6` ↔ `COM7` on Windows. |
|
|
| 3 | Linux USB-serial paths | `/dev/ttyUSB*` and `/dev/ttyACM*` — covers WSL2 with `usbipd-win` plus generic Linux USB adapters. Linux/WSL only. |
|
|
| 4 | Full `scan_ports()` | Last resort — probes every serial port `pyserial` reports. |
|
|
|
|
Linux device files that don't exist on disk are skipped without an
|
|
open attempt, so the resolver is fast even on machines with many
|
|
phantom `ttyS*` entries.
|
|
|
|
### What works on each platform with `port: COM7`
|
|
|
|
| Host | What happens |
|
|
|---|---|
|
|
| **Windows native** | Phase 1 hits `COM7` directly. |
|
|
| **WSL1** | Phase 1 fails on `COM7`, Phase 2 finds `/dev/ttyS6` (the COM7 mapping). |
|
|
| **WSL2 + `usbipd-win`** | Phase 1+2 fail, Phase 3 finds the attached adapter at `/dev/ttyUSB0`. |
|
|
| **Linux native (USB adapter)** | Phases 1+2 fail, Phase 3 finds `/dev/ttyUSB0`. |
|
|
|
|
The resolved port is recorded in the JUnit testsuite properties as
|
|
`psu_resolved_port` (and the IDN as `psu_resolved_idn`), so report
|
|
viewers can see which path was used.
|
|
|
|
### Translation helpers
|
|
|
|
Useful as building blocks if you need to do the mapping yourself:
|
|
|
|
```python
|
|
from ecu_framework.power import (
|
|
windows_com_to_linux, linux_serial_to_windows,
|
|
candidate_ports, resolve_port,
|
|
)
|
|
|
|
windows_com_to_linux("COM7") # → "/dev/ttyS6"
|
|
windows_com_to_linux("com10") # → "/dev/ttyS9"
|
|
linux_serial_to_windows("/dev/ttyS6") # → "COM7"
|
|
|
|
# What resolve_port will try, in order, for port="COM7" on Linux:
|
|
candidate_ports("COM7")
|
|
# → ['COM7', '/dev/ttyS6', '/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyACM0', ...]
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Auto-detection
|
|
|
|
Leave `port` empty and set `idn_substr` to let the resolver scan:
|
|
|
|
```yaml
|
|
power_supply:
|
|
enabled: true
|
|
port: # ← empty
|
|
idn_substr: OWON # ← required so we don't grab a different SCPI device
|
|
...
|
|
```
|
|
|
|
With no `port`, Phase 1 and Phase 2 short-circuit; Phase 3 (Linux USB
|
|
paths) and Phase 4 (full scan) do the work. The first port whose IDN
|
|
contains `OWON` (case-insensitive) wins.
|
|
|
|
> **Tip:** without `idn_substr`, *any* device that responds to `*IDN?`
|
|
> on any port is accepted — fine when the PSU is the only SCPI thing
|
|
> attached, risky otherwise. Always set `idn_substr` if your bench has
|
|
> other SCPI hardware.
|
|
|
|
---
|
|
|
|
## 5. Session-managed power (the bench powers the ECU through the PSU)
|
|
|
|
On benches where the **Owon PSU powers the ECU** (the MUM only carries
|
|
LIN traffic), the PSU output must stay on for the *entire* test
|
|
session — not just the duration of an individual PSU test. Otherwise
|
|
every test that runs after a closed PSU connection would brown out
|
|
the ECU and fail.
|
|
|
|
The hardware-suite conftest
|
|
([`tests/hardware/conftest.py`](../tests/hardware/conftest.py))
|
|
implements this with three session-scoped fixtures:
|
|
|
|
| Fixture | Scope | Role |
|
|
|---|---|---|
|
|
| `_psu_or_none` | session | Tolerant: opens the PSU once, parks at `set_voltage` / `set_current`, enables output. Yields the live `OwonPSU` or `None` if unreachable. Closes (with `output 0`) at session end. |
|
|
| `_psu_powers_bench` | session, **autouse** | Realizes `_psu_or_none`. Every hardware test triggers PSU power-up at session start, even tests that don't request `psu` by name. |
|
|
| `psu` | session | Public fixture for tests that read measurements or perturb voltage. Skips cleanly when the PSU isn't available. |
|
|
|
|
### What this means for tests
|
|
|
|
Tests **should**:
|
|
|
|
- Request `psu` if they need to read measurements or change the supply voltage.
|
|
- Always restore nominal voltage in their `finally` block — the session fixture won't restore it between tests.
|
|
|
|
Tests **must not**:
|
|
|
|
- Call `psu.set_output(False)` — this kills ECU power for every later test in the same session.
|
|
- Call `psu.close()` — the session fixture owns the lifecycle.
|
|
|
|
### What changed in the existing tests
|
|
|
|
- **`tests/hardware/psu/test_owon_psu.py`** is now read-only: it queries `*IDN?`, `output?`, and the parsed measurement helpers, but doesn't toggle the output. The previous toggle-and-restore cycle has been deleted because it would brown out the bench mid-session.
|
|
- **`tests/hardware/_test_case_template_psu_lin.py`** drops its local `psu` fixture and uses the conftest's. Its autouse `_park_at_nominal` only restores voltage between tests — it never toggles output.
|
|
|
|
---
|
|
|
|
## 6. Run the hardware test
|
|
|
|
Skips cleanly unless `power_supply.enabled` is true, a port can be
|
|
resolved, and the device responds to `*IDN?`.
|
|
|
|
```powershell
|
|
pytest -k test_owon_psu_idn_and_optional_set -m hardware -q
|
|
```
|
|
|
|
What it does:
|
|
|
|
1. Resolves a working port via `resolve_port(...)` (cross-platform,
|
|
IDN-verified).
|
|
2. Queries `*IDN?` and the initial `output?` state.
|
|
3. If `do_set` is true: sets V/I, enables output, waits, measures,
|
|
disables output. The measure/disable pair lives in an inner
|
|
`try`/`finally` so the disable runs even if measurement raises.
|
|
4. Records IDN, before/after output state, setpoints, and parsed
|
|
measurements as report properties.
|
|
5. The fixture's `safe_off_on_close=True` is a backstop — it will
|
|
send `output 0` once more when the port closes.
|
|
|
|
The test follows the four-phase
|
|
[SETUP / PROCEDURE / ASSERT / TEARDOWN pattern from the template](19_frame_io_and_alm_helpers.md#82-the-four-phase-test-pattern)
|
|
because it mutates real bench state.
|
|
|
|
### The settle-then-validate pattern (recommended for any voltage-changing test)
|
|
|
|
Voltage changes go through two delays — and confusing them is the
|
|
single most common source of flaky tests:
|
|
|
|
| Delay | Source | Bench-dependent? |
|
|
|---|---|---|
|
|
| **PSU settling** | Owon needs time to slew its output to the new setpoint | **Yes** — depends on PSU model, load, cable drop. Different up-step / down-step times in practice. |
|
|
| **ECU validation** | Firmware samples its supply rail, debounces, and republishes status on its 10 ms LIN cycle | No (firmware-dependent, but constant for a given build) |
|
|
|
|
The shared helper [`tests/hardware/psu_helpers.py`](../tests/hardware/psu_helpers.py)
|
|
exposes `apply_voltage_and_settle()` which separates the two cleanly:
|
|
|
|
```python
|
|
from psu_helpers import apply_voltage_and_settle
|
|
|
|
result = apply_voltage_and_settle(
|
|
psu, OVERVOLTAGE_V,
|
|
validation_time=ECU_VALIDATION_TIME_S, # firmware budget
|
|
)
|
|
# By here:
|
|
# - PSU output is measurably at OVERVOLTAGE_V (within ±0.10 V)
|
|
# - validation_time has elapsed since the rail settled
|
|
# So a single status read is unambiguous:
|
|
status = fio.read_signal("ALM_Status", "ALMVoltageStatus")
|
|
assert status == VOLTAGE_STATUS_OVER
|
|
```
|
|
|
|
What `apply_voltage_and_settle` does internally:
|
|
|
|
1. `psu.set_voltage(1, target_v)` — issue the setpoint.
|
|
2. Polls `measure_voltage_v()` every 50 ms until the rail is within
|
|
±100 mV of target (or raises `AssertionError` on timeout).
|
|
3. `time.sleep(validation_time)` — hold the steady rail.
|
|
4. Returns `{settled_s, validation_s, final_v, trace}` for reporting.
|
|
|
|
The poll-the-meter approach means the function works on any bench
|
|
without re-tuning sleeps. Up-step and down-step are handled
|
|
identically — each waits as long as that *specific* transition takes.
|
|
|
|
To pick `ECU_VALIDATION_TIME_S`, run the characterization in §6.1
|
|
to learn your PSU's slew time, then add a margin for the firmware's
|
|
detection-and-debounce window. Default `1.0 s` is conservative for
|
|
most automotive ECUs. Tests that change voltage many times should
|
|
use the smallest validation time their firmware tolerates.
|
|
|
|
### Characterizing PSU settling time
|
|
|
|
Voltage-tolerance tests need to wait long enough after a setpoint
|
|
change for the PSU's output to actually reach the new voltage. The
|
|
right wait depends on the PSU model and the load. To extract real
|
|
numbers, run the dedicated characterization test:
|
|
|
|
```powershell
|
|
pytest -m psu_settling -s
|
|
```
|
|
|
|
`tests/hardware/psu/test_psu_voltage_settling.py` walks four
|
|
transitions (`13 V↔18 V`, `13 V↔7 V`), polls `measure_voltage_v()`
|
|
every 50 ms until the rail is within ±100 mV of target, and records
|
|
`settling_time_s` plus a downsampled voltage trace per case. The
|
|
test is marked `psu_settling` + `slow` so it doesn't run on every
|
|
`-m hardware` invocation — it's meant for periodic re-tuning, not
|
|
every CI run.
|
|
|
|
Use the recorded settling times to size constants like
|
|
`VOLTAGE_DETECT_TIMEOUT` in `test_overvolt.py`: the timeout has to
|
|
exceed *both* the PSU's settling time *and* the ECU's detection
|
|
delay, so add a margin to the larger of the two.
|
|
|
|
### Writing a PSU+LIN test (over/undervoltage etc.)
|
|
|
|
For tests that *combine* PSU control with LIN observation — e.g.
|
|
overvoltage / undervoltage tolerance — there's a dedicated
|
|
copy-paste-ready template at
|
|
[`tests/hardware/_test_case_template_psu_lin.py`](../tests/hardware/_test_case_template_psu_lin.py).
|
|
It contains:
|
|
|
|
- The three module-scoped fixtures (`fio`, `alm`, `psu`) wired with
|
|
cross-platform port resolution and `safe_off_on_close=True`.
|
|
- An autouse `_park_at_nominal` fixture that parks the PSU at
|
|
`NOMINAL_VOLTAGE` and the LED OFF before AND after every test, so
|
|
failures don't leak supply state between tests.
|
|
- A `wait_for_voltage_status(fio, target, …)` helper that polls
|
|
`ALM_Status.ALMVoltageStatus` until it matches.
|
|
- Three flavors:
|
|
| Flavor | Demonstrates |
|
|
|---|---|
|
|
| A | Overvoltage detection — drive PSU above OV threshold, expect `ALMVoltageStatus = 0x02`, restore. |
|
|
| B | Undervoltage detection — symmetric for UV (`0x01`). |
|
|
| C | Parametrized voltage sweep walking `(V, expected_status)` tuples. |
|
|
|
|
Tune the four constants at the top of the file
|
|
(`NOMINAL_VOLTAGE`, `OVERVOLTAGE_V`, `UNDERVOLTAGE_V`,
|
|
`SET_CURRENT_A`) to your ECU's datasheet before running on real
|
|
hardware. The defaults are conservative automotive ranges.
|
|
|
|
---
|
|
|
|
## 7. Library API
|
|
|
|
```python
|
|
from ecu_framework.power import (
|
|
SerialParams, OwonPSU, resolve_port,
|
|
scan_ports, auto_detect, try_idn_on_port,
|
|
)
|
|
```
|
|
|
|
### `SerialParams`
|
|
|
|
Plain dataclass for serial-port settings. Build directly, or from the
|
|
project's PSU config:
|
|
|
|
```python
|
|
params = SerialParams(baudrate=115200, timeout=1.0)
|
|
# or
|
|
params = SerialParams.from_config(config.power_supply) # translates 'N'/'1' → pyserial constants
|
|
```
|
|
|
|
### `OwonPSU`
|
|
|
|
Context-managed controller. Two construction paths:
|
|
|
|
```python
|
|
# Manual:
|
|
psu = OwonPSU(port="COM4", params=params, eol="\n")
|
|
|
|
# From central config (recommended):
|
|
psu = OwonPSU.from_config(config.power_supply)
|
|
```
|
|
|
|
Then either use as a context manager or call `open()` / `close()` by
|
|
hand. Both forms send `output 0` before closing the port if
|
|
`safe_off_on_close=True` (the default).
|
|
|
|
```python
|
|
with OwonPSU.from_config(cfg) as psu:
|
|
print(psu.idn()) # *IDN?
|
|
psu.set_voltage(1, 5.0) # SOUR:VOLT 5.000
|
|
psu.set_current(1, 0.1) # SOUR:CURR 0.100
|
|
psu.set_output(True) # output 1
|
|
v = psu.measure_voltage_v() # MEAS:VOLT? → float
|
|
i = psu.measure_current_a() # MEAS:CURR? → float
|
|
is_on = psu.output_is_on() # output? → True/False/None
|
|
# safe_off_on_close=True turned the output OFF before the port closed
|
|
```
|
|
|
|
#### Method reference
|
|
|
|
| Method | SCPI sent | Returns |
|
|
|---|---|---|
|
|
| `idn()` | `*IDN?` | `str` |
|
|
| `set_voltage(channel, volts)` | `SOUR:VOLT <V>` | `None`. `channel` is currently ignored — placeholder for multi-channel firmware. |
|
|
| `set_current(channel, amps)` | `SOUR:CURR <A>` | `None` |
|
|
| `set_output(on)` | `output 1`/`output 0` | `None`. Note: dialect uses *lowercase* `output`, not `OUTP ON`. |
|
|
| `output_status()` | `output?` | Raw `str` (`'ON'`/`'OFF'`/`'1'`/`'0'`). |
|
|
| `output_is_on()` | `output?` | `bool` (or `None` if unparseable). |
|
|
| `measure_voltage()` | `MEAS:VOLT?` | Raw `str`. |
|
|
| `measure_voltage_v()` | `MEAS:VOLT?` | `float` (V) or `None`. |
|
|
| `measure_current()` | `MEAS:CURR?` | Raw `str`. |
|
|
| `measure_current_a()` | `MEAS:CURR?` | `float` (A) or `None`. |
|
|
| `query(s)` | `s` | Single-line `str` response (with newline stripped). |
|
|
| `write(s)` | `s` | `None`. No response read. |
|
|
|
|
#### Safety: `safe_off_on_close`
|
|
|
|
`OwonPSU(safe_off_on_close=True)` (the default) sends `output 0`
|
|
before the serial port closes. This protects against leaving the
|
|
bench powered on after an aborted test, an exception in user code, or
|
|
a forgotten manual close. Errors during the safe-off attempt are
|
|
swallowed so the close itself always completes.
|
|
|
|
Pass `safe_off_on_close=False` only when you specifically need the
|
|
output to stay enabled across context-manager boundaries. The
|
|
discovery helper `try_idn_on_port` opts out by default since it
|
|
shouldn't drive the bench in either direction.
|
|
|
|
### Discovery helpers
|
|
|
|
```python
|
|
# Probe one port, return its IDN (or "" on failure):
|
|
try_idn_on_port("COM7", params)
|
|
|
|
# Scan every serial port; returns [(port, idn), ...] for responders:
|
|
scan_ports(params)
|
|
|
|
# Pick the first responder matching idn_substr (or first responder if no substring):
|
|
auto_detect(params, idn_substr="OWON")
|
|
|
|
# Cross-platform resolver (recommended): tries the configured port,
|
|
# its translation, USB-serial paths, then a full scan. Returns
|
|
# (port, idn) or None.
|
|
resolve_port("COM7", idn_substr="OWON", params=params)
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Quick demo script
|
|
|
|
The quick demo reads `OWON_PSU_CONFIG` or `config/owon_psu.yaml` and
|
|
performs a short sequence using the same library.
|
|
|
|
```powershell
|
|
python .\vendor\Owon\owon_psu_quick_demo.py
|
|
```
|
|
|
|
It also scans ports with `*IDN?` via `scan_ports()` to help confirm
|
|
which port the device is on before you commit it to the YAML.
|
|
|
|
---
|
|
|
|
## 9. Troubleshooting
|
|
|
|
### Empty `*IDN?` / timeouts
|
|
|
|
- Verify the port and exclusivity — no other program may hold it open.
|
|
- Try `eol: "\r\n"` if your firmware revision expects CRLF.
|
|
- Adjust `parity` and `stopbits` per your device manual.
|
|
- Power-cycle the PSU and re-attempt — some firmware revisions need
|
|
a fresh boot before they accept SCPI.
|
|
|
|
### `Could not find a working PSU port`
|
|
|
|
The fixture skips with this message when `resolve_port` returns
|
|
`None`. Things to check, in order:
|
|
|
|
1. Is the device powered and connected?
|
|
2. Does another process (Putty, Owon's own tool, an old test session)
|
|
still hold the port?
|
|
3. Does your user have permission to open the device file? On
|
|
Debian-style systems: `sudo usermod -aG dialout $USER` and re-login.
|
|
4. **WSL2 specifically**: USB-serial adapters need
|
|
[`usbipd-win`](https://learn.microsoft.com/en-us/windows/wsl/connect-usb)
|
|
to bind the device into the Linux side. Once attached they appear
|
|
at `/dev/ttyUSB0` and the resolver's Phase 3 picks them up
|
|
automatically.
|
|
5. **WSL1**: COMx → /dev/ttySn mapping is automatic. If `/dev/ttyS6`
|
|
doesn't exist for `COM7`, the bench probably has Windows COM port
|
|
numbering you weren't expecting — list with
|
|
`ls /dev/ttyS*` and try `linux_serial_to_windows()` to confirm.
|
|
|
|
### Windows COM > 9
|
|
|
|
Most Python tooling (including `pyserial`) accepts `COM10` directly.
|
|
If a third-party tool needs the long form, use `\\.\COM10`. The
|
|
translator in this repo accepts any positive integer.
|
|
|
|
### Flow control
|
|
|
|
Keep `xonxoff`, `rtscts`, `dsrdtr` set to `false` unless your specific
|
|
PSU model requires otherwise — the Owon family used in this project
|
|
doesn't.
|
|
|
|
---
|
|
|
|
## 10. Related files
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `ecu_framework/power/owon_psu.py` | Controller library (`SerialParams`, `OwonPSU`, resolver helpers). |
|
|
| `tests/hardware/psu/test_owon_psu.py` | Hardware test wired to central config. |
|
|
| `vendor/Owon/owon_psu_quick_demo.py` | Quick demo runner. |
|
|
| `config/owon_psu.example.yaml` | Example per-machine YAML. |
|
|
| `tests/hardware/_test_case_template.py` | Copyable starting point for new hardware tests. |
|
|
| [`docs/19_frame_io_and_alm_helpers.md`](19_frame_io_and_alm_helpers.md) | The four-phase test pattern and the FrameIO / AlmTester helpers. |
|
|
| [`docs/15_report_properties_cheatsheet.md`](15_report_properties_cheatsheet.md) | Standard `rp(...)` keys including the PSU ones (`psu_idn`, `psu_resolved_port`, …). |
|