Skip to content

Latest commit

 

History

History
304 lines (267 loc) · 17.4 KB

File metadata and controls

304 lines (267 loc) · 17.4 KB

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Note

Two different 0.4.0 entries once existed. master and feat/v0.4.0-foundation forked at v0.3.0 and each described its own work under the same version numbers. Neither was ever tagged — v0.3.0 is the last release — so the 0.4.0 below is the only one, and the feature-surface entries that used to claim 0.4.0/0.5.0 on master are preserved on legacy/v0.5.0-feature-surface. Those features return one milestone at a time, ported onto the measurement envelope rather than merged onto it.

[Unreleased]

Changed

  • master reconciled with the foundation branch. The foundation became the mainline; master's feature surface (LTE RX/EBLER, WLAN AP signaling, WLAN throughput/DAU, BLE signaling PER, coex, IMD planner, bench profiles, test-plan engine) is preserved on legacy/v0.5.0-feature-surface and returns per milestone #41–#46. Tool count 131 → 84 until that work lands.

  • CI now triggers on feat/** / fix/** pushes and no longer filters pull_request by base branch. A pull_request run needs a buildable merge commit, so a branch that conflicts with its base was silently unverified — which is how the items below survived. Test matrix gains windows-latest / 3.11, the platform the instrument is driven from.

  • The SCPI transport, connection registry, safety checks and simulator engine now come from scpi-core. They were written here and were being copy-pasted into the sibling R&S servers, where the copies diverged -- two of the three could silently return a stale reading after a read timeout. Moving the code out rather than copying it again means there is one implementation of the atomicity and desync guarantees for every server to rely on. No behaviour here changes: the same 686 tests pass untouched.

    • The dependency resolves from git, not a path. A path dependency only works when both repos happen to sit side by side, so a single clone of this repo did not install -- precisely the situation someone hits on a bench machine with an instrument in front of them.
    • driver/scpi_socket.py and driver/lock.py are deleted; SCPISocket, Idempotency and TaskReentrantLock are imported from scpi_core and remain importable from rs_cmw500_mcp.driver.
    • exceptions.py is a re-export shim over scpi_core.exceptions. CMW500Error is now an alias of InstrumentError -- the same class, so an error raised inside the shared transport is caught by every existing except CMW500Error. MeasurementError and SignalingError stay here: a signal generator has no measurement or signaling plane.
    • safety/validators.py keeps sanitize_scpi_param and validate_safe_path with their exact wording, as thin adapters over scpi_core.safety. The three servers phrase these refusals differently and each pins its own text, so the rule that fired travels as data and the message stays local. SafetyLimits and the two-plane power validation are untouched.
    • The simulator's six engine modules are deleted. The CMW command surface is now the cmw500 node map shipped with the core (327 nodes, 3 unverified -- identical to the build_nodes() table it replaces), and cmw-simulator is a thin wrapper that preselects it, so every documented invocation and flag works unchanged.

Fixed

  • validate_safe_path rejects null bytes explicitly instead of relying on the OS. POSIX raises; Windows resolves state\x00.json without complaint, so the guard silently passed there.

  • Text I/O pins encoding="utf-8". The platform default is cp1252 on Windows, which broke reading the README and would have corrupted any saved template, state or limit set holding a non-Latin-1 character. Golden snapshots also pin newline="\n" so regenerating on Windows does not rewrite all 84 with CRLF.

  • TaskReentrantLock raises NestedTransactionInSubtaskError instead of deadlocking when a subtask of the lock holder tries to acquire. Before Python 3.12, asyncio.wait_for runs its argument in a new task, so a nested transaction blocked on a lock its own caller could not release. Ownership stays strictly per-task — granting the subtask would let two coroutines interleave a send and a read, the exact failure this transport prevents.

  • The simulator accepts parameterless event commands. SYSTem:GENerator:ALL:OFF and SYSTem:MEASurement:ALL:OFF — the emergency shutoff the bench fixture runs on teardown — drew a spurious -109 Missing parameter because "takes no parameter" was inferred from the leading mnemonic. It is now declared per node (Command.action).

  • Simulator dry-run findings go to a temp dir, not bench-results/, which the runbook tells the next person to commit and trust.

  • .mcp.json no longer hardcodes another machine's path; serverInfo.version reports the package version rather than the MCP SDK's; and the bench runbook no longer names cmw_rf_route_set, a tool that never existed. A test now requires every cmw_* name in the docs to be a registered tool.

  • An idle connection is no longer left generating. The connection pool is now scpi_core.ConnectionRegistry with an idle TTL and an eviction hook, and the hook runs the same safe-state sequence as cmw_system_all_off (SYSTem:GENerator:ALL:OFF, SYSTem:MEASurement:ALL:OFF) before the handle is dropped. The previous pool had no expiry and no hook at all, so a carrier switched on by one tool call radiated until the process died.

    This also makes cmw_disconnect force the safe state on the way out, which is a visible change to what it puts on the wire -- see the tests/golden/cmw_disconnect.txt diff. Disconnecting the control session no longer leaves the instrument transmitting.

[0.4.0] — 2026-07-27

Trustworthiness release. Everything here exists to make one claim true: a number this server returns is traceable to a synchronised acquisition, a decoded reliability verdict, and a known RF path.

Added

  • cmw_measure — arm a measurement, wait for the instrument to report the acquisition finished, then fetch. INITiate returns as soon as the measurement is armed, so separate trigger and fetch calls cannot tell whether they are reading this acquisition or the previous one. Also exposes the five statistics beyond CURRent (AVERage, MAXimum, MINimum, SDEViation, EXTReme) that the instrument has always offered and this server never surfaced.
  • cmw_get_capabilities — which technology families this instrument is licensed and fitted for. A CMW500 is a chassis; WLAN, Bluetooth, LTE measurement and LTE signaling are each separately licensed, and asking an unlicensed application to do something gets -113 Undefined header, which reads like a client bug rather than a missing licence. cmw_connect now reports the available families, and a tool whose family is unlicensed carries a notice. Policy via CMW_CAPABILITY_GATE (off/warn/block, default warn).
  • cmw-simulator — a shipped CMW500 SCPI simulator (console script, stdlib only). Real command-tree parsing, error queue, modelled command latency, fault injection (dropped/surplus responses, mid-conversation close), and settable reliability codes. Binds loopback only unless --allow-remote, and its *IDN? cannot be mistaken for hardware. Makes "evaluate this without a CMW500" possible for the first time.
  • cmw_get_errors — drain and report the instrument's SCPI error queue.
  • cmw_set_external_attenuation / cmw_get_external_attenuation — declare the cable and fixture loss so levels and measurements refer to the DUT. Previously unreachable, which meant every absolute power number was wrong by the path loss.
  • Golden-SCPI snapshots for all tools, a pinned tool manifest, and a config/documentation sync test.

Changed — BREAKING

  • Measurement tools return an envelope. Values move under values; reliability, trustworthy and source are new. Field 0 of every CMW response is the R&S reliability indicator, and it was previously discarded — so a measurement timeout, an overdriven front end or a dropped call arrived as a confident-looking number. An invalid verdict now yields no values at all, because an agent handed both a warning and a number will report the number. degraded verdicts (overdriven/underdriven/uncal) keep their values with a warning attached.
  • CMW_ALLOW_RAW_SCPI now defaults to false, matching what the README, .env.example and this changelog have always said. The code defaulted to true, so a guard users believed was on was off, and raw SCPI bypasses every safety clamp.
  • CMW500Driver.get_errors() returns structured ScpiError objects rather than raw strings.
  • LTE fetch methods return the envelope dict rather than result dataclasses.

Fixed

  • Wrong SCPI, cross-checked against R&S's own instrument drivers. R&S publishes per-technology Python drivers carrying the literal SCPI string and full response structure for ~5,100 commands. Checking ours against theirs found eleven wrong commands and a systematic parsing error:

    • cmw_set_signal_path wrote to a query-only node, so the one tool that claims to select the signal path did nothing — and cmw_get_signal_path then read back the existing setting, which looks like success. The WLAN and Bluetooth routes had the same defect; cmw_set_port emitted a node that does not exist. All now use :SCENario:SALone <connector>,<converter> with a typed RFPath.
    • The BLE PHY was wrong in kind. LE1M/LE2M/LECS2/LECS8 written to MEValuation:BURSt:TYPE conflates two independent settings and targets a third thing entirely. PHY is ISIGnal:LENergy:PHY (LE1M|LE2M|LELR); the S2/S8 coding scheme is a separate node. Getting this wrong silently measures the wrong PHY.
    • LTE cell bandwidth needs a direction (:CELL:BANDwidth:PCC:DL); LTE downlink power is RS EPRE (:DL:PCC:RSEPre:LEVel) and the node used did not exist; WLAN bandwidth is :ISIGnal:BWIDth; GPRF spectrum RBW is :SPECtrum:FSWeep:RBW, and there is no detector node at all.
    • Bluetooth commands addressed nodes that do not exist. BT result trees are per-PHY: MEValuation:MODulation:NMODe:LENergy:LE1M:CURRent?, not a generic MODulation:CURRent?. Power lives under PVTime, not POWer. And BLE has no DEVM at all -- its modulation metric is frequency deviation (ΔF1/ΔF2); DEVM is a BR/EDR measurement -- so the old BLE results were labelled with a quantity the instrument was not measuring.
    • Measurement field layouts were mis-indexed nearly everywhere. For most result views index 1 is Out_Of_Tolerance — a percentage — not the first measured value. Every WLAN EVM number this server produced was wrong (EVM is at indices 15–17 of a 22-field response). LTE ACLR was read as minus/plus pairs when it is ordered by frequency with the carrier's own power in the middle. LTE SEM has no pass flag or margin at all; the occupied bandwidth was being reported as margin_db. WLAN spectrum flatness returns per-trace margins where a positive margin means the limit was violated -- the opposite of the usual convention -- and we had a boolean read from the wrong slot, so it was both mis-indexed and inverted.
  • Measurements were never synchronised. *OPC? answers "has the command queue drained", which is true immediately after INITiate. Completion is now polled on the application's own STATe node, which has eight values (ACTive, ADJusted, INValid, OFF, PENDing, QUEued, RDY, RUN) — PENDing and QUEued are exactly what a fast poll lands on. Continuous repetition never reaches a finished state and is refused with an actionable message rather than timing out and fetching anyway.

  • Crossed responses. send() held the transport lock and read_response() did not, so two concurrent tool calls on one pooled connection could each receive the other's answer — silently, with plausible values.

  • Desynchronised connections. A read timeout does not end the exchange; the instrument may answer later, permanently offsetting the stream. A timeout now poisons the connection until resync() proves it is both aligned and empty.

  • Silent command rejection. A SCPI write returns nothing, so a malformed command produced {"status": "ok"}. The error queue is now drained per transaction (one query per group, not per command).

  • Single-plane safety clamps. With external attenuation declared, the CMW interprets levels at the DUT plane — so a request for −10 dBm through 20 dB of declared loss meant +10 dBm at the connector, and an over-declared attenuation could expose the analyser front end. Both planes are now checked.

  • lte_meas_fetch_all silently omitted frequency error.

  • Measurement fetches no longer swallow transport errors into a zero-valued result, which was indistinguishable from a genuine reading of zero.

  • mypy runs without || true in CI; the 45 errors it had been hiding are fixed.

  • mcp dependency pinned below 2.0.

  • Version drift between __init__.py (0.2.0) and pyproject.toml.

Known issues

Two items still need hardware or a datasheet, tracked as scpi-unverified:

  • Instrument-plane power limits for the specific front-end variant (#29). Defaults are deliberately conservative placeholders.
  • Option-code to family mapping (#35) is inferred from R&S naming convention, which is why the capability gate defaults to warn. Also confirm whether SYSTem:BASE:OPTion:LIST? exists on this firmware — it appears in no vendor driver, and *OPT? is used in preference. Also worth knowing: frequency-dependent correction tables are a CMWS feature, not a CMW500 one. On this instrument de-embedding is a single scalar per direction per application, so a frequency-varying cable cannot be corrected instrument-side across a sweep.

[0.3.0] — 2026-05-13

Changed

  • License: Apache-2.0 → AGPL-3.0-or-later. Aligns with the eng-mcp-suite toolkit-wide AGPL move. R&S hardware and proprietary client software are independent of this wrapper.

[0.2.0] - 2025-02-21

Added

  • WLAN non-signaling support -- 11 new MCP tools for 802.11a/b/g/n/ac/ax testing
    • TX power, EVM, spectrum flatness, and frequency error measurements
    • Configurable standard, bandwidth (20/40/80/160 MHz), frequency, and expected power
    • Multi-instance measurement support (MEAS1-MEASn)
  • Bluetooth/BLE non-signaling support -- 11 new MCP tools
    • Classic Bluetooth: DH1/DH3/DH5/DM1/DM3/DM5 packet types
    • BLE: 1M, 2M, Coded S2, Coded S8 PHY modes
    • TX power, modulation (DEVM), and frequency offset/drift measurements
  • Advanced GPRF tools -- 6 new tools for trigger configuration, power filters, baseband mode, RF port selection, user margin, and system-wide off
  • Measurement templates -- 4 new pre-built configs
    • ble_tx -- BLE TX measurement (1M/2M/Coded S2 presets)
    • ble_rx -- BLE RX sensitivity via GPRF generator
    • bt_classic_tx -- Bluetooth Classic TX (DH1/DH5 presets)
    • wlan_rx -- WLAN RX sensitivity via GPRF generator (Wi-Fi 6 presets)
  • Tool registry architecture -- modular tool system replacing monolithic dispatch
    • Each technology in its own module with auto-registration
    • Centralized error handling with proper isError propagation
  • CI/CD pipeline -- GitHub Actions with Python 3.10/3.11/3.12 matrix
    • Lint (ruff check), format (ruff format), test (pytest + coverage), typecheck (mypy)
  • Data models -- typed enums, config dataclasses, and result dataclasses for WLAN and Bluetooth
  • SCPI input sanitization -- protection against injection attacks
  • Raw SCPI guard -- CMW_ALLOW_RAW_SCPI setting (default: disabled)
  • Asyncio locks -- connection pool, template, and measurement locks for concurrent safety
  • Integration test marker -- @pytest.mark.integration for hardware-dependent tests

Changed

  • Tool count increased from 51 to 79 tools
  • Test count increased from 248 to 373 tests
  • Source code grew from ~3,500 to 7,087 lines
  • lte_meas_configure() -- de-stubbed with real SCPI (stat count, repetition)
  • lte_configure_bearer() -- de-stubbed with real SCPI (APN, IP version)
  • meas_configure_spectrum() -- de-stubbed with real SCPI (center freq, span, RBW, detector)
  • wlan_tx template -- now uses native WLAN SCPI subsystem instead of GPRF workaround
  • Version bumped to 0.2.0

Architecture

  • Decomposed tools.py (1,621 lines) into tools/ package (10 modules)
  • Added tools/registry.py with ToolRegistry singleton pattern
  • Added tools/shared.py with connection pooling, locks, and shared helpers
  • Each technology module (connection, gprf, lte, wlan, bluetooth, scpi) self-registers tools at import time

[0.1.0] - 2025-01-15

Added

  • Initial release
  • GPRF generator control (frequency, level, ARB waveforms)
  • GPRF analyzer measurements (power, spectrum)
  • LTE signaling mode (cell configuration, connection management)
  • LTE TX measurements (power, EVM, ACLR, SEM)
  • Safety limits system
  • State save/restore
  • Pass/fail limit checking
  • 3 measurement templates (LTE TX, GPRF power, non-signaling RX)