From dfb974d6586d738b4cb90091019374e64e0b6cfd Mon Sep 17 00:00:00 2001 From: Brian Harrison Date: Fri, 3 Apr 2026 13:54:54 -0400 Subject: [PATCH] feat: add endpoint to download full raw ADC waveform for a single event --- sfm/server.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/sfm/server.py b/sfm/server.py index eafdefd..2503602 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -385,6 +385,81 @@ def device_event( return _serialise_event(matching[0]) +@app.get("/device/event/{index}/waveform") +def device_event_waveform( + index: int, + port: Optional[str] = Query(None, description="Serial port (e.g. COM5)"), + baud: int = Query(38400, description="Serial baud rate"), + host: Optional[str] = Query(None, description="TCP host — modem IP or ACH relay"), + tcp_port: int = Query(DEFAULT_TCP_PORT, description=f"TCP port (default {DEFAULT_TCP_PORT})"), +) -> dict: + """ + Download the full raw ADC waveform for a single event (0-based index). + + Supply either *port* (serial) or *host* (TCP/modem). + + Performs: POLL startup → get_events() (to locate the 4-byte waveform key) → + download_waveform() (full SUB 5A stream, stop_after_metadata=False). + + Response includes: + - **total_samples**: expected sample-sets from the STRT record + - **pretrig_samples**: pre-trigger sample count + - **rectime_seconds**: record duration + - **samples_decoded**: actual sample-sets decoded (may be less than total_samples + if the device is not storing all frames yet, or the capture was partial) + - **sample_rate**: samples per second (from compliance config) + - **channels**: dict of channel name → list of signed int16 ADC counts + (keys: "Tran", "Vert", "Long", "Mic") + """ + log.info("GET /device/event/%d/waveform port=%s host=%s", index, port, host) + + try: + def _do(): + with _build_client(port, baud, host, tcp_port) as client: + info = client.connect() + events = client.get_events() + matching = [ev for ev in events if ev.index == index] + if not matching: + return None, None, info + ev = matching[0] + client.download_waveform(ev) + return ev, events, info + ev, events, info = _run_with_retry(_do, is_tcp=_is_tcp(host)) + except HTTPException: + raise + except ProtocolError as exc: + raise HTTPException(status_code=502, detail=f"Protocol error: {exc}") from exc + except OSError as exc: + raise HTTPException(status_code=502, detail=f"Connection error: {exc}") from exc + except Exception as exc: + raise HTTPException(status_code=500, detail=f"Device error: {exc}") from exc + + if ev is None: + raise HTTPException( + status_code=404, + detail=f"Event index {index} not found on device", + ) + + raw = getattr(ev, "raw_samples", None) or {} + samples_decoded = len(raw.get("Tran", [])) + + # Resolve sample_rate from compliance config if not on the event itself + sample_rate = ev.sample_rate + if sample_rate is None and info.compliance_config: + sample_rate = info.compliance_config.sample_rate + + return { + "index": ev.index, + "timestamp": _serialise_timestamp(ev.timestamp), + "total_samples": ev.total_samples, + "pretrig_samples": ev.pretrig_samples, + "rectime_seconds": ev.rectime_seconds, + "samples_decoded": samples_decoded, + "sample_rate": sample_rate, + "channels": raw, + } + + # ── Entry point ──────────────────────────────────────────────────────────────── if __name__ == "__main__":