Files
seismo-relay/docs/instantel_protocol_reference.md

49 KiB
Raw Blame History

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).
Cross-referenced against Instantel MiniMate Plus Operator Manual (716U0101 Rev 15) from v0.18 onward.
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.170x11 = 17). Byte 0 is unit-specific, derivation unknown.
2026-02-26 §15 Binary Log Format NEW: .bin logger format strips DLE from ETX (0x10 0x030x03). 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 6883.
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.17S337.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 12 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.
2026-03-02 §7.4 Event Index Block CONFIRMED: Backlight and power save offsets independently confirmed via device-set capture (backlight=100=0x64 at +75, power-save=30=0x1E at +83). On-device change visible in S3→BW read response — no Blastware write involved. Offsets are CONFIRMED.
2026-03-02 §7.4 Event Index Block NEW: Monitoring LCD Cycle identified at offsets +84/+85 as uint16 BE. Default value = 65500 (0xFFDC) = effectively disabled / maximum. Confirmed from operator manual §3.13.1g.
2026-03-02 §7.4 Event Index Block UPDATED: Backlight confirmed as uint8 range 0255 seconds per operator manual §3.13.1e ("adjustable timer, 0 to 255 seconds"). Power save unit confirmed as minutes per operator manual §3.13.1f.
2026-03-02 Global NEW SOURCE: Operator manual (716U0101 Rev 15) added as reference. Cross-referencing settings definitions, ranges, and units. Header updated.
2026-03-02 §14 Open Questions Float 6.2061 in/s mystery: manual confirms only two geo ranges (1.25 in/s and 10.0 in/s). 6.2061 is NOT a user-selectable range → likely internal ADC full-scale calibration constant or hardware range ceiling. Downgraded to LOW priority.
2026-03-02 §14 Open Questions 0x082A hypothesis refined: 2090 decimal. At 1024 sps, 2 sec record = 2048 samples. Possible that 0x082A = total samples including 0.25s pre-trigger (256 samples) at some adjusted rate. Needs capture with different record time.
2026-03-02 §14 Open Questions NEW items added: Trigger sample width (default=2), Auto Window (1-9 sec), Aux Trigger (enabled/disabled) — all confirmed settings from operator manual not yet mapped in protocol.
2026-03-02 §14 Open Questions Monitoring LCD Cycle resolved — removed from open questions.
2026-03-02 Appendix A CORRECTED: Previous entry stated logger strips DLE from ETX. This was wrong — it applied to an older logger version. s3_bridge v0.5.0 confirmed to preserve raw wire bytes including 0x10 0x03 intact. HxD inspection of new capture confirmed 10 03 present in S3→BW record payloads.
2026-03-02 Appendix A UPDATED: New capture architecture: two flat raw wire dumps per session (raw_s3.bin, raw_bw.bin), one per direction, no record wrapper. Replaces structured .bin format for parser input.
2026-03-02 Appendix A PARSER: Deterministic DLE state machine implemented (s3_parser.py). Three states: IDLE → IN_FRAME → AFTER_DLE. Replaces heuristic global scanning. Properly handles DLE stuffing (10 10 → literal 10). Only complete STX→ETX pairs counted as frames.
2026-03-02 Appendix A VALIDATED: raw_bw.bin yields 7 complete frames via state machine. raw_s3.bin contains large structured responses (first frame payload ~3922 bytes). Both files confirmed lossless. BW bare 0x02 pattern confirmed as asymmetric framing (BW sends bare STX, S3 sends DLE+STX).

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 0x41 as STX and 0x02/0x03 as 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: 0x100x10 0x10. This prevents the parser from confusing real data with frame control sequences.

  • Transmit: Replace every 0x10 in payload with 0x10 0x10
  • Receive: Replace every 0x10 0x10 in the frame body with a single 0x10
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 0x41 ACK always arrives in a separate read() 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 0x100x10):

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 12: After de-stuffing, bytes 1 and 2 are both 0x10 in 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 consecutive 0x10 bytes (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 0x600x83 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 35× 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 1215 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). 0x20 is 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 firmware S337.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

