On benches where the Owon PSU powers the ECU, every per-file PSU
fixture that closed the port (sending 'output 0' on close) browned
out the bench between modules — every MUM test that ran after a
closed PSU connection failed with "ECU not responding".
New tests/hardware/conftest.py provides three session-scoped
fixtures:
- _psu_or_none: tolerant. Opens the Owon PSU once via resolve_port,
parks at config.power_supply.set_voltage / set_current, enables
output. Yields the live OwonPSU or None. Closes (with
safe_off_on_close=True) at session end — the bench ends safely
de-energized.
- _psu_powers_bench: autouse=True. Realizes _psu_or_none so even
tests that don't request `psu` by name benefit from the
session-level power-up. No-op if PSU isn't configured.
- psu: public. Skips cleanly when the PSU isn't reachable.
Contract for tests:
- request `psu` if you need to read measurements or change voltage
- restore nominal voltage in your finally block
- MUST NOT call psu.set_output(False) (would brown out the bench)
- MUST NOT call psu.close() (the session fixture owns it)
test_owon_psu.py becomes read-only:
- removed the local module-scoped psu fixture
- removed the set_output toggle (would have killed the session)
- now validates IDN, output_is_on(), and parsed measurements
against the always-on PSU. Renamed to
test_owon_psu_idn_and_measurements to reflect the new shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits hardware-test concerns into two reusable modules and rebuilds
test_mum_alm_animation.py on top of them.
- frame_io.py — generic LDF-driven I/O class. Knows nothing about
ALM. Three access levels:
high: send/receive/read_signal by frame and signal name
mid: pack/unpack — bytes ↔ signals without I/O
low: send_raw/receive_raw — bypass the LDF entirely
Plus introspection: frame, frame_id, frame_length. Frame lookups
are cached per FrameIO instance.
- alm_helpers.py — ALM_Node domain helpers built on FrameIO.
AlmTester class bound to (fio, nad) exposes:
force_off, read_led_state, wait_for_state,
measure_animating_window, assert_pwm_matches_rgb,
assert_pwm_wo_comp_matches_rgb
Plus pure utilities (ntc_kelvin_to_celsius, pwm_within_tol) and
the LED-state / pacing / PWM-tolerance constants. PWM assertions
use vendor/rgb_to_pwm.py (compute_pwm) at the runtime
Tj_Frame_NTC temperature.
- test_mum_alm_animation.py rewritten:
* fio + alm fixtures replace the previous dict-based _ctx
* SETUP / PROCEDURE / ASSERT / TEARDOWN section markers
* test_mode1_fade now wraps its ConfigFrame change in
try/finally so EnableCompensation is restored even on
assertion failure (was leaking state into later tests)
* test_disable_compensation_pwm_wo_comp uses the four-phase
pattern explicitly
Sibling imports work because pytest's default rootdir mode puts the
test file's directory on sys.path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>