- minimateplus/transport.py: add TcpTransport — stdlib socket-based transport with same interface as SerialTransport. Overrides read_until_idle() with idle_gap=1.5s to absorb the modem's 1-second serial data forwarding buffer. - minimateplus/client.py: make `port` param optional (default "") so MiniMateClient works cleanly when a pre-built transport is injected. - minimateplus/__init__.py: export SerialTransport and TcpTransport. - sfm/server.py: add `host` / `tcp_port` query params to all device endpoints. New _build_client() helper selects TCP or serial transport automatically. OSError (connection refused, timeout) now returns HTTP 502. - docs/instantel_protocol_reference.md: add changelog entry and full §14 (TCP/Modem Transport) documenting confirmed transparent passthrough, no ENQ on connect, modem forwarding delay, call-up vs ACH modes, and hardware note deprecating Raven X in favour of RV55/RX55. Usage: GET /device/info?host=<modem_ip>&tcp_port=12345
70 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).
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.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. |
| 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 0–255 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). |
| 2026-03-09 | §7.6, §Appendix B | CONFIRMED: Record time located in SUB E5 data page2 at payload offset +0x28 as float32 BE. Confirmed via two controlled captures: 7 sec = 40 E0 00 00, 13 sec = 41 50 00 00. Geo range (only 1.25 or 10.0 in/s) eliminates ambiguity — 7 and 13 are not valid geo range values. |
| 2026-03-09 | §7.5, §14 | CORRECTED: The byte 0x0A appearing after the "Extended Notes" null-padded label in the E5 payload is NOT record time. It is an unknown field that equals 10 and does not change when record time changes. False lead closed. |
| 2026-03-09 | §14 | RESOLVED: 0x082A mystery closed — confirmed as fixed-size E5 payload length (2090 bytes), not a record-time-derived sample count. Value is constant regardless of record time or other settings. |
| 2026-03-09 | §7.8, §14, Appendix B | NEW — Trigger Sample Width confirmed: Located in BW→S3 write frame SUB 0x82, destuffed payload offset [22], uint8. Confirmed via BW-side capture (raw_bw.bin) diffing two sessions: Width=4 → 0x04, Width=3 → 0x03. Setting is transmitted only on BW→S3 write (SUB 0x82), invisible in S3-side compliance dumps. |
| 2026-03-09 | §14, Appendix B | CONFIRMED — Mode gating is a real protocol behavior: Several settings are only transmitted (and possibly only interpreted by the device) when the required mode is active. Trigger Sample Width is only sent when in Compliance/Single-Shot/Fixed Record Time mode. Auto Window is only relevant when Record Stop Mode = Auto — attempting to capture it in Fixed mode produced no change on the wire (F7 and D1 blocks identical before/after). This is an architectural property, not a gap in the capture methodology. Future capture attempts for mode-gated settings must first activate the appropriate mode. |
| 2026-03-09 | §14 | UPDATED — Auto Window: Capture attempted (Auto Window 3→9) in Fixed record time mode. No change observed in any S3-side frame (F7, D1, E5 all identical). Confirmed mode-gated behind Record Stop Mode = Auto. Not capturable without switching modes — deferred. |
| 2026-03-11 | §14, Appendix B | CONFIRMED — Aux Trigger read location: SUB FE (FULL_CONFIG_RESPONSE), destuffed payload offset 0x0109, uint8. 0x00 = disabled, 0x01 = enabled. Confirmed via controlled capture: changed Aux Trigger in Blastware, sent to unit, re-read config. FE diff showed clean isolated flip at 0x0109 with only 3 other bytes changing (likely counters/checksums at 0x0033, 0x00C0, 0x04ED). |
| 2026-03-11 | §14, Appendix B | PARTIAL — Aux Trigger write path: Write command not yet isolated. The BW→S3 write appears to occur inside the A4 (POLL_RESPONSE) stream via inner frame handshaking — multiple WRITE_CONFIRM_RESPONSE inner frames (SUBs 7C, 7D, 8B, 8C, 8D, 8E, 96, 97) appeared in A4 after the write, and the TRIGGER_CONFIG_RESPONSE (SUB E3) inner frames were removed. Write command itself not yet captured in a clean session — likely SUB 15 or embedded in the partial session 0. Write path deferred for a future clean capture. |
| 2026-03-11 | §4, §14 | NEW — SUB A4 is a composite container frame: A4 (POLL_RESPONSE) payload contains multiple embedded inner frames using the same DLE framing (10 02 start, 10 03 end, 10 10 stuffing). Phase-shift diffing issue resolved in s3_analyzer.py by adding _extract_a4_inner_frames() and _diff_a4_payloads() — diff count reduced from 2300 → 17 meaningful entries. |
| 2026-03-11 | §14 | NEW — SUB 6E response anomaly: BW sends SUB 1C (TRIGGER_CONFIG_READ) and S3 responds with SUB 6E — does NOT follow the 0xFF - SUB rule (0xFF - 0x1C = 0xE3). Only known exception to the response pairing rule observed to date. SUB 6E payload starts with ASCII string "Long2". |
| 2026-03-12 | §11 | CONFIRMED — BW→S3 large-frame checksum algorithm: SUBs 68, 69, 71, 82, and 1A (with data) use: chk = (sum(b for b in payload[2:-1] if b != 0x10) + 0x10) % 256 — SUM8 of payload bytes [2:-1] skipping all 0x10 bytes, plus 0x10 as a constant, mod 256. Validated across 20 frames from two independent captures with differing string content (checksums differ between sessions, both validate correctly). Small frames (POLL, read commands) continue to use plain SUM8 of payload[0:-1]. The two formulas are consistent: small frames have exactly one 0x10 (CMD at [0]), which the large-frame formula's [2:] start and +0x10 constant account for. |
| 2026-03-12 | §11 | RESOLVED — BAD CHK false positives on BW POLL frames: Parser bug — BW frame terminator (03 41, ETX+ACK) was being included in the de-stuffed payload instead of being stripped as framing. BW frames end with bare 0x03 (not 10 03). Fix: strip trailing 03 41 from BW payloads before checksum computation. |
| 2026-03-30 | §3, §5.1 | CONFIRMED — BW→S3 two-step read offset is at payload[5], NOT payload[3:4]. All BW read-command frames have payload[3] = 0x00 and payload[4] = 0x00 unconditionally. The two-step offset byte lives at payload[5]: 0x00 for the length-probe step, DATA_LEN for the data-fetch step. Validated against all captured frames in bridges/captures/3-11-26/raw_bw_*.bin — every frame is an exact bit-for-bit match when built with offset at [5]. The page_hi/page_lo framing in the docstring was a misattribution from the S3-side response layout (where [3]/[4] ARE page bytes). |
| 2026-03-30 | §4, §5.2 | CONFIRMED — S3 probe response page_key is always 0x0000. The S3 response to a length-probe step does NOT carry the data length back in page_hi/page_lo. Both bytes are 0x00 in every observed probe response. Data lengths for each SUB are fixed constants (see §5.1 table). The minimateplus library now uses a hardcoded DATA_LENGTHS dict rather than trying to read the length from the probe response. |
| 2026-03-31 | §12 TCP Transport | NEW SECTION — TCP/modem transport confirmed transparent from Blastware Operator Manual (714U0301 Rev 22). Key facts confirmed: (1) Protocol bytes over TCP are bit-for-bit identical to RS-232 — no handshake framing. (2) No ENQ byte on TCP connect (Enable ENQ on TCP Connect: 0-Disable in Raven ACEmanager). (3) Raven modem Data Forwarding Timeout = 1 second — modem buffers serial bytes up to 1s before forwarding over TCP; TcpTransport.read_until_idle uses idle_gap=1.5s to compensate. (4) TCP port is user-configurable (12335 in manual example; user's install uses 12345). (5) Baud rate over serial link to modem is 38400,8N1 regardless of TCP path. (6) ACH (Auto Call Home) = INBOUND to server (unit calls home); "call up" = OUTBOUND from client (Blastware/SFM connects to modem IP). TcpTransport implements outbound (call-up) mode. |
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 / Composite Container
⚠️ SUB A4 is a composite container frame. The large A4 payload (~3600+ bytes) contains multiple embedded inner sub-frames using the same DLE framing as the outer protocol (
10 02start,10 03end,10 10stuffing). Inner frames carry WRITE_CONFIRM_RESPONSE and TRIGGER_CONFIG_RESPONSE sub-frames among others. Flat byte-by-byte diffing of A4 is unreliable due to phase shifting — use inner-frame-aware diffing (_diff_a4_payloads()in s3_analyzer.py). Confirmed 2026-03-11.
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
✅ 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 (0–255 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
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.
7.6.1 Record Time (SUB E5 data page2 +0x28)
✅ CONFIRMED — 2026-03-09 from two controlled captures (record time 7→13 sec, raw_s3-3-9-26_2.bin and raw_s3-3-9-26_3.bin).
Record time is stored as a 32-bit IEEE 754 float, big-endian at offset +0x28 from the start of the E5 data page2 payload.
| Record Time | float32 BE bytes | Decoded |
|---|---|---|
| 7 seconds | 40 E0 00 00 |
7.0 |
| 10 seconds | 41 20 00 00 |
10.0 |
| 13 seconds | 41 50 00 00 |
13.0 |
Disambiguation note: Max geo range (also a float in this block) only takes values 1.25 or 10.0 in/s. The values 7 and 13 are not valid geo range selections, confirming this field is record time.
0x0A after "Extended Notes" label: The byte 0x0A that appears after the null-padded "Extended Notes" string in the E5 payload is not record time. It is an unknown field that equals 10 and is invariant across record time changes. Do not confuse it with the record time float at +0x28.
✅
0x082A(= 2090) — RESOLVED: This value is the fixed payload length of the E5 response block (2090 bytes). It is constant regardless of record time, trigger level, or any other setting. It appears in the E5 frame header as the declared data length for the paged read, not as a settings field.
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.8 Trigger / Advanced Config Write Frame (BW→S3 SUB 0x82)
✅ CONFIRMED — 2026-03-09 from controlled BW-side capture diff (Trigger Sample Width 4→3).
SUB 0x82 is the BW→S3 write command for the advanced trigger configuration block. It is the write counterpart to the S3→BW read response SUB 0xD1 (0xFF − 0x82 = 0x7D is a separate sub; the D1/2E read pair is distinct). The 0x82 write frame is only visible in raw_bw.bin — it does not appear in S3-side compliance dumps.
Destuffed BW write frame layout (47 raw bytes → 46 destuffed):
offset value meaning
[00] 0x10 addr (literal 0x10 after destuffing)
[01] 0x00 unknown
[02] 0x82 SUB: advanced config write
[03] 0x00 unknown
[04] 0x00 unknown
[05] 0x1C length = 28 bytes (payload size)
[06..10] 00.. header/padding
[11..16] 00.. header/padding
[17] 0x1A unknown (constant 26 = 0x1A)
[18] 0xD5 unknown (constant)
[19] 0x00 unknown
[20] 0x00 unknown
[21] 0x10 literal 0x10 (stuffed in raw frame as 10 10)
[22] 0x04/0x03 Trigger Sample Width ✅ CONFIRMED (uint8, samples)
[23] 0x0A unknown (constant 10; NOT Auto Window)
[24..43] 0xFF.. padding
[44] 0x00 unknown
[45] checksum
Confirmed Trigger Sample Width values:
| Width setting | Byte [22] |
|---|---|
| 4 samples | 0x04 |
| 3 samples | 0x03 |
| 2 samples (default) | 0x02 (expected — not yet captured) |
Known constants in this frame: [17]=0x1A, [18]=0xD5, [23]=0x0A. These do not change with Trigger Sample Width changes. Byte [23] = 10 was initially a candidate for Auto Window (range 1–9) but cannot be Auto Window because 10 is outside the valid range.
Mode gating: This write frame is only transmitted when Blastware performs a Send To Unit operation in Compliance / Single-Shot / Fixed Record Time mode. The frame is absent from other session types.
7.9 Mode Gating — Protocol Architecture Note
✅ CONFIRMED — 2026-03-09 from controlled captures and null-change experiments.
Several settings are mode-gated: the device only transmits (reads) or accepts (writes) certain fields when the appropriate operating mode is active. This is an architectural property of the protocol, not a gap in capture methodology.
Observed mode gating:
| Setting | Gate Condition | Evidence |
|---|---|---|
| Trigger Sample Width | Compliance / Single-Shot / Fixed Record Time mode | Not visible in S3-side reads; only in BW write frame (SUB 0x82) when mode is active |
| Auto Window | Record Stop Mode = Auto | Capture of 3→9 change in Fixed mode produced zero wire change in all frames (F7, D1, E5 all identical) |
Implication for captures: To map a mode-gated setting, you must first activate the gating mode on the device, then perform the compliance dump or write capture. Changing the setting value while in the wrong mode will produce no observable wire change.
Suspected mode-gated settings not yet captured:
- Auto Window (requires Record Stop Mode = Auto)
- Auxiliary Trigger (unknown gate condition)
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-03-12 — BW→S3 large-frame checksum algorithm confirmed. Two distinct formulas apply depending on frame direction and size.
Checksum Overview
| Direction | Frame type | Formula | Coverage |
|---|---|---|---|
| S3→BW | All frames | sum(payload) & 0xFF |
All de-stuffed payload bytes [0:-1] |
| BW→S3 | Small frames (POLL, read cmds) | sum(payload) & 0xFF |
All de-stuffed payload bytes [0:-1] |
| BW→S3 | Large write frames (SUB 68,69,71,82,1A+data) |
See formula below | De-stuffed payload bytes [2:-1], skipping 0x10 bytes, plus constant |
BW→S3 Large-Frame Checksum Formula
def calc_checksum_bw_large(payload: bytes) -> int:
"""
Checksum for large BW→S3 write frames (SUB 68, 69, 71, 82, 1A with data).
Formula: sum all bytes in payload[2:-1], skipping 0x10 bytes, add 0x10, mod 256.
Confirmed across 20 frames from two independent captures (2026-03-12).
"""
return (sum(b for b in payload[2:-1] if b != 0x10) + 0x10) & 0xFF
Why this formula: The CMD byte at payload[0] is always 0x10 (DLE). The byte at payload[1] is always 0x00. Starting from payload[2] skips both. All 0x10 bytes in the data section are excluded from the sum, then 0x10 is added back as a constant — effectively treating DLE as a transparent/invisible byte in the checksum. This is consistent with 0x10 being a framing/control character in the protocol.
Consistency check: For small frames, payload[0] = 0x10 and there are no other 0x10 bytes in the payload. The large-frame formula applied to a small frame would give (sum(payload[2:-1]) + 0x10) = sum(payload[0:-1]) — identical to the plain SUM8. The two formulas converge for frames without embedded 0x10 data bytes.
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_s3(payload: bytes) -> int:
"""
Standard SUM8: used for all S3→BW frames and small BW→S3 frames.
Sum of all payload bytes (excluding the checksum byte itself), mod 256.
"""
return sum(payload) & 0xFF
def calc_checksum_bw_large(payload: bytes) -> int:
"""
Large BW→S3 write frame checksum (SUB 68, 69, 71, 82, 1A with data).
Sum payload[2:-1] skipping 0x10 bytes, add 0x10, mod 256.
"""
return (sum(b for b in payload[2:-1] if b != 0x10) + 0x10) & 0xFF
# Backwards-compatible alias
def calc_checksum(payload: bytes) -> int:
return calc_checksum_s3(payload)
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 |
14. TCP / Modem Transport
✅ CONFIRMED — 2026-03-31 from Blastware Operator Manual 714U0301 Rev 22 §4.4 and ACEmanager Raven modem configuration screenshots.
The MiniMate Plus protocol is fully transport-agnostic at the byte level. The same DLE-framed S3/BW frame stream that flows over RS-232 is transmitted unmodified over a TCP socket. No additional framing, handshake bytes, or session tokens are added at the application layer.
14.1 Two Usage Modes
"Call Up" (Outbound TCP — SFM connects to modem)
Blastware or SFM opens a TCP connection to the modem's static IP address on its device port. The modem bridges the TCP socket to its RS-232 serial port, which is wired directly to the MiniMate Plus. From the protocol perspective this is identical to a direct serial connection.
SFM ──TCP──► Raven modem ──RS-232──► MiniMate Plus
(static IP, port N) (38400,8N1)
This is the mode implemented by TcpTransport(host, port). Typical call:
GET /device/info?host=203.0.113.5&tcp_port=12345
"Call Home" / ACH (Inbound TCP — unit calls the server)
The MiniMate Plus is configured with an IP address and port. On an event trigger or scheduled time it powers up its modem, which establishes a TCP connection outbound to the server. Blastware (or a future SFM ACH listener) accepts the incoming connection. After the unit connects, the PC has a configurable "Wait for Connection" window to send the first command before the unit times out and hangs up.
MiniMate Plus ──RS-232──► Raven modem ──TCP──► ACH server (listening)
(static office IP, port N)
TcpTransport is a client (outbound connect only). A separate AchServer listener component is needed for this mode — not yet implemented.
14.2 No Application-Layer Handshake on TCP Connect
✅ Confirmed from ACEmanager configuration screenshot:
Enable ENQ on TCP Connect: 0-Disable
When a TCP connection is established (in either direction), no ENQ byte or other handshake marker is sent by the modem before the protocol stream starts. The first byte from either side is a raw protocol byte — for SFM-initiated call-up, SFM sends POLL_PROBE immediately after connect().
No banner, no "CONNECT" string, no Telnet negotiation preamble. The Raven modem's TCP dialog is configured with:
| ACEmanager Setting | Value | Meaning |
|---|---|---|
| TCP Auto Answer | 2 — Telnet Server | TCP mode (transparent pass-through, not actually Telnet) |
| Telnet Echo Mode | 0 — No Echo | No echo of received bytes |
| Enable ENQ on TCP Connect | 0 — Disable | No ENQ byte on connect |
| TCP Connect Response Delay | 0 | No delay before first byte |
| TCP Idle Timeout | 0 | No modem-level idle disconnect |
14.3 Modem Serial Port Configuration
Hardware note: The Raven X modem shown in the Blastware manual is 3G-only and no longer operational (3G network shutdown). The current field hardware is the Sierra Wireless RV55 (and newer RX55). Both run ALEOS firmware and have an identical ACEmanager web UI — the settings below apply to all three generations.
The modem's RS-232 port (wired to the MiniMate Plus) must be configured as:
| ACEmanager Setting | Value |
|---|---|
| Configure Serial Port | 38400,8N1 |
| Flow Control | None |
| DB9 Serial Echo | OFF |
| Data Forwarding Timeout | 1 second (S50=1) |
| Data Forwarding Character | 0 (disabled) |
The Data Forwarding Timeout is the most protocol-critical setting. The modem accumulates bytes from the RS-232 port for up to 1 second before forwarding them as a TCP segment. This means:
- A large S3 response frame may arrive as multiple TCP segments with up to 1-second gaps between them.
- A
read_until_idleimplementation withidle_gap < 1.0 swill incorrectly declare the frame complete mid-stream. TcpTransport.read_until_idleoverrides the defaultidle_gap=0.05 stoidle_gap=1.5 sto compensate.
If connecting to a unit via a direct Ethernet connection (no serial modem in the path), the 1.5 s idle gap will still work but will feel slower. In that case you can pass idle_gap=0.1 explicitly.
14.4 Connection Timeouts on the Unit Side
The MiniMate Plus firmware has two relevant timeouts configurable via Blastware's Call Home Setup dialog:
| Timeout | Description | Impact |
|---|---|---|
| Wait for Connection | Seconds after TCP connect during which the unit waits for the first BW frame. If nothing arrives, unit terminates the session. | SFM must send POLL_PROBE within this window after connect(). Default appears short (≈15–30 s). |
| Serial Idle Time | Seconds of inactivity after which the unit terminates the connection. | SFM must complete its work and disconnect cleanly — or send periodic keep-alive frames — within this window. |
For our TcpTransport + MiniMateProtocol stack, both timeouts are satisfied automatically because connect() is immediately followed by protocol.poll() which sends POLL_PROBE, and the full session (POLL + read + disconnect) typically completes in < 30 seconds.
14.5 Port Numbers
The TCP port is user-configurable in both Blastware and the modem. There is no universally fixed port.
| Setting location | Value in manual example | Value in user's install |
|---|---|---|
| Blastware TCP Communication dialog | 12335 | 12345 |
| Raven ACEmanager Destination Port | 12349 (UDP example) | varies |
TcpTransport defaults to DEFAULT_TCP_PORT = 12345 which matches the user's install. This can be overridden by the port argument or the tcp_port query parameter in the SFM server.
14.6 ACH Session Lifecycle (Call Home Mode — Future)
When the unit calls home under ACH, the session lifecycle from the unit's perspective is:
- Unit triggers (event or scheduled time)
- Unit powers up modem, dials / connects TCP to server IP:port
- Unit waits for "Wait for Connection" window for first BW frame from server
- Server sends POLL_PROBE → unit responds with POLL_RESPONSE (same as serial)
- Server reads serial number, full config, events as needed
- Server disconnects (or unit disconnects on Serial Idle Time expiry)
- Unit powers modem down, returns to monitor mode
Step 4 onward is identical to the serial/call-up protocol. The only difference from our perspective is that we are the listener rather than the connector. A future AchServer class will accept the incoming TCP connection and hand the socket to TcpTransport for processing.
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 | 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. RESOLVED: fixed E5 payload length (2090 bytes). Constant regardless of all settings. |
RESOLVED | 2026-03-01 | Resolved 2026-03-09 |
Record time in wire protocol — float32 BE at E5 data page2 +0x28. RESOLVED. See §7.6.1. |
RESOLVED | 2026-03-09 | Confirmed via 7→13 sec captures |
| 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 — RESOLVED: BW→S3 write frame SUB 0x82, destuffed payload offset [22], uint8. Width=4 → 0x04, Width=3 → 0x03. Confirmed via BW-side capture diff. Only visible in raw_bw.bin write traffic, not in S3-side compliance reads. |
RESOLVED | 2026-03-02 | Confirmed 2026-03-09 |
| Auto Window — "1 to 9 seconds" per manual (§3.13.1b). Mode-gated: only transmitted/active when Record Stop Mode = Auto. Capture attempted in Fixed mode (3→9 change) — no wire change observed in any frame. Deferred pending mode switch. | LOW | 2026-03-02 | Updated 2026-03-09 |
Auxiliary Trigger read location — RESOLVED: SUB FE offset 0x0109, uint8, 0x00=disabled, 0x01=enabled. Confirmed 2026-03-11 via controlled toggle capture. |
RESOLVED | 2026-03-02 | Resolved 2026-03-11 |
Auxiliary Trigger write path — Write command not yet captured in a clean session. Inner frame handshake visible in A4 (multiple WRITE_CONFIRM_RESPONSE SUBs appear, TRIGGER_CONFIG_RESPONSE removed), but the BW→S3 write command itself was in a partial session. Likely SUB 15 or similar. Deferred for clean capture. |
LOW | 2026-03-11 | NEW |
SUB 6E response to SUB 1C — S3 responds to TRIGGER_CONFIG_READ (SUB 1C) with SUB 6E, NOT 0xE3 as the 0xFF - SUB rule would predict. Only known exception to the response pairing rule observed to date. Payload starts with ASCII "Long2". Purpose unknown. |
LOW | 2026-03-11 | 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 | 0–255 seconds |
| Power Saving Timeout | §3.13.1f | Event Index +53 | uint8 | minutes (user sets 1–60+) |
| 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.005–10.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 | 100–148 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 | E5 data page2 +0x28 (wire); .set +16 (file) |
float32 BE (wire); uint32 LE (file) | 1–105 seconds (menu label <105); confirmed 7→40E00000, 10→41200000, 13→41500000 |
| 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 | BW→S3 SUB 0x82 write frame, destuffed [22], uint8 |
uint8 | Default=2; confirmed 4=0x04, 3=0x03. BW-side write only — not visible in S3 compliance reads. Mode-gated: only sent in Compliance/Single-Shot/Fixed mode. |
| Auto Window | §3.13.1b | Mode-gated — NOT YET MAPPED | uint8? | 1–9 seconds; only active when Record Stop Mode = Auto. Capture in Fixed mode produced no wire change. |
| Auxiliary Trigger | §3.13.1d | SUB FE (FULL_CONFIG_RESPONSE) offset 0x0109 (read); write path not yet isolated |
uint8 (bool) | 0x00=disabled, 0x01=enabled; confirmed 2026-03-11 |
| 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 0x10in payload = literal0x10✅
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
0x02used 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
- Global byte counting ≠ frame counting.
0x10 0x02appears inside payloads. Only state machine transitions produce valid frame boundaries. - STX count ≠ frame count. Only STX→ETX pairs within proper state transitions count.
- EOF mid-frame is normal. Capture termination during active traffic produces an incomplete trailing frame. Not an error.
- 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.