2026-03-02 — CONFIRMED: Backlight and power save offsets confirmed via two independent captures with device-set values. Offsets are from the start of the data section (after the 16-byte protocol header).

Layout (offsets relative to data section start):

Offset +00:  00 58 09            — Total index size or record count ❓
Offset +03:  00 00 00 01         — Possibly stored event count = 1 ❓
Offset +07:  01 07 CB 00 06 1E  — Timestamp of event 1 (see §8)
Offset +0D:  01 07 CB 00 14 00  — Timestamp of event 2 (see §8)
Offset +13:  00 00 00 17 3B     — Unknown ❓
Offset +4B:  [backlight]         — BACKLIGHT ON TIME ✅ CONFIRMED
Offset +4C:  00                  — padding (backlight is uint8, not uint16)
Offset +53:  [power_save]        — POWER SAVING TIMEOUT ✅ CONFIRMED
Offset +54:  [lcd_hi] [lcd_lo]  — MONITORING LCD CYCLE (uint16 BE) ✅ CONFIRMED
Offset Size Type Known values Meaning Certainty
+4B 1 uint8 250, 100 BACKLIGHT ON TIME (0255 seconds per manual) CONFIRMED
+4C 1 0x00 Padding / high byte of potential uint16 🔶 INFERRED
+53 1 uint8 10, 30 POWER SAVING TIMEOUT (minutes) CONFIRMED
+54..+55 2 uint16 BE 0xFFDC = 65500 MONITORING LCD CYCLE (seconds; 65500 ≈ disabled/max) CONFIRMED

Confirmation captures:

Capture Backlight (+4B) Power Save (+53) LCD Cycle (+54/55)
20260301_160702 (BW-written) 0xFA = 250 0x0A = 10 min 0xFF 0xDC = 65500
20260302_144606 (device-set) 0x64 = 100 0x1E = 30 min 0xFF 0xDC = 65500

📖 Manual cross-reference (716U0101 Rev 15, §3.13.1):

  • Backlight On Time: "adjustable timer, from 0 to 255 seconds" (§3.13.1e)
  • Power Saving Timeout: "automatically turns the Minimate Plus off" — stored in minutes (§3.13.1f)
  • Monitoring LCD Cycle: "cycles off for the time period... set to zero to turn off" — 65500 = effectively disabled (§3.13.1g)

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 193237 and 151147). Trigger changed 0.500 → 0.200, then 0.200 → 0.600. Alarm changed 1.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.set cross-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 .set file = 0x00000003 (3 sec), which would be 00 00 00 03 on wire — not 0x082A. The original sessions had record time = 2, which would be 00 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. 01 consistently observed. Full mapping unknown. 🔶 INFERRED
  • 9× signed 16-bit big-endian ADC samples. Noise floor ≈ 0x000A0x000B
  • 00 00 — separator / padding
  • 01 — unknown flag byte
  • 3-byte partial IEEE 754 float — peak value for this sample window. 0x43 prefix = range 130260

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 00 byte 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 0x02 bytes 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

