diff --git a/README.md b/README.md index aa522b5..bd2be05 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,29 @@ evaluates traces against CISPR/FCC limit lines and produces reproducible reports | Platforms | Windows 10/11, Linux (Ubuntu 22.04+), macOS 13+ | | Docs | [Contributing](CONTRIBUTING.md) · [Requirements](docs/requirements.md) · [Architecture](docs/architecture.md) · [Plugin API](docs/plugin-api.md) · [Tasklist](tasklist.md) | +## Why PeakEmi + +* **Runs where you work.** One source tree, one feature set, on Windows, Linux and macOS. +* **Not tied to one instrument vendor.** The buses are the standard ones and the driver is + chosen by scoring the instrument's `*IDN?` reply. A model nobody has written a driver for + can be added as a single Python file rather than as a patch to the application. Supported + models declare their own frequency range, point count and command dialect, so a sweep the + instrument cannot make is refused with a reason rather than sent and rejected. +* **Nothing is hidden.** Every SCPI exchange is visible in the console and in the + `--verbose` transcript. Limit lines and correction tables are documented CSV/JSON you can + write by hand, and sessions are versioned JSON — your measurements stay diffable, + scriptable and readable without PeakEmi. +* **Nothing to plug in to start.** The simulated analyzer ships with the application, so the + full loop, including the report, can be tried before any hardware is on the bench. +* **Results keep their provenance.** Every verified point carries the settings that produced + it, and the session file is the record of the run rather than a screenshot of it. +* **No licence server, no dongle, no seat count.** GPL-3.0-or-later; build it yourself and + run it offline. + +The instrument list is short and the scope is deliberately narrow — pre-compliance +measurement, not lab automation. Turntables, masts and LISNs are switched through the +[commands sent around a run](#what-works-today), not driven by PeakEmi. + ## Screenshots A completed run against the built-in simulated analyzer: a CISPR 32 class B radiated scan with @@ -40,6 +63,11 @@ bandwidth, dwell time, limit, margin and timestamp — coloured by verdict. ![Phase 2 result table](docs/images/results-table.png) +The log dock keeps the run narrative and the raw SCPI transcript side by side, so what the +application asked the instrument is always inspectable. + +![Application log and SCPI console](docs/images/log-console.png) + The screenshots are generated by driving the real main window against the simulated analyzer, so they cannot drift from the application: @@ -61,9 +89,15 @@ QT_QPA_PLATFORM=offscreen ./build/debug/bin/peakemi_screenshots docs/images selection, bounded and opt-in LAN sweep, live USB hotplug detection, serial port enumeration, an optional VISA path, and a raw SCPI console. * **Supported analyzers** — Siglent SSA3021X/3032X/3075X and SVA1015X/1032X/1075X, Rigol - DSA705/710 and DSA815/832/875. Each model carries its own frequency range, point count - and command dialect, so a sweep the instrument cannot make is refused with a reason - instead of being sent and rejected. + DSA705/710 and DSA815/832/875, each with its own frequency range, point count and + command dialect. +* **Python driver plugins** — in a build configured with `PEAKEMI_WITH_PYTHON=ON`, a + driver can be a single Python file loaded into the embedded interpreter, and the + measurement engine cannot tell it from one written in C++. A plugin is + imported only after you approve it, and the approval is recorded as a hash of the file's + contents, so an edited plugin is untrusted again until you say otherwise. See the + [plugin API](docs/plugin-api.md) and the worked + [example driver](plugins/drivers/example_sweeper.py). * **Commands around a run** — a run can send operator-supplied commands when it starts and when it ends, which is how a LISN, a relay box or a mast that speaks SCPI is switched. The closing commands are sent whether the run finished, was aborted or failed. PeakEmi @@ -77,7 +111,8 @@ QT_QPA_PLATFORM=offscreen ./build/debug/bin/peakemi_screenshots docs/images a report template that can be edited in the app, shared as a file and kept as the default. -Not yet implemented: the embedded Python plugin bridge — see [tasklist.md](tasklist.md). +What is left is packaging: a notarised macOS disk image, signed Windows artifacts and a +Windows installer — see [tasklist.md](tasklist.md). ### Instrument buses diff --git a/docs/requirements.md b/docs/requirements.md index c9f5603..8197834 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -363,20 +363,3 @@ The core feature is a two-phase scan: | Quasi-peak dwell makes runs long | Poor UX | Bounded peak counts, pause/resume, progress with time estimate | | GPL-3.0 vs proprietary in-house drivers | Adoption friction | Plugin API headers dual-licensed (CON-3) | -**Open questions** -1. Which instruments constitute the v1 supported set (proposal: Siglent SSA3000X/SVA1000X, Rigol - DSA800/DSA700, plus the simulated driver)? -2. ~~Plot backend: Qt Graphs 2D via a `QQuickWidget` host, or a QPainter-based widget?~~ - **Settled: QPainter.** The plot decimates a trace to a min/max envelope per pixel - column, so redraw cost follows the widget width rather than the trace length, and a - 40,001-point trace stays interactive without a QML runtime in the process. The backend - stays behind `IPlotBackend` (ADR-5), so this can be revisited without touching - application code. -3. ~~Should conducted-emission LISN control (relay switching of line/neutral) be in v1?~~ - **Settled: no.** PeakEmi drives no relays. Instead a run may carry user-supplied - commands sent at its start and at its end (FR-RUN-9), which is enough to switch a LISN, - a relay box or an antenna mast that already speaks SCPI, without PeakEmi modelling any - of them. -4. ~~Minimum Qt version: hold at 6.5 LTS or require 6.8+?~~ **Settled: 6.8 minimum.** CI - builds against it, so code that needs something newer fails there rather than in a - user's build. diff --git a/tasklist.md b/tasklist.md index 6654583..a76a696 100644 --- a/tasklist.md +++ b/tasklist.md @@ -1,55 +1,8 @@ # PeakEmi tasklist -## Done - -* **Build system** — CMakeLists (modern C++23, Qt 6), `CMakePresets.json`, - `.clang-format`, `.clang-tidy`, `.editorconfig`. -* **Core domain (`peakemi_core`)** — strong units, traces, capabilities, limit lines and - the built-in CISPR/FCC catalogue, correction chain, CISPR band table, peak detection, - run configuration with segment planning, session model and versioned JSON container, - atomic file writes, categorised + rotating logging. -* **Ports** — `ITransport` and `AbstractAnalyzerDriver`, error values via `std::expected`, - `CancelToken`. -* **HAL (`peakemi_hal`)** — SCPI parsing helpers, TCP and serial transports, driver - registry with scored `*IDN?` matching, LAN sweep and serial port enumeration. -* **Drivers (`peakemi_drivers`)** — deterministic `SimulatedDriver`, and a SCPI driver - with per-model profiles for the Siglent SSA3000X and SVA1000X and the Rigol DSA700 and - DSA800 families: each model's frequency range, point count, bandwidths and dialect, - narrowed from the family defaults as soon as the instrument identifies itself. -* **Engine** — two-phase scan → detect → verify state machine on a worker thread, with - pause/resume/abort, bounded retries, multi-pass max-hold and autosave. -* **Transports (M5)** — VXI-11 over ONC-RPC including the portmapper lookup, USBTMC over - libusb with USB hotplug discovery, and an optional VISA path resolved at run time. The - optional buses compile in or out without touching the rest of the tree. -* **Reporting (`peakemi_reporting`)** — CSV and JSON export of traces and results, PDF - report, and a report template (company, address, logo, free text) that is editable in - the UI, importable, exportable and stored as the default for new sessions. -* **User interface (`peakemi_ui`)** — main window with instrument, configuration, results - and log/SCPI-console docks, decimating spectrum plot behind `IPlotBackend`, run - controller owning the acquisition thread, session and export actions. -* **Tests** — 11 Qt Test executables: unit tests for every non-UI algorithm, component - tests against a scripted transport, UI smoke tests and a headless integration test of - the full two-phase run. -* **CI** — GitHub Actions matrix (Windows/MSVC, Ubuntu/GCC, macOS/Clang) building with - warnings as errors and running the suite headless, plus a format/clang-tidy job. -* **Release** — tag-triggered workflow that verifies the tag sits on `main` and matches - `project(VERSION ...)`, then builds and publishes an AppImage, a macOS `.dmg` and a - Windows zip. -* **Python bridge (M7)** — embedded CPython through pybind11, the `peakemi_plugin` - bindings, discovery with a SHA-256 trust store that requires explicit per-file - approval, a driver proxy turning plugin exceptions into errors with their traceback, - the plugin manager UI, a worked example driver and the versioned - [plugin API specification](docs/plugin-api.md). -* **Documentation** — README screenshots generated headlessly from the real main window - by `tools/screenshots` (`PEAKEMI_BUILD_TOOLS=ON`). - ## Next * **M8** — a notarised macOS release (the disk image, its icon, the bundle layout and ad-hoc signing are done; a Developer ID identity and notarisation credentials are not something the build can supply for itself), code signing for the Windows artifacts, and a Windows installer. -All three open questions are settled and recorded in -[requirements.md §6](docs/requirements.md#6-assumptions-risks--open-questions): the -QPainter plot backend stays, PeakEmi drives no relays and sends operator-supplied commands -around a run instead, and the minimum Qt version is 6.8.