feat(protocol): fully decode Blastware filename encoding and update related documentation

This commit is contained in:
2026-04-22 23:43:31 -04:00
parent c47e3a3af0
commit 6dcca4da79
3 changed files with 76 additions and 60 deletions
+44 -37
View File
@@ -10,13 +10,20 @@ internal binary structure (same type tag 00 12 03 00, same STRT record layout).
Blastware identifies the file type by extension, not by a magic marker.
EXTENSION ENCODING — V10.72 firmware FULLY CONFIRMED 2026-04-22:
Format: AB0T where AB = 2-char base-36 of (total_seconds % 1296),
0 = literal zero, T = W (Full Waveform) or H (Full Histogram).
total_seconds = (event_local_time 1985-01-01T00:00:00_local).
Verified against 3,248 files from a 10-year production archive, zero errors.
Old firmware (S338, 3-char extensions ending in '0'): encoding unknown.
The extension is NOT recording mode — confirmed false 2026-04-21.
Direct / manual download: AB0 (3-char, no type character)
Call-home (ACH) download: AB0W or AB0H (4-char, W=waveform H=histogram)
AB = 2-char base-36 of (total_seconds % 1296), where
total_seconds = (event_local_time 1985-01-01T00:00:00_local).
0 = always literal digit zero.
Verified against 3,248 call-home files from a 10-year production archive.
The 10-year archive contains only ACH files (all end in W or H).
Manual Blastware downloads produce 3-char AB0 extensions — same encoding
but without the trailing type character.
Old firmware (S338, 3-char extensions): encoding unknown / same as manual?
Micromate Series 4 uses a different scheme (literal datetime in filename).
─── File structure overview ─────────────────────────────────────────────────────
@@ -120,14 +127,14 @@ MLG CRC:
─── Public API ──────────────────────────────────────────────────────────────────
blastware_filename(event, serial)
Return a Blastware-style filename for an event (e.g. "M529LIY6.N00").
Extension encoding is UNKNOWN — always returns .N00 as a placeholder.
Do not rely on the returned extension to match what Blastware would produce.
Return the correct Blastware filename for an event (e.g. "M529LIY6.CE0W").
Full AB0T extension encoding confirmed 2026-04-22 against 3,248 archive files.
Extension matches what Blastware itself would generate for the same event.
write_n00(event, a5_frames, path)
Create a .N00 or .9T0 waveform file from an Event and the full A5 frame
list (include_terminator=True required when calling read_bulk_waveform_stream).
Identical binary format for both extensions — caller picks the path/ext.
write_blastware_file(event, a5_frames, path)
Create a Blastware waveform file from an Event and the full A5 frame list.
All waveform extensions share the same binary format — the extension is set
by blastware_filename() based on the event timestamp and type.
read_n00(path) → Event
Parse a .N00 file into an Event object with waveform data populated.
@@ -161,8 +168,8 @@ _FILE_HEADER_PREFIX = bytes.fromhex("1000018000004973") + b"tantel\x00\x07\x2c"
# Simpler construction:
_FILE_HEADER_PREFIX = b"\x10\x00\x01\x80\x00\x00Instantel\x00\x07\x2c" # 17 bytes
# N00 type tag (4 bytes after common prefix)
_N00_TYPE_TAG = b"\x00\x12\x03\x00" # confirmed from M529LIY6.N00 offset 0x11..0x14
# Waveform file type tag (4 bytes after common prefix) — shared by ALL waveform extensions
_N00_TYPE_TAG = b"\x00\x12\x03\x00" # confirmed from M529LIY6.N00 — same tag for .CE0W, .VM0H, etc.
# MLG type tag (4 bytes after common prefix)
_MLG_TYPE_TAG = b"\x22\x01\x0e\xa0" # confirmed from BE11529.MLG offset 0x11..0x14
@@ -406,14 +413,13 @@ def _make_stem(ts_local: datetime.datetime) -> str:
return s
def blastware_filename(event: Event, serial: str) -> str:
def blastware_filename(event: Event, serial: str, ach: bool = False) -> str:
"""
Return a Blastware-style waveform filename for an event.
Return the correct Blastware filename for an event.
FULLY CONFIRMED 2026-04-22 — verified against 3,248 files from a 10-year
production archive (zero errors on MiniMate Plus / V10.72 firmware files).
CONFIRMED 2026-04-22 — verified against 3,248 files from a 10-year archive.
Filename format: <prefix_letter><serial3><stem><AB>0<T>
Filename format: <prefix_letter><serial3><stem><AB>0[T]
where:
prefix_letter = chr(ord('B') + floor(serial_numeric / 1000))
@@ -431,15 +437,15 @@ def blastware_filename(event: Event, serial: str) -> str:
0 = always literal digit zero
T = 'W' (Full Waveform) or 'H' (Full Histogram)
T = 'W' or 'H' — ONLY appended for call-home (ACH) downloads (ach=True).
Manual / direct downloads produce a 3-char extension (AB0) with no type char.
Call-home downloads produce a 4-char extension (AB0W or AB0H).
total_seconds = (event_local_time 1985-01-01T00:00:00_local) in seconds
NOTE: Old firmware units (S338, 3-char extensions ending in '0') use a
different unknown extension encoding. This function returns the correct
extension only for V10.72 / new-firmware MiniMate Plus units. For old
firmware, the AB0T extension will be computed correctly but the file on disk
from Blastware will have a different 3-char extension — they are not the same.
The 10-year production archive contains only call-home files (all end in W or H).
Manual Blastware downloads produce 3-char extensions — the same AB0 prefix but
without the trailing type character.
Micromate Series 4 uses a completely different naming scheme (literal datetime
in filename); this function does not apply to Micromate units.
@@ -447,9 +453,11 @@ def blastware_filename(event: Event, serial: str) -> str:
Args:
event: Event object with timestamp set.
serial: Device serial number string (e.g. "BE11529").
ach: If True, append W/H type character (call-home style).
If False (default), omit type character (direct download style).
Returns:
Filename string (e.g. "M529LIY6.CE0H").
Filename string, e.g. "M529LIY6.CE0" (direct) or "M529LIY6.CE0H" (ACH).
"""
# ── Serial prefix ──────────────────────────────────────────────────────────
serial_digits = "".join(c for c in serial if c.isdigit())
@@ -472,7 +480,7 @@ def blastware_filename(event: Event, serial: str) -> str:
)
delta_sec = int((ts_local - _INSTANTEL_EPOCH).total_seconds())
stem = _make_stem(ts_local)
ab_val = delta_sec % _STEM_UNIT_SEC # 01295
ab_val = delta_sec % _STEM_UNIT_SEC
ab_str = _STEM_CHARS[ab_val // 36] + _STEM_CHARS[ab_val % 36]
except (ValueError, TypeError, AttributeError):
stem = "0000"
@@ -481,17 +489,16 @@ def blastware_filename(event: Event, serial: str) -> str:
stem = "0000"
ab_str = "00"
# ── Event type character ──────────────────────────────────────────────────
# H = Full Histogram, W = Full Waveform
# record_type is set from the 0A header byte: 0x46=triggered, 0x2C=monitor log
# Histogram vs waveform distinction comes from the compliance recording_mode.
# Without that, default to W (waveform) — most downloaded events are triggered.
if getattr(event, 'recording_mode', None) in (3, 4): # Histogram / Hist+Cont
type_char = 'H'
# ── Type character (ACH only) ─────────────────────────────────────────────
if ach:
if getattr(event, 'recording_mode', None) in (3, 4): # Histogram / Hist+Cont
type_char = 'H'
else:
type_char = 'W'
ext = f".{ab_str}0{type_char}"
else:
type_char = 'W'
ext = f".{ab_str}0"
ext = f".{ab_str}0{type_char}"
return prefix + stem + ext