Build in this order — each step is independently testable:

  1. DLE frame parser — stateful byte-by-byte reader implementing the §10 state machine. Handles ACK, stuffing, de-stuffing, checksum validation.
  2. connect(port, baud=38400) — open port, flush buffer, discard ASCII boot strings, send first POLL frame.
  3. identify() — SUB 5B two-step read → returns {"manufacturer": "Instantel", "model": "MiniMate Plus"}.
  4. get_serial() — SUB 15 two-step read → returns serial number string.
  5. get_config() — SUB 01 two-step read → returns full config dict (firmware, channel scales, etc.).
  6. get_event_count() — SUB 08 two-step read → returns number of stored events.
  7. get_event_header(index) — SUB 1E read → returns timestamp dict.
  8. get_event_record(timestamp) — SUB 0C paginated read → returns PPV dict per channel.
  9. download_waveform(timestamp) — SUB 5A bulk stream → returns raw ADC arrays per channel.
  10. 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 (not 0x10 0x03)
  • Checksum: the byte immediately before the bare 0x03 is 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 trailing 0x10 would 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 Notes
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 directly. Hypothesis: total sample count at 1024 sps: 2 sec record + 0.25 pre-trigger = 2.25 sec × ~930 sps? Or encoded differently. Capture with different record time needed. MEDIUM 2026-03-01 Updated 2026-03-02
Unknown uint16 fields at channel block +0A (=80), +0C (=15), +0E (=40), +10 (=21) — manual describes "Sensitive (Gain=8) / Normal (Gain=1)" per-channel range; 80/15/40/21 might encode gain, sensitivity, 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
Trigger Sample Width — setting confirmed in manual (default=2 samples, §3.13.1h). Location in protocol not yet mapped. LOW 2026-03-02 NEW
Auto Window — "1 to 9 seconds" per manual (§3.13.1b). Location in protocol not yet mapped. LOW 2026-03-02 NEW
Auxiliary Trigger — Enabled/Disabled per manual (§3.13.1d). Location in protocol not yet mapped. LOW 2026-03-02 NEW
Max Geo Range float 6.2061 in/s — NOT a user-selectable range (manual only shows 1.25 and 10.0 in/s). Likely internal ADC full-scale constant or hardware range ceiling. Not worth capturing. LOW 2026-02-26 Downgraded 2026-03-02
MicL channel units — RESOLVED: psi, confirmed from .set file unit string "psi\0" RESOLVED 2026-03-01
Backlight offset — RESOLVED: +4B in event index data, uint8, seconds RESOLVED 2026-03-02
Power save offset — RESOLVED: +53 in event index data, uint8, minutes RESOLVED 2026-03-02
Monitoring LCD Cycle — RESOLVED: +54/+55 in event index data, uint16 BE, seconds (65500 = disabled) RESOLVED 2026-03-02


Appendix B — Operator Manual Cross-Reference (716U0101 Rev 15)

Added 2026-03-02. Cross-referencing confirms setting names, ranges, units, and behavior for fields found in protocol captures. The manual does NOT describe the wire protocol — it describes the user-facing device interface. Use to infer data types, ranges, and semantics of protocol fields.

Setting Name (Manual) Manual Location Protocol Location Type Range / Notes
Backlight On Time §3.13.1e Event Index +4B uint8 0255 seconds
Power Saving Timeout §3.13.1f Event Index +53 uint8 minutes (user sets 160+)
Monitoring LCD Cycle §3.13.1g Event Index +54/55 uint16 BE seconds; 0=off; 65500≈disabled
Trigger Level (Geo) §3.8.6 Channel block, float float32 BE 0.00510.000 in/s
Alarm Level (Geo) §3.9.9 Channel block, float float32 BE higher than trigger level
Trigger Level (Mic) §3.8.6 Channel block, float float32 BE 100148 dB in 1 dB steps
Alarm Level (Mic) §3.9.10 Channel block, float float32 BE higher than mic trigger
Record Time §3.8.9 .set +16 confirmed uint32 1500 seconds
Max Geo Range §3.8.4 Channel block, float float32 BE 1.25 or 10.0 in/s (user); 6.2061 in protocol = internal constant
Microphone Units §3.9.7 Inline unit string char[4] "psi\0", "pa.\0", "dB\0\0"
Sample Rate §3.8.2 Unknown — needs capture 1024, 2048, 4096 (compliance); up to 65536 (advanced)
Record Mode §3.8.1 Unknown Single Shot, Continuous, Manual, Histogram, Histogram Combo
Trigger Sample Width §3.13.1h NOT YET MAPPED uint8? Default=2 samples
Auto Window §3.13.1b NOT YET MAPPED uint8? 19 seconds
Auxiliary Trigger §3.13.1d NOT YET MAPPED bool Enabled/Disabled
Password §3.13.1c Unknown 4-key sequence
Serial Connection §3.9.11 Unknown Direct / Via Modem
Baud Rate §3.9.12 Unknown 38400 for direct

Appendix C — Logger & Parser Validation (2026-03-02)

