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 from __future__ import annotations
import datetime import datetime
import logging
import struct import struct
from pathlib import Path from pathlib import Path
from typing import Optional, Union from typing import Optional, Union
@@ -158,6 +159,8 @@ from typing import Optional, Union
from .framing import S3Frame from .framing import S3Frame
from .models import Event, MonitorLogEntry, Timestamp from .models import Event, MonitorLogEntry, Timestamp
log = logging.getLogger(__name__)
# ── File header constants ───────────────────────────────────────────────────── # ── File header constants ─────────────────────────────────────────────────────
# Common 16-byte prefix shared by waveform files and MLG (confirmed from binary inspection). # 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 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 ─────────────────────────────────────────────────────────── # ── Waveform file writer ───────────────────────────────────────────────────────────
def write_blastware_file( def write_blastware_file(
@@ -631,12 +679,29 @@ def write_blastware_file(
all_bytes = bytearray() all_bytes = bytearray()
for fi, frame in enumerate(body_frames): 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: if fi == 0:
# Probe frame: always process regardless of classification.
# It holds the STRT record; probe_skip positions us past it.
skip = probe_skip skip = probe_skip
elif fi == 1: elif ftype == "waveform":
skip = 13 # 7-byte frame.data prefix + 6-byte first-chunk header skip = 13 if fi == 1 else 12
else: 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)) all_bytes.extend(_frame_body_bytes(frame, skip))
# Terminator contributes its content, which ends with the 26-byte footer. # Terminator contributes its content, which ends with the 26-byte footer.