diff --git a/bridges/ach_server.py b/bridges/ach_server.py index 6b03d2f..cc3be25 100644 --- a/bridges/ach_server.py +++ b/bridges/ach_server.py @@ -627,10 +627,13 @@ def _build_waveform_blob(e: Event) -> Optional[str]: pv = e.peak_values peak_values = None if pv: + # Key names must match sfm/server.py _serialise_peak_values() so the + # waveform viewer (which reads tran_in_s / vert_in_s / long_in_s) works + # identically in both live-device mode and DB mode. peak_values = { - "tran": pv.tran, - "vert": pv.vert, - "long": pv.long, + "tran_in_s": pv.tran, + "vert_in_s": pv.vert, + "long_in_s": pv.long, "micl_psi": pv.micl, "peak_vector_sum": pv.peak_vector_sum, } diff --git a/minimateplus/client.py b/minimateplus/client.py index 29a1a63..6973214 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -1366,11 +1366,13 @@ def _decode_a5_waveform( cumulative global byte offset; at each new frame, the starting alignment within the T,V,L,M cycle is (global_offset % 8). - Confirmed sizes from 4-2-26 (A5[0..8], skipping A5[7] metadata frame - and A5[9] terminator): + Confirmed sizes from 4-2-26 blast capture (A5[0..8], metadata at A5[7]): Frame 0: 934B Frame 1: 963B Frame 2: 946B Frame 3: 960B Frame 4: 952B Frame 5: 946B Frame 6: 941B Frame 8: 992B — none are multiples of 8. + NOTE: Metadata frame position is variable — at fi==7 for blast events + (4-2-26 capture) and fi==6 for desk-thump events (2026-04-14 confirmed). + The dynamic b"Project:" detection handles both cases. ── Modifies event in-place. ───────────────────────────────────────────────── """ @@ -1454,9 +1456,14 @@ def _decode_a5_waveform( wave[:24].hex(' '), ) - # Frame 7 carries event-time metadata strings ("Project:", "Client:", …) - # and no waveform ADC data. - elif fi == 7: + # Metadata frame: contains "Project:", "Client:", etc. strings. + # Originally assumed to be always fi==7 (A5[7] in 4-2-26 blast capture), + # but confirmed variable position — it appears at whatever chunk index the + # device places it (observed at fi=6 for desk-thump events 2026-04-14). + # Skip ANY frame whose raw bytes contain b"Project:" — this is the same + # anchor used by stop_after_metadata in read_bulk_waveform_stream. + elif b"Project:" in w: + log.info("_decode_a5_waveform: fi=%d skipped (metadata frame)", fi) continue # Terminator frames have page_key=0x0000 and are excluded upstream diff --git a/sfm/server.py b/sfm/server.py index 8e15364..9021369 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -1028,7 +1028,7 @@ def db_event_waveform(event_id: str) -> dict: waveform viewer can consume either source without modification: - total_samples, pretrig_samples, rectime_seconds, samples_decoded - sample_rate - - peak_values (tran, vert, long, micl_psi, peak_vector_sum) + - peak_values (tran_in_s, vert_in_s, long_in_s, micl_psi, peak_vector_sum) - channels ({"Tran": [...], "Vert": [...], "Long": [...], "Mic": [...]}) Returns 404 if the event doesn't exist, 422 if the event exists but has no diff --git a/sfm/waveform_viewer.html b/sfm/waveform_viewer.html index 2208f12..f0da66b 100644 --- a/sfm/waveform_viewer.html +++ b/sfm/waveform_viewer.html @@ -507,11 +507,13 @@ const micPeakPsi = data.peak_values?.micl_psi ?? null; const DBL_REF_PSI = 2.9e-9; // 20 µPa in psi - // 0C record peak values (device-computed, authoritative) per channel + // 0C record peak values (device-computed, authoritative) per channel. + // Keys: live-device endpoint uses tran_in_s/vert_in_s/long_in_s; + // DB blobs created before 2026-04-14 used tran/vert/long — fall back for compat. const peakValues0C = { - Tran: data.peak_values?.tran_in_s ?? null, - Vert: data.peak_values?.vert_in_s ?? null, - Long: data.peak_values?.long_in_s ?? null, + Tran: data.peak_values?.tran_in_s ?? data.peak_values?.tran ?? null, + Vert: data.peak_values?.vert_in_s ?? data.peak_values?.vert ?? null, + Long: data.peak_values?.long_in_s ?? data.peak_values?.long ?? null, }; for (const [ch, color] of Object.entries(CHANNEL_COLORS)) {