fix: add STRT invalid detction, ach server passes config for get events,

This commit is contained in:
2026-04-14 17:08:27 -04:00
parent 4f4c1a8f64
commit 171dc2551c
5 changed files with 104 additions and 14 deletions
+67 -4
View File
@@ -448,7 +448,7 @@ class MiniMateClient:
proto.confirm_erase_all()
log.info("delete_all_events: erase confirmed — device memory cleared")
def get_events(self, full_waveform: bool = False, debug: bool = False, stop_after_index: Optional[int] = None, skip_waveform_for_keys: Optional[set] = None) -> list[Event]:
def get_events(self, full_waveform: bool = False, debug: bool = False, stop_after_index: Optional[int] = None, skip_waveform_for_keys: Optional[set] = None, compliance_config: Optional["ComplianceConfig"] = None) -> list[Event]:
"""
Download all stored events from the device using the confirmed
1E → 0A → 0C → 5A → 1F event-iterator protocol.
@@ -479,6 +479,7 @@ class MiniMateClient:
ProtocolError: on unrecoverable communication failure.
"""
proto = self._require_proto()
_compliance_config = compliance_config # passed through to _decode_a5_waveform
log.info("get_events: requesting first event (SUB 1E)")
try:
@@ -608,7 +609,7 @@ class MiniMateClient:
if a5_frames:
a5_ok = True
_decode_a5_metadata_into(a5_frames, ev)
_decode_a5_waveform(a5_frames, ev)
_decode_a5_waveform(a5_frames, ev, compliance_config=_compliance_config)
log.info(
"get_events: 5A decoded %d sample-sets",
len((ev.raw_samples or {}).get("Tran", [])),
@@ -1311,6 +1312,7 @@ def _decode_a5_metadata_into(frames_data: list[bytes], event: Event) -> None:
def _decode_a5_waveform(
frames_data: list[bytes],
event: Event,
compliance_config: Optional["ComplianceConfig"] = None,
) -> None:
"""
Decode the raw 4-channel ADC waveform from a complete set of SUB 5A
@@ -1397,15 +1399,19 @@ def _decode_a5_waveform(
# 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 so the viewer renders.
# 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_invalid = False
if pretrig_samples >= total_samples:
log.warning(
"_decode_a5_waveform: pretrig_samples=%d >= total_samples=%d"
"STRT layout suspect. Raw strt[0:21]: %s "
"Clamping pretrig to 0 for rendering.",
"Will attempt to derive pretrig from compliance config after decode.",
pretrig_samples, total_samples, strt[0:21].hex(' '),
)
pretrig_samples = 0
total_samples = 0 # also invalid; will be filled from decoded count below
_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):
@@ -1534,6 +1540,63 @@ def _decode_a5_waveform(
"Mic": mic,
}
# ── Correct STRT-invalid pretrig/total from decoded count + compliance config ──
# When the STRT layout was suspect (pretrig >= total_samples), both values were
# zeroed out earlier. Now that we know the actual decoded sample count, use it
# together with the compliance config record_time to derive a meaningful pretrig.
#
# Formula:
# post_trig = record_time (s) × sample_rate (sps)
# pretrig = decoded_samples post_trig
#
# This gives the pre-trigger window length, which correctly places t=0 in the
# waveform. If compliance_config is not available, leave pretrig=0 (viewer shows
# full waveform starting at t=0 — better than a crash or garbage).
n_decoded = len(tran)
if _strt_invalid:
if compliance_config is not None:
cc_sr = compliance_config.sample_rate or 1024
cc_rt = compliance_config.record_time
# Pre-trigger time is a separate device setting from Record Time.
# Blastware documentation confirms the compliance monitoring standard
# is 0.25 seconds pre-trigger ("the default Time Scale is -0.25 to 1
# second — this negative number accounts for the pre-trigger set for
# compliance monitoring").
# The pre-trigger field has not yet been located in the raw compliance
# config bytes; 0.25 s is used as the best-known default until it is
# decoded. TODO: locate pretrig_time in ComplianceConfig bytes.
_PRETRIG_SECONDS_DEFAULT = 0.25
derived_pretrig = int(round(_PRETRIG_SECONDS_DEFAULT * cc_sr))
if cc_rt and cc_rt > 0:
post_trig_samples = int(round(cc_rt * cc_sr))
# Clip total to pretrig + post_trig so the viewer doesn't show the
# zero-padded tail frames the device appends beyond the record window.
event.total_samples = derived_pretrig + post_trig_samples
event.pretrig_samples = derived_pretrig
event.rectime_seconds = int(round(cc_rt))
log.info(
"_decode_a5_waveform: STRT was invalid — using pretrig=%d "
"(%.2fs default) from compliance standard; record_time=%.1fs "
"sr=%d sps decoded=%d samples display_total=%d",
derived_pretrig, _PRETRIG_SECONDS_DEFAULT,
cc_rt, cc_sr, n_decoded, event.total_samples,
)
else:
event.total_samples = n_decoded
event.pretrig_samples = derived_pretrig
log.warning(
"_decode_a5_waveform: STRT invalid, compliance config missing "
"record_time — pretrig=%d (%.2fs default), total=decoded %d",
derived_pretrig, _PRETRIG_SECONDS_DEFAULT, n_decoded,
)
else:
event.total_samples = n_decoded
log.warning(
"_decode_a5_waveform: STRT invalid, no compliance config available "
"— pretrig left as 0, total set to decoded count %d",
n_decoded,
)
def _extract_record_type(data: bytes) -> Optional[str]:
"""