From 9ae968b1085ebd675f24f103d5994997cc47e81b Mon Sep 17 00:00:00 2001 From: Brian Harrison Date: Tue, 14 Apr 2026 17:46:38 -0400 Subject: [PATCH] fix: peak0c scope bug and strt cross check fix --- minimateplus/client.py | 46 +++++++++++++++++++++++++++++----------- sfm/waveform_viewer.html | 6 +++++- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/minimateplus/client.py b/minimateplus/client.py index d3e596d..e7637fe 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -1397,29 +1397,51 @@ def _decode_a5_waveform( total_samples = struct.unpack_from(">H", strt, 14)[0] pretrig_samples = struct.unpack_from(">H", strt, 16)[0] - # Sanity check: pretrig must be less than total_samples. - # If not, the STRT layout is suspect (DLE-stuffing shift, different record variant, etc.). - # Log the raw bytes for diagnosis and clamp pretrig to 0 for now — will try to derive - # a better value from the compliance config after the full waveform is decoded. + # ── STRT sanity checks ─────────────────────────────────────────────────── + # Check 1: pretrig must be strictly less than total_samples. + # Check 2 (cross-check): if compliance_config is available, the STRT-derived + # post-trigger record time must be within 2× of the configured record_time. + # A 3-second record at 1024 sps = 3072 post-trig samples; if STRT gives + # e.g. 21 s, something is wrong with the byte offsets. + # + # Both failures trigger the same fallback: derive pretrig from the compliance + # monitoring standard (0.25 s) and total from compliance record_time. _strt_invalid = False + _sample_rate_default = 1024 + if pretrig_samples >= total_samples: log.warning( - "_decode_a5_waveform: pretrig_samples=%d >= total_samples=%d — " - "STRT layout suspect. Raw strt[0:21]: %s " - "Will attempt to derive pretrig from compliance config after decode.", + "_decode_a5_waveform: STRT check1 FAIL — pretrig_samples=%d >= " + "total_samples=%d. Raw strt[0:21]: %s " + "Will derive pretrig from compliance config.", pretrig_samples, total_samples, strt[0:21].hex(' '), ) pretrig_samples = 0 - total_samples = 0 # also invalid; will be filled from decoded count below + total_samples = 0 _strt_invalid = True + elif compliance_config is not None and compliance_config.record_time: + cc_rt = compliance_config.record_time + cc_sr = compliance_config.sample_rate or _sample_rate_default + strt_post_trig = (total_samples - pretrig_samples) / cc_sr + # Allow up to 2× tolerance to account for zero-padding beyond the window. + # Anything more than that is a layout error, not zero-padding. + if strt_post_trig > cc_rt * 2.0 or strt_post_trig < cc_rt * 0.5: + log.warning( + "_decode_a5_waveform: STRT check2 FAIL — STRT-derived rectime " + "%.1fs is implausible vs compliance record_time=%.1fs (ratio=%.1f×). " + "Raw strt[0:21]: %s Falling back to compliance-config values.", + strt_post_trig, cc_rt, strt_post_trig / cc_rt, + strt[0:21].hex(' '), + ) + pretrig_samples = 0 + total_samples = 0 + _strt_invalid = True # strt[18] is a record-mode/type byte, NOT rectime in seconds. # Confirmed from analysis of 4-9-26 ACH capture (15 distinct events): # flags=0xFFFE (single-shot) → strt[18]=0x46 ('F') for all events regardless of duration # flags=0xFFFD (continuous) → strt[18]=0x0E for all events regardless of duration # The actual post-trigger record time must be derived from total_samples and pretrig_samples. - # Default sample rate of 1024 is used here; the server overrides with compliance config sr. - _sample_rate_default = 1024 rectime_seconds = int(round( max(0, total_samples - pretrig_samples) / _sample_rate_default )) @@ -1428,9 +1450,9 @@ def _decode_a5_waveform( event.pretrig_samples = pretrig_samples event.rectime_seconds = rectime_seconds - log.debug( + log.info( "_decode_a5_waveform: STRT total_samples=%d pretrig=%d " - "strt[18]=0x%02X (mode byte, not seconds) computed_rectime=%ds " + "strt[18]=0x%02X (mode byte) computed_rectime=%ds " "raw strt[0:21]: %s", total_samples, pretrig_samples, strt[18], rectime_seconds, strt[0:21].hex(' '), diff --git a/sfm/waveform_viewer.html b/sfm/waveform_viewer.html index c719793..2208f12 100644 --- a/sfm/waveform_viewer.html +++ b/sfm/waveform_viewer.html @@ -527,6 +527,10 @@ ? samples.slice(0, displayCount) : samples; + // peak0C declared here (function scope) so it is visible in the Chart.js + // config block below (which lives outside the if(isGeo) block). + let peak0C = null; + if (isGeo) { // Geo channels: counts × (range / 32767) → in/s // Scale factor for the waveform shape (may need calibration per unit) @@ -535,7 +539,7 @@ // Use the device-computed 0C record peak for the label (authoritative). // The raw-sample-computed peak can be inflated by frame-boundary artifacts. - const peak0C = peakValues0C[ch]; + peak0C = peakValues0C[ch]; const peakIns = (peak0C !== null && peak0C !== undefined) ? peak0C : Math.max(...plotSamples.map(Math.abs));