Documents the logger integrity verification and parser refactor completed 2026-03-02. Tooling behavior only — not protocol semantics.

C.1 Logger Validation

Concern: Earlier sessions noted that the s3_bridge logger may have been stripping 0x10 from DLE ETX sequences, producing bare 0x03 terminators in the capture file.

Resolution: HxD inspection of a new capture produced by s3_bridge v0.5.0 confirmed that 10 03 sequences are present intact inside S3→BW record payloads. The forward_loop function writes raw bytes to the .bin before any sniffer or framing logic runs — there is no ETX stripping in v0.5.0.

The earlier stripping behavior applied to a previous logger version. v0.5.0 is confirmed lossless with respect to wire bytes.

Confirmed wire framing:

  • Frame start: 0x10 0x02 (DLE STX)
  • Frame end: 0x10 0x03 (DLE ETX)
  • DLE stuffing: 0x10 0x10 in payload = literal 0x10

C.2 Capture Architecture (Current)

As of 2026-03-02 the capture pipeline produces two flat raw wire dump files per session:

File Contents
raw_s3.bin All bytes transmitted by S3 (device → Blastware), in order
raw_bw.bin All bytes transmitted by BW (Blastware → device), in order

No record headers, no timestamps, no framing logic applied by the dumper. Files are flat concatenations of serial.read() chunks. Frame boundaries must be recovered by the parser.

C.3 Parser Design — DLE State Machine

A deterministic state machine replaces all prior heuristic scanning.

States:

STATE_IDLE       — scanning for frame start
STATE_IN_FRAME   — consuming payload bytes
STATE_AFTER_DLE  — last byte was 0x10, awaiting qualifier

Transitions:

Current State Byte Action Next State
IDLE 10 02 Begin new frame IN_FRAME
IDLE any Discard IDLE
IN_FRAME != 10 Append to payload IN_FRAME
IN_FRAME 10 AFTER_DLE
AFTER_DLE 10 Append literal 0x10 IN_FRAME
AFTER_DLE 03 Frame complete, emit IDLE
AFTER_DLE other Treat as payload (recovery) IN_FRAME

Properties:

  • Does not scan globally for 10 02
  • Only complete STX→ETX pairs are emitted as frames
  • Incomplete trailing frames at EOF are discarded (expected at capture boundaries)
  • DLE stuffing handled correctly

C.4 Observed Traffic (Validation Captures)

raw_bw.bin (Blastware → S3):

  • 7 complete frames via state machine
  • Mostly small command/control frames, several zero-length payloads
  • Bare 0x02 used as STX (asymmetric — BW does not use DLE STX)
  • Contains project metadata strings: "Standard Recording Setup.set", "Claude test2", "Location #1 - Brians House"

raw_s3.bin (S3 → Blastware):

  • First frame payload ~3922 bytes (large structured response)
  • Repeated "Instantel" / "MiniMate Plus" / "BE18189" strings throughout
  • Multiple medium-length structured frames
  • DLE+ETX confirmed intact

C.5 Key Lessons

  1. Global byte counting ≠ frame counting. 0x10 0x02 appears inside payloads. Only state machine transitions produce valid frame boundaries.
  2. STX count ≠ frame count. Only STX→ETX pairs within proper state transitions count.
  3. EOF mid-frame is normal. Capture termination during active traffic produces an incomplete trailing frame. Not an error.
  4. Layer separation. The parser extracts frames only. Decoding block IDs, validating checksums, and interpreting semantics are responsibilities of a separate protocol decoder layer above it.

C.6 Parser Layer Architecture

raw_s3.bin / raw_bw.bin
        ↓
DLE Frame Parser (s3_parser.py)   <- framing only
        ↓
Protocol Decoder (future)         <- SUB IDs, block layout, checksums
        ↓
Semantic Interpretation           <- settings, events, responses

All findings reverse-engineered from live RS-232 bridge captures.
Cross-referenced from 2026-03-02 with Instantel MiniMate Plus Operator Manual (716U0101 Rev 15).
This is a living document — append changelog entries and timestamps as new findings are confirmed or corrected.