37 KiB
Instantel MiniMate Plus — Blastware RS-232 Protocol Reference
"The Rosetta Stone"
Reverse-engineered via RS-232 serial bridge sniffing between Blastware software and an Instantel MiniMate Plus seismograph (S/N: BE18189).
All findings derived from live packet capture. No vendor documentation was used.
Certainty Ratings: ✅ CONFIRMED | 🔶 INFERRED | ❓ SPECULATIVE Certainty ratings apply only to protocol semantics, not to capture tooling behavior.
Changelog
| Date | Section | Change |
|---|---|---|
| 2026-02-26 | Initial | Document created from first hex dump analysis |
| 2026-02-26 | §2 Frame Structure | CORRECTED: Frame uses DLE-STX (0x10 0x02) and DLE-ETX (0x10 0x03), not bare 0x02/0x03. 0x41 confirmed as ACK not STX. DLE stuffing rule added. |
| 2026-02-26 | §8 Timestamp | UPDATED: Year 0x07CB = 1995 confirmed as MiniMate hardware default date when RTC battery is disconnected. Not an encoding error. Confidence upgraded from ❓ to 🔶. |
| 2026-02-26 | §10 DLE Stuffing | UPGRADED: Section upgraded from ❓ SPECULATIVE to ✅ CONFIRMED. Full stuffing rules and parser state machine documented. |
| 2026-02-26 | §11 Checksum | UPDATED: Frame builder and parser rewritten to handle DLE framing and byte stuffing correctly. |
| 2026-02-26 | §14 Open Questions | DLE question removed (resolved). Timestamp year question removed (resolved). |
| 2026-02-26 | §7.2 Serial Number Response | CORRECTED: Trailing bytes are 0x79 0x11 only (2 bytes, not 3). 0x20 was misidentified as a trailing byte — it is the frame checksum. |
| 2026-02-26 | §7.2 Serial Number Response | UPDATED: Two-unit comparison confirms 0x11 = firmware minor version (S337.17 → 0x11 = 17). Byte 0 is unit-specific, derivation unknown. |
| 2026-02-26 | §15 Binary Log Format | NEW: .bin logger format strips DLE from ETX (0x10 0x03 → 0x03). Not raw wire bytes. |
| 2026-02-26 | §5.1 Request Commands | ADDED: Three new read commands confirmed: SUB 09, 1A, 2E. |
| 2026-02-26 | §5.3 Write Commands | NEW SECTION: Full write command family documented from compliance setup capture. SUBs 68–83. |
| 2026-02-26 | §7.5 Full Waveform Record | UPDATED: Project strings field layout fully mapped from write payload diff. Client field confirmed at byte +230 in SUB 71 frame. |
| 2026-02-26 | §14 Open Questions | Write commands question resolved. Session 152427 had swapped port labels — superseded by sessions 184518 and 185019. |
| 2026-02-26 | Global | CORRECTED: Firmware version S338.17 → S337.17 everywhere, including hex encoding at config offset 0x34. |
| 2026-02-26 | §3 Payload Structure | DOWNGRADED: ADDR field certainty 🔶 INFERRED → ❓ SPECULATIVE. Added note that bytes 1–2 purpose is unconfirmed. |
| 2026-02-26 | §5.2 Response SUBs | STRENGTHENED: 0xFF - SUB rule wording clarified — high confidence, no counterexample, not yet formally proven. |
| 2026-02-26 | §15 → Appendix A | RENAMED: Binary log format section moved to Appendix A with explicit note that it describes tooling behavior, not protocol. |
| 2026-02-26 | Header | ADDED: Certainty legend clarification — ratings apply to protocol semantics only, not tooling behavior. |
| 2026-02-26 | §7.6 Channel Config Float Layout | NEW SECTION: Trigger level confirmed as IEEE 754 BE float in in/s. Alarm level identified as adjacent float = 1.0 in/s. Unit string "in./s" embedded inline. 0x082A removed as trigger level candidate. |
| 2026-03-01 | §7.6 Channel Config Float Layout | UPGRADED: Alarm level offset fully confirmed via controlled capture (alarm 1.0→2.0, trigger 0.5→0.6). Complete per-channel layout documented. Three-channel repetition confirmed (Tran, Vert, Long). Certainty upgraded to ✅ CONFIRMED. |
| 2026-03-01 | §7.7 .set File Format |
NEW SECTION: Blastware save-to-disk format decoded. Little-endian binary struct matching wire protocol payload. Full per-channel block layout mapped. Record time confirmed as uint32 at +16. MicL unit string confirmed as "psi\0". 0x082A mystery noted — not obviously record time, needs one more capture to resolve. |
1. Physical Layer
| Parameter | Value | Certainty |
|---|---|---|
| Interface | RS-232 serial | ✅ CONFIRMED |
| Baud rate | 38400 | ✅ CONFIRMED (from bridge log header) |
| Data bits | 8 | ✅ CONFIRMED (standard for this baud/era) |
| Parity | None | 🔶 INFERRED (no parity errors observed) |
| Stop bits | 1 | 🔶 INFERRED (standard assumption) |
| Flow control | None (no RTS/CTS activity) | 🔶 INFERRED |
2. Frame Structure
⚠️ 2026-02-26 — CORRECTED: Previous version incorrectly identified
0x41as STX and0x02/0x03as bare frame delimiters. The protocol uses proper DLE framing. See below.
Every message follows this structure:
[ACK] [DLE+STX] [PAYLOAD...] [CHECKSUM] [DLE+ETX]
0x41 0x10 0x02 N bytes 1 byte 0x10 0x03
Special Byte Definitions
| Token | Raw Bytes | Meaning | Certainty |
|---|---|---|---|
| ACK | 0x41 (ASCII 'A') |
Acknowledgment / ready token. Standalone single byte. Sent before every frame by both sides. | ✅ CONFIRMED |
| DLE | 0x10 |
Data Link Escape. Prefixes the next byte to give it special meaning. | ✅ CONFIRMED — 2026-02-26 |
| STX | 0x10 0x02 |
DLE+STX = Start of frame (two-byte sequence) | ✅ CONFIRMED — 2026-02-26 |
| ETX | 0x10 0x03 |
DLE+ETX = End of frame (two-byte sequence) | ✅ CONFIRMED — 2026-02-26 |
| CHECKSUM | 1 byte | 8-bit sum of de-stuffed payload bytes, modulo 256. Sits between payload and DLE+ETX. | ✅ CONFIRMED |
DLE Byte Stuffing Rule
✅ CONFIRMED — 2026-02-26
Any 0x10 byte appearing naturally in the payload data is escaped by doubling it: 0x10 → 0x10 0x10. This prevents the parser from confusing real data with frame control sequences.
- Transmit: Replace every
0x10in payload with0x10 0x10 - Receive: Replace every
0x10 0x10in the frame body with a single0x10
| Sequence on wire | Meaning |
|---|---|
0x10 0x02 |
Frame START — only valid at beginning |
0x10 0x03 |
Frame END |
0x10 0x10 |
Escaped literal 0x10 byte in payload data |
Any other 0x10 0xXX |
Protocol error / undefined |
Frame Parser Notes
- The
0x41ACK always arrives in a separateread()call before the frame body due to RS-232 inter-byte timing at 38400 baud. This is normal. - Your parser must be stateful and buffered — read byte by byte, accumulate between DLE+STX and DLE+ETX. Never assume one
read()= one frame. - Checksum is computed on the de-stuffed payload, not the raw wire bytes.
- The ACK and DLE+STX are not included in the checksum.
Checksum Verification Example
Raw frame on wire (with ACK and DLE framing):
41 10 02 | 10 10 00 5B 00 00 00 00 00 00 00 00 00 00 00 00 00 | 6B | 10 03
^ACK^^STX^ ^---------- stuffed payload (0x10→0x10 0x10) ------^ ^chk^ ^ETX^
After de-stuffing (0x10 0x10 → 0x10):
De-stuffed: 10 00 5B 00 00 00 00 00 00 00 00 00 00 00 00 00
Checksum: 10+00+5B+00+... = 0x6B ✅
3. Payload Structure
The payload (bytes between DLE+STX and CHECKSUM, after de-stuffing) has consistent internal structure:
[CMD] [DLE] [ADDR] [FLAGS] [SUB_CMD] [OFFSET_HI] [OFFSET_LO] [PARAMS × N]
xx 0x10 0x10 0x00 xx xx xx
| Field | Position | Notes | Certainty |
|---|---|---|---|
| CMD | byte 0 | Command or response code | ✅ CONFIRMED |
| DLE | byte 1 | Always 0x10. Part of address/routing scheme. On wire this is stuffed as 0x10 0x10. |
✅ CONFIRMED — 2026-02-26 |
| ADDR | byte 2 | Always observed as 0x10. Also stuffed on wire. Purpose unknown — may not be an address. |
❓ SPECULATIVE |
| FLAGS | byte 3 | Usually 0x00. Non-zero values seen in event-keyed requests. |
🔶 INFERRED |
| SUB_CMD | byte 4 | The actual operation being requested. | ✅ CONFIRMED |
| OFFSET_HI | byte 5 | High byte of data offset for paged reads. | ✅ CONFIRMED |
| OFFSET_LO | byte 6 | Low byte of data offset. | ✅ CONFIRMED |
❓ NOTE on bytes 1–2: After de-stuffing, bytes 1 and 2 are both
0x10in every observed frame across all captured sessions and both units. Their semantic meaning is not yet confirmed. No capture has shown either field vary across units, commands, or directions. They may represent routing, bus ID, or fixed header constants — or the field boundaries assumed here may be wrong entirely.
🔶 NOTE: Because bytes 1 and 2 are both
0x10, they appear on the wire as four consecutive0x10bytes (0x10 0x10 0x10 0x10). This is normal — both are stuffed. Do not mistake them for DLE+STX or DLE+ETX.
4. Communication Pattern
4.1 ACK Handshake (Every Transaction)
Side A → 0x41 (ACK: "ready / received")
Side A → 10 02 [payload] [chk] 10 03 (frame)
Side B → 0x41 (ACK)
Side B → 10 02 [payload] [chk] 10 03 (response frame)
4.2 Two-Step Paged Read Pattern
All data reads use a two-step length-prefixed pattern. It is not optional.
Step 1 — Request with offset=0 ("how much data is there?"):
BW → 0x41
BW → 10 02 [CMD] 10 10 00 [SUB] 00 00 [00 00 ...] [chk] 10 03
Step 2 — Device replies with total data length:
S3 → 0x41
S3 → 10 02 [RSP] 00 10 10 [SUB] 00 00 00 00 00 00 [LEN_HI] [LEN_LO] [chk] 10 03
Step 3 — Re-request using LEN as offset ("now send the data"):
BW → 0x41
BW → 10 02 [CMD] 10 10 00 [SUB] 00 00 [LEN_HI] [LEN_LO] [00 ...] [chk] 10 03
Step 4 — Device sends actual data payload:
S3 → 0x41
S3 → 10 02 [RSP] 00 10 10 [SUB] 00 00 [LEN_HI] [LEN_LO] [DATA...] [chk] 10 03
5. Command Reference Table
5.1 Request Commands (Blastware → S3)
| SUB Byte | Name | Description | Certainty |
|---|---|---|---|
5B |
POLL / KEEPALIVE | Sent continuously (~every 80ms). Requests device identity/status. | ✅ CONFIRMED |
15 |
SERIAL NUMBER REQUEST | Requests device serial number. | ✅ CONFIRMED |
01 |
FULL CONFIG READ | Requests complete device configuration block (~0x98 bytes). Firmware, model, serial, channel config, scaling factors. | ✅ CONFIRMED |
08 |
EVENT INDEX READ | Requests the event record index (0x58 bytes). Event count and record pointers. | ✅ CONFIRMED |
06 |
CHANNEL CONFIG READ | Requests channel configuration block (0x24 bytes). | ✅ CONFIRMED |
1C |
TRIGGER CONFIG READ | Requests trigger settings block (0x2C bytes). | ✅ CONFIRMED |
1E |
EVENT HEADER READ | Reads event header by index. Contains timestamp and sample rate. | ✅ CONFIRMED |
0A |
WAVEFORM HEADER READ | Reads waveform header keyed by timestamp (0x30 bytes/page). | ✅ CONFIRMED |
0C |
FULL WAVEFORM RECORD | Downloads complete waveform record (0xD2 bytes/page, 2 pages). Project strings, PPV floats, channel labels. | ✅ CONFIRMED |
5A |
BULK WAVEFORM STREAM | Initiates bulk download of raw ADC sample data, keyed by timestamp. Large multi-page transfer. | ✅ CONFIRMED |
24 |
WAVEFORM PAGE A? | Paged waveform read, possibly channel group A. | 🔶 INFERRED |
25 |
WAVEFORM PAGE B? | Paged waveform read, possibly channel group B. | 🔶 INFERRED |
1F |
EVENT ADVANCE / CLOSE | Sent after waveform download completes. Likely advances internal record pointer. | 🔶 INFERRED |
09 |
UNKNOWN READ A | Read command, response (F6) returns 0xCA (202) bytes. Purpose unknown. |
🔶 INFERRED |
1A |
CHANNEL SCALING / COMPLIANCE CONFIG READ | Read command, response (E5) returns large block containing IEEE 754 floats including trigger level, alarm level, max range, and unit strings. Contains 0x082A — purpose unknown, possibly alarm threshold or record config. |
🔶 INFERRED |
2E |
UNKNOWN READ B | Read command, response (D1) returns 0x1A (26) bytes. Purpose unknown. |
🔶 INFERRED |
All requests use CMD byte 0x02. All responses use CMD byte 0x10 0x02 (which, after de-stuffing, is just the DLE+CMD combination — see §3).
5.2 Response SUB Bytes (S3 → Blastware)
🔶 INFERRED pattern: Response SUB =
0xFF - Request SUB. Verified on all observed pairs to date — no counterexample has been observed across read commands, write commands, or either unit. Confidence is high but not formally proven across the full command space.
| Request SUB | Response SUB | Certainty |
|---|---|---|
5B |
A4 |
✅ CONFIRMED |
15 |
EA |
✅ CONFIRMED |
01 |
FE |
✅ CONFIRMED |
08 |
F7 |
✅ CONFIRMED |
06 |
F9 |
✅ CONFIRMED |
1C |
E3 |
✅ CONFIRMED |
1E |
E1 |
✅ CONFIRMED |
0A |
F5 |
✅ CONFIRMED |
0C |
F3 |
✅ CONFIRMED |
5A |
A5 |
✅ CONFIRMED |
1F |
E0 |
🔶 INFERRED |
09 |
F6 |
✅ CONFIRMED |
1A |
E5 |
✅ CONFIRMED |
2E |
D1 |
✅ CONFIRMED |
5.3 Write Commands (Blastware → Device)
✅ CONFIRMED — 2026-02-26 from compliance setup capture (session
185019).
Write commands are initiated by Blastware (BW->S3) and use SUB bytes in the 0x60–0x83 range. The device acknowledges each write with a short response frame containing no data payload.
Pattern: Write SUB = Read SUB + 0x60 (e.g. 0x08 EVENT INDEX READ → 0x68 EVENT INDEX WRITE).
| SUB | Name | Description | Response SUB | Certainty |
|---|---|---|---|---|
68 |
EVENT INDEX WRITE | Writes event index block (mirrors SUB 08 read). Contains event count and timestamps. |
97 |
✅ CONFIRMED |
69 |
WAVEFORM DATA WRITE | Writes large waveform/channel data block (0xCA bytes, mirrors SUB 09). |
96 |
✅ CONFIRMED |
71 |
COMPLIANCE / PROJECT STRINGS WRITE | Writes compliance config and all project string fields. Contains setup name, project, client, operator, sensor location, and extended notes. Also contains channel scaling floats and 0x082A threshold value. |
8E |
✅ CONFIRMED |
72 |
WRITE CONFIRM A | Short frame, no data. Likely commit/confirm step after 71. |
8D |
✅ CONFIRMED |
73 |
WRITE CONFIRM B | Short frame, no data. | 8C |
✅ CONFIRMED |
74 |
WRITE CONFIRM C | Short frame, no data. | 8B |
✅ CONFIRMED |
82 |
TRIGGER CONFIG WRITE | Writes trigger config block (0x1C bytes, mirrors SUB 1C read). |
7D |
✅ CONFIRMED |
83 |
TRIGGER WRITE CONFIRM | Short frame, no data. Likely commit step after 82. |
7C |
✅ CONFIRMED |
Write response SUB pairs follow the same 0xFF - request rule:
| Write SUB | Response SUB |
|---|---|
68 |
97 |
69 |
96 |
71 |
8E |
72 |
8D |
73 |
8C |
74 |
8B |
82 |
7D |
83 |
7C |
6. Session Startup Sequence
1. Device powers on / resets
2. S3 → "Operating System" (raw ASCII, no DLE framing — UART boot string)
3. BW → 0x41 + POLL frame (SUB 5B)
4. S3 → 0x41 + POLL RESPONSE (SUB A4, reports data length = 0x30)
5. BW → 0x41 + POLL frame (SUB 5B, offset = 0x30)
6. S3 → 0x41 + POLL RESPONSE with data: "Instantel" + "MiniMate Plus"
7. [Poll loop repeats 3–5× during initialization]
8. BW → SUB 06 → channel config read
9. BW → SUB 15 → serial number
10. BW → SUB 01 → full config block
11. BW → SUB 08 → event index
12. BW → SUB 1E → first event header
13. BW → SUB 0A → waveform header (timestamp-keyed)
14. BW → SUB 0C → full waveform record download (2 pages)
15. BW → SUB 1F → advance / close event
16. [Repeat steps 12–15 for each stored event]
17. BW → SUB 5A → bulk raw waveform stream
18. Poll loop resumes (SUB 5B keepalive every ~80ms)
7. Known Data Payloads
7.1 Poll Response (SUB A4) — Device Identity Block
Two-step read. Data payload = 0x30 bytes.
Offset 0x00: 0x08 — string length prefix
Offset 0x01: "Instantel" — manufacturer (null-padded to ~20 bytes)
Offset 0x15: "MiniMate Plus" — model name (null-padded to ~20 bytes)
Raw payload (after de-stuffing):
00 00 00 08 49 6E 73 74 61 6E 74 65 6C 00 00 00 00 00 00 00 00 00 00 00 00 00
4D 69 6E 69 4D 61 74 65 20 50 6C 75 73 00 00 00 00 00 00 00 00 00
7.2 Serial Number Response (SUB EA)
Data payload = 0x0A bytes:
"BE18189\x00" — 7 ASCII bytes + null terminator (8 bytes)
79 11 — 2 trailing bytes
| Trailing Byte | Value (Unit 1) | Value (Unit 2) | Meaning | Certainty |
|---|---|---|---|---|
trail[0] |
0x79 |
0x70 |
Unit-specific — factory calibration ID or HW stamp? | ❓ SPECULATIVE |
trail[1] |
0x11 |
0x11 |
Firmware minor version — 0x11 = 17 = S337.**17** |
✅ CONFIRMED — 2026-02-26 |
Two-unit comparison data:
Unit 1: serial="BE18189" trail=79 11 firmware=S337.17
Unit 2: serial="BE11529" trail=70 11 firmware=S337.17
✅ 2026-02-26 — CORRECTED: Previously documented as
79 11 20(3 bytes).0x20is the frame checksum, not payload data. Actual data block is exactly 10 bytes (0x0A).
✅ 2026-02-26 — CONFIRMED:
trail[1]= firmware minor version. Both units share firmwareS337.17→ minor = 17 =0x11. Will change if firmware differs between units.
❓ Still unknown:
trail[0]is unit-specific. Does not derive from serial string via sum, XOR, or modulo. Possibly written at factory calibration. Needs a third unit or write-command capture to determine.
7.3 Full Config Response (SUB FE) — 0x98 bytes
| Offset | Raw | Decoded | Certainty |
|---|---|---|---|
| 0x00 | 42 45 31 38 31 38 39 00 |
"BE18189\x00" — Serial number |
✅ CONFIRMED |
| 0x08 | 79 11 |
Unknown — possibly HW revision or calibration stamp | ❓ SPECULATIVE |
| 0x0A | 00 01 |
Unknown flags | ❓ SPECULATIVE |
| 0x14 | 3F 80 00 00 |
IEEE 754 float = 1.0 (Tran scale factor) | 🔶 INFERRED |
| 0x18 | 41 00 00 00 |
IEEE 754 float = 8.0 (unknown — MicL range?) | 🔶 INFERRED |
| 0x1C | 3F 80 00 00 ×6 |
IEEE 754 float = 1.0 ×6 (remaining channel scales) | 🔶 INFERRED |
| 0x34 | 53 33 33 37 2E 31 37 00 |
"S337.17\x00" — Firmware version |
✅ CONFIRMED |
| 0x3C | 31 30 2E 37 32 00 |
"10.72\x00" — DSP / secondary firmware version |
✅ CONFIRMED |
| 0x44 | 49 6E 73 74 61 6E 74 65 6C... |
"Instantel" — Manufacturer (repeated) |
✅ CONFIRMED |
| 0x6D | 4D 69 6E 69 4D 61 74 65 20 50 6C 75 73 |
"MiniMate Plus" — Model name |
✅ CONFIRMED |
7.4 Event Index Response (SUB F7) — 0x58 bytes
Offset 0x00: 00 58 09 — Total index size or record count ❓
Offset 0x03: 00 00 00 01 — Possibly stored event count = 1 ❓
Offset 0x07: 01 07 CB 00 06 1E — Timestamp of event 1 (see §8)
Offset 0x0D: 01 07 CB 00 14 00 — Timestamp of event 2 (see §8)
Offset 0x13: 00 00 00 17 3B — Unknown ❓
Offset 0x50: 10 02 FF DC — Sub-block pointer or data segment header ❓
7.5 Full Waveform Record (SUB F3) — 0xD2 bytes × 2 pages
✅ 2026-02-26 — UPDATED: Project strings field layout confirmed by diffing compliance setup write payload (SUB
71). Client field change"Hello Claude"→"Claude test2"isolated exact byte position.
Project strings field layout (confirmed from SUB 71 write frame, offset +230 from frame start):
Offset Field label (null-padded, ~16 bytes) Field value (null-padded, ~32 bytes)
------ ------------------------------------ ------------------------------------
+0x00 "Standard Recording Setup" ← setup name (no label)
+0x28 "Project:" project description string
+0x50 "Client:" client name string ← confirmed at +230
+0x78 "User Name:" operator name string
+0xA0 "Seis Loc:" sensor location string
+0xC8 "Extended Notes" notes string
🔶 Offsets are approximate — exact byte boundaries need one more targeted capture with a known-length string change to pin down padding rules.
Confirmed ASCII strings extracted from payload:
"Project:"
"I-70 at SR 51-75978 - Loc 1 - 4256 SR51 " ← project description
"BE18189" ← serial number
"Histogram" ← record type
"Standard Recording Setup" ← setup name
"Client:"
"Golden Triangle" ← client name
"User Name:"
"Terra-Mechanics Inc. - B. Harrison" ← operator
"Seis Loc:"
"Location #1 - 4256 SR 51 - Intec" ← sensor location
"Extended Notes"
"Tran" ← Transverse channel
"Vert" ← Vertical channel
"Long" ← Longitudinal channel
"MicL" ← Microphone / air overpressure
7.6 Channel Config Float Layout (SUB E5 / SUB 71)
✅ CONFIRMED — 2026-03-01 from controlled captures (sessions
193237and151147). Trigger changed0.500 → 0.200, then0.200 → 0.600. Alarm changed1.0 → 2.0. All positions confirmed.
The SUB 1A read response (E5) and SUB 71 write block contain per-channel threshold and scaling values packed as IEEE 754 big-endian floats, with inline unit strings. This layout repeats once per geophone channel (Tran, Vert, Long — 3×):
[00 00] [max_range float] [00 00] [trigger float] ["in.\0"] [alarm float] ["/s\0\0"] [00 01] [chan_label...]
40 C6 97 FD 3F 19 99 9A 69 6E 2E 40 00 00 00 2F 73 00 00
= 6.206 = 0.600 in/s "in." = 2.000 in/s "/s"
| Field | Example bytes | Decoded | Certainty |
|---|---|---|---|
[00 00] |
00 00 |
Separator / padding | 🔶 INFERRED |
| Max range float | 40 C6 97 FD |
6.206 — full-scale range in in/s | 🔶 INFERRED |
[00 00] |
00 00 |
Separator / padding | 🔶 INFERRED |
| Trigger level | 3F 19 99 9A |
0.600 in/s — IEEE 754 BE float | ✅ CONFIRMED |
| Unit string | 69 6E 2E 00 |
"in.\0" |
✅ CONFIRMED |
| Alarm level | 40 00 00 00 |
2.000 in/s — IEEE 754 BE float | ✅ CONFIRMED |
| Unit string | 2F 73 00 00 |
"/s\0\0" |
✅ CONFIRMED |
[00 01] |
00 01 |
Unknown flag / separator | 🔶 INFERRED |
| Channel label | e.g. 56 65 72 74 |
"Vert" — identifies which channel |
✅ CONFIRMED |
State transitions observed across captures:
| Capture | Trigger | Alarm | Notes |
|---|---|---|---|
193237 (read) |
3F000000 = 0.500 |
3F800000 = 1.000 |
Device state before any change |
193237 (write 1) |
3E4CCCCD = 0.200 |
3F800000 = 1.000 |
Trigger changed only |
151147 (write 1) |
3E4CCCCD = 0.200 |
40000000 = 2.000 |
Alarm changed, trigger carried over |
151147 (write 2) |
3F19999A = 0.600 |
40000000 = 2.000 |
Trigger changed, alarm carried over |
Values are stored natively in imperial units (in/s) — unit strings "in." and "/s" embedded inline confirm this regardless of display locale.
❓
0x082A(= 2090) — appears in the same block but did not change when trigger or alarm level was adjusted. Possibly record time, sample count, or a different threshold. Needs a targeted capture changing a known integer field to identify.
7.7 Blastware .set File Format
🔶 INFERRED — 2026-03-01 from
Standard_Recording_Setup.setcross-referenced against known wire payloads.
Blastware's "save setup to disk" feature produces a binary .set file that is structurally identical to the wire protocol payload, but with all multi-byte values in little-endian byte order (Windows-native) rather than the big-endian order used on the wire. No DLE framing, no checksums — raw struct dump.
File layout (2522 bytes observed):
0x0000 Header / metadata block (~40 bytes) — partially decoded
0x002A "Standard Recording Setup.set\0" — setup filename, null-padded
0x0078 Project strings block — same layout as SUB 71 wire payload
"Project:\0" + value, "Client:\0" + value, "User Name:\0" + value,
"Seis Loc:\0" + value, "Extended Notes\0" + value
0x06A0 Channel records block — one record per channel (geo×3 + mic×1 + duplicates)
0x0820 Device info block — serial number, firmware, model strings
0x08C0 Event index / timestamp block
0x0910 Histogram / reporting config
0x09D0 Trailer (10 bytes)
Per-channel record layout (little-endian, ~46 bytes per channel):
offset size type value (Tran example) meaning
+00 2 uint16 0x0001 channel type (1=geophone, 0=mic)
+02 4 char[4] "Tran" channel label
+06 2 uint16 0x0000 padding
+08 2 uint16 0x0001 unknown
+0A 2 uint16 0x0050 = 80 unknown (sensitivity? gain?)
+0C 2 uint16 0x000F = 15 unknown
+0E 2 uint16 0x0028 = 40 unknown
+10 2 uint16 0x0015 = 21 unknown
+12 4 bytes 03 02 04 01 flags (recording mode etc.)
+16 4 uint32 0x00000003 record time in seconds ✅ CONFIRMED
+1A 4 float32 6.2061 max range (in/s for geo, psi for mic)
+1E 2 00 00 padding
+20 4 float32 0.6000 trigger level ✅ CONFIRMED
+24 4 char[4] "in.\0" / "psi\0" unit string (geo vs mic)
+28 4 float32 2.0000 alarm level ✅ CONFIRMED
+2C 4 char[4] "/s\0\0" / varies unit string 2
MicL channel differences:
channel_type= 0 (vs 1 for geophones)- trigger = 0.009, alarm = 0.021 (in psi)
- unit string =
"psi\0"instead of"in.\0"— confirms MicL units are psi ✅
Endianness summary:
| Context | Byte order | Example (0.6 in/s trigger) |
|---|---|---|
.set file |
Little-endian | 9A 99 19 3F |
| Wire protocol (SUB 71 / E5) | Big-endian | 3F 19 99 9A |
❓
0x082A— still unidentified. Record time in the.setfile =0x00000003(3 sec), which would be00 00 00 03on wire — not0x082A. The original sessions had record time = 2, which would be00 00 00 02.0x082A= 2090 doesn't match any obvious record time encoding. May correspond to one of the unknown uint16 fields at +0A through +10. A capture changing sample rate or histogram interval would help isolate it.
7.5 Full Waveform Record (SUB F3) — 0xD2 bytes × 2 pages
Peak values as IEEE 754 big-endian floats (restored section header):
Tran: 3D BB 45 7A = 0.0916 (in/s — unit config dependent)
Vert: 3D B9 56 E1 = 0.0907
Long: 3D 75 C2 7C = 0.0605
MicL: 39 BE 18 B8 = 0.000145 (PSI or dB linear — ❓ units unconfirmed)
Peak values — event 2:
Tran: 3D 56 CB B9 = 0.0521
Vert: 3C F5 C2 7C = 0.0300
Long: 3C F5 C2 7C = 0.0300
MicL: 39 64 1D AA = 0.0000875
7.6 Bulk Waveform Stream (SUB A5) — Raw ADC Sample Records
Each repeating record (🔶 INFERRED structure):
[CH_ID] [S0_HI] [S0_LO] [S1_HI] [S1_LO] ... [S8_HI] [S8_LO] [00 00] [01] [PEAK × 3 bytes]
01 00 0A 00 0B 43 xx xx
CH_ID— Channel identifier.01consistently observed. Full mapping unknown. 🔶 INFERRED- 9× signed 16-bit big-endian ADC samples. Noise floor ≈
0x000A–0x000B 00 00— separator / padding01— unknown flag byte- 3-byte partial IEEE 754 float — peak value for this sample window.
0x43prefix = range 130–260
❓ SPECULATIVE: At 1024 sps, 9 samples ≈ 8.8ms per record. Sample rate unconfirmed from captured data alone.
8. Timestamp Format
🔶 Updated 2026-02-26 — Year field resolved. Confidence upgraded.
Timestamps are 6-byte sequences appearing in event headers and waveform keys.
Observed example:
01 07 CB 00 06 1E
Decoded:
| Byte(s) | Value | Meaning | Certainty |
|---|---|---|---|
01 |
1 | Record validity / type flag | 🔶 INFERRED |
07 CB |
1995 | Year — 16-bit big-endian integer | ✅ CONFIRMED — 2026-02-26 |
00 |
0 | Unknown — possibly hours, minutes, or padding | ❓ SPECULATIVE |
06 |
6 | Month (June) | ✅ CONFIRMED |
1E |
30 | Day (0x1E = 30 decimal) | ✅ CONFIRMED |
✅ 2026-02-26 — CONFIRMED: The year 1995 is the MiniMate Plus factory default RTC date, which the device reverts to whenever the internal battery is disconnected or the real-time clock loses power. Any event timestamped around 1995 means the clock was not set. This is known device behavior, not an encoding anomaly.
❓ Still unknown: The
00byte at offset 3. Likely encodes time-of-day (hours or minutes). Needs a capture with a precisely known event time to decode.
9. Out-of-Band / Non-Frame Messages
| Message | Direction | Trigger | Certainty |
|---|---|---|---|
"Operating System" |
S3 → BW | Device boot / UART init / RTC reset | ✅ CONFIRMED |
The device prints this boot string directly to the UART before switching to DLE-framed binary protocol mode. Your implementation should discard any non-
0x41/non-0x10 0x02bytes during the connection phase. Wait for the first valid framed poll response before proceeding.
10. DLE Byte Stuffing
✅ CONFIRMED — 2026-02-26 (previously ❓ SPECULATIVE)
This protocol uses standard DLE (Data Link Escape) byte stuffing, a classical technique used in protocols like IBM BISYNC dating to the 1970s.
Parser State Machine
IDLE:
receive 0x41 → emit ACK event, stay IDLE
receive 0x10 → goto WAIT_STX
WAIT_STX:
receive 0x02 → frame started, goto IN_FRAME
receive anything → error, goto IDLE
IN_FRAME:
receive 0x10 → goto ESCAPE
receive any byte → append to buffer, stay IN_FRAME
ESCAPE:
receive 0x03 → frame complete — validate checksum, process buffer, goto IDLE
receive 0x10 → append single 0x10 to buffer, goto IN_FRAME (stuffed literal)
receive 0x02 → error (nested STX), goto IDLE
receive anything → error, goto IDLE
11. Checksum Reference Implementation
⚠️ Updated 2026-02-26 — Rewritten for correct DLE framing and byte stuffing.
DLE = 0x10
STX = 0x02
ETX = 0x03
ACK = 0x41
def stuff(data: bytes) -> bytes:
"""Escape all 0x10 bytes in payload as 0x10 0x10 for transmission."""
out = []
for b in data:
out.append(b)
if b == DLE:
out.append(DLE) # double it
return bytes(out)
def destuff(data: bytes) -> bytes:
"""Remove DLE stuffing from received payload bytes."""
out = []
i = 0
while i < len(data):
if data[i] == DLE and i + 1 < len(data) and data[i + 1] == DLE:
out.append(DLE)
i += 2
else:
out.append(data[i])
i += 1
return bytes(out)
def calc_checksum(payload: bytes) -> int:
"""
8-bit sum of de-stuffed payload bytes, modulo 256.
Pass the original (pre-stuff) payload — not the wire bytes.
"""
return sum(payload) & 0xFF
def build_frame(payload: bytes) -> bytes:
"""
Build a complete on-wire frame from a raw payload.
Output: ACK + DLE+STX + stuffed_payload + checksum + DLE+ETX
"""
chk = calc_checksum(payload)
stuffed = stuff(payload)
return bytes([ACK, DLE, STX]) + stuffed + bytes([chk, DLE, ETX])
def parse_frame(raw: bytes) -> bytes | None:
"""
Parse and validate a raw on-wire frame.
Accepts input starting with ACK (0x41) or DLE+STX (0x10 0x02).
Returns de-stuffed payload bytes on success, None on any error.
"""
# Strip optional leading ACK
if raw and raw[0] == ACK:
raw = raw[1:]
# Validate frame delimiters
if len(raw) < 5:
return None
if raw[0] != DLE or raw[1] != STX:
return None
if raw[-2] != DLE or raw[-1] != ETX:
return None
# Extract: everything between DLE+STX and DLE+ETX
inner = raw[2:-2]
chk_received = inner[-1]
stuffed_payload = inner[:-1]
# De-stuff and validate checksum
payload = destuff(stuffed_payload)
if calc_checksum(payload) != chk_received:
return None
return payload
# ── Example: build a POLL request (SUB 5B) ────────────────────────────────────
poll_payload = bytes([
0x02, # CMD
0x10, 0x10, # DLE, ADDR (each stuffed to 0x10 0x10 on wire)
0x00, # FLAGS
0x5B, # SUB: POLL
0x00, 0x00, # OFFSET_HI, OFFSET_LO
0x00, 0x00, 0x00, 0x00, # padding
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
])
frame = build_frame(poll_payload)
# Wire output: 41 10 02 02 10 10 10 10 00 5B 00 00 00 00 00 00 00 00 00 00 6B 10 03
12. Recommended Implementation Sequence
Build in this order — each step is independently testable:
- DLE frame parser — stateful byte-by-byte reader implementing the §10 state machine. Handles ACK, stuffing, de-stuffing, checksum validation.
connect(port, baud=38400)— open port, flush buffer, discard ASCII boot strings, send first POLL frame.identify()— SUB5Btwo-step read → returns{"manufacturer": "Instantel", "model": "MiniMate Plus"}.get_serial()— SUB15two-step read → returns serial number string.get_config()— SUB01two-step read → returns full config dict (firmware, channel scales, etc.).get_event_count()— SUB08two-step read → returns number of stored events.get_event_header(index)— SUB1Eread → returns timestamp dict.get_event_record(timestamp)— SUB0Cpaginated read → returns PPV dict per channel.download_waveform(timestamp)— SUB5Abulk stream → returns raw ADC arrays per channel.set_*()write commands — not yet captured, requires additional sniffing sessions.
13. Device Under Test
| Field | Value |
|---|---|
| Manufacturer | Instantel |
| Model | MiniMate Plus |
| Serial Number | BE18189 |
| Firmware | S337.17 |
| DSP / Secondary FW | 10.72 |
| Channels | Tran, Vert, Long, MicL (4 channels) |
| Sample Rate | ~1024 sps (🔶 INFERRED) |
| Bridge Config | COM5 (Blastware) ↔ COM4 (Device), 38400 baud |
| Capture Tool | s3_bridge v0.4.0 |
Appendix A — s3_bridge Capture Format
✅ CONFIRMED — 2026-02-26
⚠️ This behavior is not part of the Instantel protocol. It is an artifact of the bridge logger implementation.
The .bin files produced by s3_bridge are not raw wire bytes. The logger makes one modification:
| Wire sequence | In .bin file | Notes |
|---|---|---|
0x10 0x03 (DLE+ETX) |
0x03 |
DLE stripped from end-of-frame marker |
| All other bytes | Unchanged | ACK, DLE+STX, stuffed payload, checksum all preserved verbatim |
Practical impact for parsing .bin files:
- Frame end: scan for bare
0x03(not0x10 0x03) - Checksum: the byte immediately before the bare
0x03is the checksum - Everything else (ACK detection, DLE+STX, payload de-stuffing) works as documented in §10
⚠️ This means checksums cannot be verified on frames where the stuffed payload ends in
0x10— that trailing0x10would normally be the DLE prefix of ETX, but the logger strips it, making the frame boundary ambiguous in that edge case. In practice this has not been observed in captured data.
14. Open Questions / Still Needs Cracking
| Question | Priority | Added |
|---|---|---|
| Byte at timestamp offset 3 — hours, minutes, or padding? | MEDIUM | 2026-02-26 |
trail[0] in serial number response — unit-specific byte, derivation unknown. trail[1] resolved as firmware minor version. |
MEDIUM | 2026-02-26 |
Full channel ID mapping in SUB 5A stream (01/02/03/04 → which sensor?) |
MEDIUM | 2026-02-26 |
Exact byte boundaries of project string fields in SUB 71 write frame — padding rules unconfirmed |
MEDIUM | 2026-02-26 |
Purpose of SUB 09 / response F6 — 202-byte read block |
MEDIUM | 2026-02-26 |
Purpose of SUB 2E / response D1 — 26-byte read block |
MEDIUM | 2026-02-26 |
Full field mapping of SUB 1A / response E5 — channel scaling / compliance config block |
MEDIUM | 2026-02-26 |
0x082A in channel config block — not trigger, alarm, or record time. Likely one of the unknown uint16 fields (+0A to +10 in per-channel struct). Needs capture changing sample rate or histogram interval to isolate. |
MEDIUM | 2026-03-01 |
| Max range float = 6.2061 in/s — meaning unclear. UI shows "Normal 10.000 in/s" range setting. | LOW | 2026-02-26 |
| Unknown uint16 fields at channel block +0A (=80), +0C (=15), +0E (=40), +10 (=21) — likely sensitivity, gain, or ADC config. | LOW | 2026-03-01 |
Full trigger configuration field mapping (SUB 1C / write 82) |
LOW | 2026-02-26 |
Whether SUB 24/25 are distinct from SUB 5A or redundant |
LOW | 2026-02-26 |
Meaning of 0x07 E7 field in config block |
LOW | 2026-02-26 |
MicL channel units — RESOLVED: psi, confirmed from .set file unit string "psi\0" |
RESOLVED | 2026-03-01 |
All findings reverse-engineered from live RS-232 bridge captures. No Instantel proprietary documentation was referenced or used.
This is a living document — append changelog entries and timestamps as new findings are confirmed or corrected.