# 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-03 | Β§2 Frame Structure | **UPDATED:** Documented BW/S3 framing asymmetry. BW uses bare STX (`0x02`); S3 uses DLE+STX (`0x10 0x02`). ETX initially believed symmetric β€” see correction below. | | 2026-03-03 | Β§2 Frame Structure | **CORRECTED:** ETX is also asymmetric. BW uses bare ETX (`0x03`); S3 uses DLE+ETX (`0x10 0x03`). Confirmed via checksum validation: 91/98 BW frames pass with bare `0x03` as terminator. All `10 03` sequences in `raw_bw.bin` are in-payload data, never followed by `41 02` (next frame start). Full confirmed grammar: BW=`02`...`03`, S3=`10 02`...`10 03`. Both sides stuff literal `0x10` as `10 10`. This is the formally confirmed link-layer grammar. | | 2026-03-03 | Appendix A | **CORRECTED:** Previous entry stated logger strips DLE from ETX. This was wrong β€” applied to older logger only. `s3_bridge v0.5.0+` is lossless. Section rewritten to reflect current flat raw dump format. | | 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-03 | Β§2, Β§10, Appendix C | **MILESTONE β€” Link-layer grammar formally confirmed.** BW start marker is `41 02` (ACK+STX as a unit β€” bare `02` alone is not sufficient). BW frame boundary is structural sequence `03 41 02`. ETX lookahead: bare `03` only accepted as ETX when followed by `41 02` or at EOF. Checksum confirmed split: small frames use SUM8, large frames use unknown algorithm. Checksum is semantic, not a framing primitive. `s3_parser.py` v0.2.2 implements dual `--mode s3/bw`. | --- ## 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. > ⚠️ **2026-03-03 β€” UPDATED:** Frame start AND end are both asymmetric by direction. See confirmed grammar below. ### Confirmed Link-Layer Grammar βœ… CONFIRMED β€” 2026-03-03 The two sides of the connection use **fully asymmetric framing**. DLE stuffing applies on both sides. | Direction | Start marker | End marker | Stuffing | Notes | |---|---|---|---|---| | S3 β†’ BW (device) | `10 02` (DLE+STX) | `10 03` (DLE+ETX) | `10` β†’ `10 10` | Full DLE framing | | BW β†’ S3 (Blastware) | `41 02` (ACK+STX) | `03` (bare ETX) | `10` β†’ `10 10` | ACK is part of start marker | **BW start marker:** `41 02` is treated as a single two-byte start signature. Bare `02` alone is **not** sufficient to start a BW frame β€” it must be preceded by `41` (ACK). This prevents false triggering on `02` bytes inside payload data. **BW ETX rule:** Bare `03` is accepted as frame end **only** when followed by `41 02` (next frame's ACK+STX) or at EOF. The structural boundary pattern is: ``` ... [payload] 03 41 02 [next payload] ... ``` In-payload `03` bytes are preserved as data when not followed by `41 02`. **Evidence:** - 98/98 BW frames extracted from `raw_bw.bin` using `41 02` start + `03 41 02` structural boundary - 91/98 small BW frames validate SUM8 checksum; 7 large config/write frames do not match any known checksum algorithm - All `10 03` sequences in `raw_bw.bin` confirmed as in-payload data (none followed by `41 02`) - `s3_parser.py v0.2.2` implements both modes; BW ETX lookahead confirmed working **Checksum is NOT a framing primitive:** - Small frames (e.g. keepalive SUB `5B`): SUM8 validates consistently - Large frames (e.g. SUB `71` config writes): checksum algorithm unknown β€” does not match SUM8, CRC-16/IBM, CRC-16/CCITT-FALSE, or CRC-16/X25 - Frame boundaries are determined structurally; checksum validation is a semantic-layer concern only ### Frame Structure by Direction **S3 β†’ BW (device responses):** ``` [ACK] [DLE+STX] [PAYLOAD...] [CHECKSUM] [DLE+ETX] 0x41 0x10 0x02 N bytes 1 byte 0x10 0x03 ``` **BW β†’ S3 (Blastware commands):** ``` [ACK] [STX] [PAYLOAD...] [CHECKSUM] [ETX] 0x41 0x02 N bytes 1 byte 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. Used for stuffing on both sides; also prefixes STX/ETX on S3 side only. | βœ… CONFIRMED β€” 2026-02-26 | | STX (S3) | `0x10 0x02` | DLE+STX = Start of frame sent by device | βœ… CONFIRMED β€” 2026-02-26 | | STX (BW) | `0x02` | Bare STX = Start of frame sent by Blastware | βœ… CONFIRMED β€” 2026-03-03 | | ETX (S3) | `0x10 0x03` | DLE+ETX = End of frame sent by device | βœ… CONFIRMED β€” 2026-02-26 | | ETX (BW) | `0x03` | Bare ETX = End of frame sent by Blastware | βœ… CONFIRMED β€” 2026-03-03 | | CHECKSUM | 1 byte | 8-bit sum of de-stuffed payload bytes, modulo 256. Sits between payload and 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 applies on **both sides** of the connection. - **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 | S3 context | BW context | |---|---|---| | `0x10 0x02` | Frame START | Stuffed `0x10` + payload `0x02` | | `0x10 0x03` | Frame END | Stuffed `0x10` + payload `0x03` | | `0x10 0x10` | Escaped literal `0x10` | Escaped literal `0x10` | | `0x02` | Payload byte | Frame START | | `0x03` | Payload byte | Frame END | ### 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 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 `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 `0x60`–`0x83` range. The device acknowledges each write with a short response frame containing no data payload. **Pattern:** Write SUB = Read SUB + `0x60` (e.g. `0x08` EVENT INDEX READ β†’ `0x68` EVENT INDEX WRITE). | SUB | Name | Description | Response SUB | Certainty | |---|---|---|---|---| | `68` | **EVENT INDEX WRITE** | Writes event index block (mirrors SUB `08` read). Contains event count and timestamps. | `97` | βœ… CONFIRMED | | `69` | **WAVEFORM DATA WRITE** | Writes large waveform/channel data block (0xCA bytes, mirrors SUB `09`). | `96` | βœ… CONFIRMED | | `71` | **COMPLIANCE / PROJECT STRINGS WRITE** | Writes compliance config and all project string fields. Contains setup name, project, client, operator, sensor location, and extended notes. Also contains channel scaling floats and `0x082A` threshold value. | `8E` | βœ… CONFIRMED | | `72` | **WRITE CONFIRM A** | Short frame, no data. Likely commit/confirm step after `71`. | `8D` | βœ… CONFIRMED | | `73` | **WRITE CONFIRM B** | Short frame, no data. | `8C` | βœ… CONFIRMED | | `74` | **WRITE CONFIRM C** | Short frame, no data. | `8B` | βœ… CONFIRMED | | `82` | **TRIGGER CONFIG WRITE** | Writes trigger config block (0x1C bytes, mirrors SUB `1C` read). | `7D` | βœ… CONFIRMED | | `83` | **TRIGGER WRITE CONFIRM** | Short frame, no data. Likely commit step after `82`. | `7C` | βœ… CONFIRMED | **Write response SUB pairs** follow the same `0xFF - request` rule: | Write SUB | Response SUB | |---|---| | `68` | `97` | | `69` | `96` | | `71` | `8E` | | `72` | `8D` | | `73` | `8C` | | `74` | `8B` | | `82` | `7D` | | `83` | `7C` | --- ## 6. Session Startup Sequence ``` 1. Device powers on / resets 2. S3 β†’ "Operating System" (raw ASCII, no DLE framing β€” UART boot string) 3. BW β†’ 0x41 + POLL frame (SUB 5B) 4. S3 β†’ 0x41 + POLL RESPONSE (SUB A4, reports data length = 0x30) 5. BW β†’ 0x41 + POLL frame (SUB 5B, offset = 0x30) 6. S3 β†’ 0x41 + POLL RESPONSE with data: "Instantel" + "MiniMate Plus" 7. [Poll loop repeats 3–5Γ— during initialization] 8. BW β†’ SUB 06 β†’ channel config read 9. BW β†’ SUB 15 β†’ serial number 10. BW β†’ SUB 01 β†’ full config block 11. BW β†’ SUB 08 β†’ event index 12. BW β†’ SUB 1E β†’ first event header 13. BW β†’ SUB 0A β†’ waveform header (timestamp-keyed) 14. BW β†’ SUB 0C β†’ full waveform record download (2 pages) 15. BW β†’ SUB 1F β†’ advance / close event 16. [Repeat steps 12–15 for each stored event] 17. BW β†’ SUB 5A β†’ bulk raw waveform stream 18. Poll loop resumes (SUB 5B keepalive every ~80ms) ``` --- ## 7. Known Data Payloads ### 7.1 Poll Response (SUB A4) β€” Device Identity Block Two-step read. Data payload = 0x30 bytes. ``` Offset 0x00: 0x08 β€” string length prefix Offset 0x01: "Instantel" β€” manufacturer (null-padded to ~20 bytes) Offset 0x15: "MiniMate Plus" β€” model name (null-padded to ~20 bytes) ``` Raw payload (after de-stuffing): ``` 00 00 00 08 49 6E 73 74 61 6E 74 65 6C 00 00 00 00 00 00 00 00 00 00 00 00 00 4D 69 6E 69 4D 61 74 65 20 50 6C 75 73 00 00 00 00 00 00 00 00 00 ``` ### 7.2 Serial Number Response (SUB EA) Data payload = 0x0A bytes: ``` "BE18189\x00" β€” 7 ASCII bytes + null terminator (8 bytes) 79 11 β€” 2 trailing bytes ``` | Trailing Byte | Value (Unit 1) | Value (Unit 2) | Meaning | Certainty | |---|---|---|---|---| | `trail[0]` | `0x79` | `0x70` | Unit-specific β€” factory calibration ID or HW stamp? | ❓ SPECULATIVE | | `trail[1]` | `0x11` | `0x11` | Firmware minor version β€” `0x11` = 17 = `S337.**17**` | βœ… CONFIRMED β€” 2026-02-26 | Two-unit comparison data: ``` Unit 1: serial="BE18189" trail=79 11 firmware=S337.17 Unit 2: serial="BE11529" trail=70 11 firmware=S337.17 ``` > βœ… **2026-02-26 β€” CORRECTED:** Previously documented as `79 11 20` (3 bytes). `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** (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 `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 β‰ˆ `0x000A`–`0x000B` - `00 00` β€” separator / padding - `01` β€” unknown flag byte - 3-byte partial IEEE 754 float β€” peak value for this sample window. `0x43` prefix = 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 `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. Both sides stuff literal `0x10` bytes as `0x10 0x10`. The framing delimiters differ by direction β€” see Β§2. ### Parser State Machine β€” S3β†’BW direction (device responses) Trigger on DLE+STX, terminate on DLE+ETX. ``` IDLE: receive 0x41 β†’ emit ACK event, stay IDLE receive 0x10 β†’ goto WAIT_STX receive anything β†’ discard, stay IDLE 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 anything β†’ append DLE + byte to buffer (recovery), goto IN_FRAME ``` ### Parser State Machine β€” BWβ†’S3 direction (Blastware commands) Trigger on `41 02` (ACK+STX as a unit). ETX accepted only when followed by `41 02` or at EOF. ``` IDLE: receive 0x41 + next==0x02 β†’ frame started (consume both), goto IN_FRAME receive anything β†’ discard, stay IDLE IN_FRAME: receive 0x10 β†’ goto ESCAPE receive 0x03 + lookahead==0x41 0x02, or EOF β†’ frame complete β€” validate checksum, process buffer, goto IDLE receive 0x03 (no lookahead) β†’ append to buffer (in-payload 03), stay IN_FRAME receive any byte β†’ append to buffer, stay IN_FRAME ESCAPE: receive 0x10 β†’ append single 0x10 to buffer, goto IN_FRAME (stuffed literal) receive anything β†’ append DLE + byte to buffer (recovery), goto IN_FRAME ``` **Architectural note:** Checksum validation is optional and informational only. Frame boundaries are determined structurally via the `03 41 02` sequence β€” never by checksum gating. --- ## 11. Checksum Reference Implementation > ⚠️ **Updated 2026-02-26** β€” Rewritten for correct DLE framing and byte stuffing. ```python DLE = 0x10 STX = 0x02 ETX = 0x03 ACK = 0x41 def stuff(data: bytes) -> bytes: """Escape all 0x10 bytes in payload as 0x10 0x10 for transmission.""" out = [] for b in data: out.append(b) if b == DLE: out.append(DLE) # double it return bytes(out) def destuff(data: bytes) -> bytes: """Remove DLE stuffing from received payload bytes.""" out = [] i = 0 while i < len(data): if data[i] == DLE and i + 1 < len(data) and data[i + 1] == DLE: out.append(DLE) i += 2 else: out.append(data[i]) i += 1 return bytes(out) def calc_checksum(payload: bytes) -> int: """ 8-bit sum of de-stuffed payload bytes, modulo 256. Pass the original (pre-stuff) payload β€” not the wire bytes. """ return sum(payload) & 0xFF def build_frame(payload: bytes) -> bytes: """ Build a complete on-wire frame from a raw payload. Output: ACK + DLE+STX + stuffed_payload + checksum + DLE+ETX """ chk = calc_checksum(payload) stuffed = stuff(payload) return bytes([ACK, DLE, STX]) + stuffed + bytes([chk, DLE, ETX]) def parse_frame(raw: bytes) -> bytes | None: """ Parse and validate a raw on-wire frame. Accepts input starting with ACK (0x41) or DLE+STX (0x10 0x02). Returns de-stuffed payload bytes on success, None on any error. """ # Strip optional leading ACK if raw and raw[0] == ACK: raw = raw[1:] # Validate frame delimiters if len(raw) < 5: return None if raw[0] != DLE or raw[1] != STX: return None if raw[-2] != DLE or raw[-1] != ETX: return None # Extract: everything between DLE+STX and DLE+ETX inner = raw[2:-2] chk_received = inner[-1] stuffed_payload = inner[:-1] # De-stuff and validate checksum payload = destuff(stuffed_payload) if calc_checksum(payload) != chk_received: return None return payload # ── Example: build a POLL request (SUB 5B) ──────────────────────────────────── poll_payload = bytes([ 0x02, # CMD 0x10, 0x10, # DLE, ADDR (each stuffed to 0x10 0x10 on wire) 0x00, # FLAGS 0x5B, # SUB: POLL 0x00, 0x00, # OFFSET_HI, OFFSET_LO 0x00, 0x00, 0x00, 0x00, # padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]) frame = build_frame(poll_payload) # Wire output: 41 10 02 02 10 10 10 10 00 5B 00 00 00 00 00 00 00 00 00 00 6B 10 03 ``` --- ## 12. Recommended Implementation Sequence Build in this order β€” each step is independently testable: 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 > ⚠️ **This section describes tooling behavior, not protocol semantics.** > **2026-03-03 β€” CORRECTED:** Previous version of this section incorrectly stated that `s3_bridge` strips DLE from ETX. This applied to an older logger version only. `s3_bridge v0.5.0+` is confirmed lossless. See Appendix C for full validation details. ### Current Format (v0.5.0+) βœ… CONFIRMED β€” 2026-03-03 As of `s3_bridge v0.5.0`, captures are produced as **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 | Every byte on the wire is written verbatim β€” no modification, no record headers, no timestamps. `0x10 0x03` (DLE+ETX) is preserved intact. **Practical impact for parsing:** - `raw_s3.bin`: trigger on `10 02`, terminate on `10 03` (state-machine-aware) - `raw_bw.bin`: trigger on `41 02` (ACK+STX as a unit), terminate on `03` only when followed by `41 02` or at EOF - Both: handle `10 10` β†’ literal `10` unstuffing - Use `s3_parser.py --mode s3` and `--mode bw` respectively --- ## 14. Open Questions / Still Needs Cracking | Question | Priority | Added | Notes | |---|---|---|---| | **Large BW frame checksum algorithm** β€” Small frames (SUB `5B` keepalive etc.) validate with SUM8. Large config/write frames (SUB `71`, `68`, `69` etc.) do not match SUM8, CRC-16/IBM, CRC-16/CCITT-FALSE, or CRC-16/X25 in either endianness. Unknown whether it covers full payload or excludes header bytes, or whether different SUB types use different algorithms. | MEDIUM | 2026-03-03 | NEW | | 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 | 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 | `.set` +16 confirmed | uint32 | 1–500 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? | 1–9 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:** - S3β†’BW: frame start `0x10 0x02`, frame end `0x10 0x03` βœ… - BWβ†’S3: frame start `0x02`, frame end `0x03` βœ… - Both sides: DLE stuffing `0x10 0x10` = 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 β€” Dual-Mode State Machine (`s3_parser.py v0.2.2`) 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:** **S3β†’BW parser states:** | 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 | Append DLE + byte (recovery) | IN_FRAME | **BWβ†’S3 parser states:** | Current State | Condition | Action | Next State | |---|---|---|---| | IDLE | byte==`41` AND next==`02` | Begin new frame (consume both) | IN_FRAME | | IDLE | any | Discard | IDLE | | IN_FRAME | byte==`03` AND (next two==`41 02` OR at EOF) | Frame complete, emit | IDLE | | IN_FRAME | byte==`03` (no lookahead match) | Append `03` to payload | IN_FRAME | | IN_FRAME | byte==`10` | β€” | AFTER_DLE | | IN_FRAME | other | Append to payload | IN_FRAME | | AFTER_DLE | byte==`10` | Append literal `10` | IN_FRAME | | AFTER_DLE | other | Append DLE + byte (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): - 98 complete frames via `41 02` start + `03 41 02` structural boundary detection - 91/98 small frames validate SUM8 checksum; 7 large config/write frames fail all known checksum algorithms - `41 02` confirmed as two-byte start signature; bare `02` alone is insufficient - Bare `03` ETX confirmed; in-payload `03` bytes correctly preserved via lookahead rule - 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. **Start marker must be the full signature.** In BW mode, `41 02` is the start marker β€” not bare `02`. Bare `02` appears in payload data and would cause phantom frames. 5. **ETX lookahead prevents false termination.** In BW mode, `03` is only a frame terminator when followed by `41 02` or at EOF. In-payload `03` bytes are common in large config frames. 6. **Framing is structural. Checksum is semantic.** Frame boundaries are determined by grammar patterns β€” never by checksum validation. Checksum belongs to the protocol decoder layer, not the framing layer. 7. **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.*