v0.12.6 #10

Merged
serversdown merged 43 commits from seismo-lab-new into main 2026-05-04 13:22:56 -04:00
Showing only changes of commit 43c8158493 - Show all commits
+68 -3
View File
@@ -151,6 +151,7 @@ MLG CRC:
from __future__ import annotations
import datetime
import logging
import struct
from pathlib import Path
from typing import Optional, Union
@@ -158,6 +159,8 @@ from typing import Optional, Union
from .framing import S3Frame
from .models import Event, MonitorLogEntry, Timestamp
log = logging.getLogger(__name__)
# ── File header constants ─────────────────────────────────────────────────────
# Common 16-byte prefix shared by waveform files and MLG (confirmed from binary inspection).
@@ -502,6 +505,51 @@ def blastware_filename(event: Event, serial: str, ach: bool = False) -> str:
return prefix + stem + ext
# ── A5 frame classifier ───────────────────────────────────────────────────────────
# ASCII markers that identify a compliance-config / metadata frame.
# These strings appear in the A5 bulk stream as part of the device's
# compliance setup payload. They should NEVER appear in raw ADC waveform
# frames (which are binary-heavy, < 20 % printable ASCII).
_METADATA_FRAME_MARKERS = (
b"Project:",
b"Client:",
b"Standard Recording Setup",
b"Extended Notes",
b"User Name:",
b"Seis Loc:",
)
def classify_frame(frame: S3Frame) -> str:
"""
Classify an A5 bulk waveform stream frame by its content.
Returns one of:
"terminator" — page_key == 0x0000
"probe_or_strt" — data contains b"STRT" (the initial probe response)
"metadata" — data contains ASCII compliance-config markers
"waveform" — predominantly binary (< 20 % printable ASCII)
"unknown" — none of the above criteria matched
Used by write_blastware_file() to filter non-waveform frames out of
the reconstructed body so that metadata blocks (Project:, Client:, …)
and spurious STRT records do not corrupt the output file.
"""
if frame.page_key == 0x0000:
return "terminator"
data = bytes(frame.data)
if b"STRT" in data:
return "probe_or_strt"
if any(m in data for m in _METADATA_FRAME_MARKERS):
return "metadata"
if len(data) > 0:
printable = sum(1 for b in data if 32 <= b < 127)
if printable / len(data) < 0.20:
return "waveform"
return "unknown"
# ── Waveform file writer ───────────────────────────────────────────────────────────
def write_blastware_file(
@@ -631,12 +679,29 @@ def write_blastware_file(
all_bytes = bytearray()
for fi, frame in enumerate(body_frames):
ftype = classify_frame(frame)
print(f"Frame {fi}: type={ftype}, page_key={frame.page_key:04x}, len={len(frame.data)}")
if fi == 0:
# Probe frame: always process regardless of classification.
# It holds the STRT record; probe_skip positions us past it.
skip = probe_skip
elif fi == 1:
skip = 13 # 7-byte frame.data prefix + 6-byte first-chunk header
elif ftype == "waveform":
skip = 13 if fi == 1 else 12
else:
skip = 12 # 7-byte frame.data prefix + 5-byte chunk header
# Skip metadata, probe_or_strt, and unknown frames.
if b"STRT" in bytes(frame.data):
log.warning(
"write_blastware_file: frame %d (%s) contains STRT — skipping",
fi, ftype,
)
else:
log.debug(
"write_blastware_file: frame %d classified as %s — skipping",
fi, ftype,
)
continue
all_bytes.extend(_frame_body_bytes(frame, skip))
# Terminator contributes its content, which ends with the 26-byte footer.