feat(protocol): fully decode Blastware filename encoding and update related documentation
This commit is contained in:
@@ -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 # 0–1295
|
||||
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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user