diff --git a/docs/instantel_protocol_reference.md b/docs/instantel_protocol_reference.md new file mode 100644 index 0000000..208bf57 --- /dev/null +++ b/docs/instantel_protocol_reference.md @@ -0,0 +1,423 @@ +# Instantel MiniMate Plus — Blastware RS-232 Protocol Reference v0.1.0 +### "The Rosetta Stone" +> Reverse-engineered via RS-232 serial bridge sniffing between Blastware software and an Instantel MiniMate Plus seismograph (S/N: BE18189). +> All findings derived from live packet capture. No vendor documentation was used. +> **Certainty Ratings:** ✅ CONFIRMED | 🔶 INFERRED | ❓ SPECULATIVE + +--- + +## 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 + +Every message follows this structure: + +``` +[ACK] [STX] [PAYLOAD...] [CHECKSUM] [ETX] + 0x41 0x02 N bytes 1 byte 0x03 +``` + +### Byte Definitions + +| Byte | Value | Meaning | Certainty | +|---|---|---|---| +| ACK | `0x41` (ASCII `'A'`) | Acknowledgment / ready token. Always sent as a standalone byte before a frame. Both sides send it. | ✅ CONFIRMED | +| STX | `0x02` | Start of frame | ✅ CONFIRMED | +| ETX | `0x03` | End of frame | ✅ CONFIRMED | +| CHECKSUM | 1 byte | 8-bit sum of all payload bytes between STX and CHECKSUM, modulo 256 | ✅ CONFIRMED (verified on multiple frames) | + +### Important Notes + +- The `0x41` ACK byte **always arrives in a separate read() call** before the frame due to RS-232 inter-byte timing at 38400 baud. +- Your frame parser **must buffer until ETX `0x03`** is received — do not treat each read() as a complete frame. +- The `0x41` and `0x02` are **not** part of the checksum calculation. Only bytes after STX and before CHECKSUM are summed. + +### Checksum Verification Example + +``` +Frame: 02 10 10 00 5B 00 00 00 00 00 00 00 00 00 00 00 00 00 6B 03 +Payload: 10+10+00+5B+00+00+00+00+00+00+00+00+00+00+00+00+00 = 0x6B ✅ +``` + +``` +Frame: 10 02 00 10 10 A4 00 00 00 00 00 00 30 00 00 00 00 00 00 E4 03 +Payload: 10+02+00+10+10+A4+00+00+00+00+00+00+30+00+00+00+00+00+00 = 0xE4 ✅ +``` + +--- + +## 3. Payload Structure + +The payload (bytes between STX `0x02` and CHECKSUM) has consistent internal structure: + +``` +[CMD] [??] [ADDR] [FLAGS] [SUB_CMD] [OFFSET_HI] [OFFSET_LO] [PARAMS × N bytes] +``` + +| Field | Position | Notes | Certainty | +|---|---|---|---| +| CMD | byte 0 | Command or response code | ✅ CONFIRMED | +| ?? | byte 1 | Always `0x10` in most frames. Possibly device class or protocol version | 🔶 INFERRED | +| ADDR | byte 2 | Always `0x10`. Possibly device address on bus | 🔶 INFERRED | +| 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 | + +--- + +## 4. Communication Pattern + +### 4.1 ACK Handshake (Every Transaction) + +``` +Side A → 0x41 (ACK: "I'm ready / got your last frame") +Side A → 02 [payload] [chk] 03 (actual frame) +Side B → 0x41 (ACK) +Side B → 02 [payload] [chk] 03 (response frame) +``` + +### 4.2 Two-Step Paged Read Pattern + +All data reads use a two-step length-then-data pattern: + +``` +Step 1 — Ask how much data exists: + BW → 0x41 + BW → 02 [CMD] 10 10 00 [SUB] 00 00 [00 00 ...] [chk] 03 (offset = 0) + +Step 2 — Device replies with total length: + S3 → 0x41 + S3 → 02 [RSP] 00 10 10 [SUB] 00 00 00 00 00 00 [LEN_HI] [LEN_LO] ... [chk] 03 + +Step 3 — Request data at that length as offset: + BW → 0x41 + BW → 02 [CMD] 10 10 00 [SUB] 00 00 [LEN_HI] [LEN_LO] [00 ...] [chk] 03 + +Step 4 — Device sends actual data payload: + S3 → 0x41 + S3 → 02 [RSP] 00 10 10 [SUB] 00 00 [LEN_HI] [LEN_LO] ... [DATA...] [chk] 03 +``` + +This two-step is used for **every** data command. It is not optional. + +--- + +## 5. Command Reference Table + +### 5.1 Request Commands (Blastware → S3) + +| CMD Byte | SUB Byte | Name | Description | Certainty | +|---|---|---|---|---| +| `02` | `5B` | **POLL / KEEPALIVE** | Sent continuously. Requests device identity/status. | ✅ CONFIRMED | +| `02` | `15` | **SERIAL NUMBER REQUEST** | Requests device serial number. | ✅ CONFIRMED | +| `02` | `01` | **FULL CONFIG READ** | Requests complete device configuration block (~0x98 bytes). Contains firmware version, model, serial, channel config, scaling. | ✅ CONFIRMED | +| `02` | `08` | **EVENT INDEX READ** | Requests the event record index (0x58 bytes). Contains event count and record pointers. | ✅ CONFIRMED | +| `02` | `06` | **CHANNEL CONFIG READ** | Requests channel configuration block (0x24 bytes). | ✅ CONFIRMED | +| `02` | `1C` | **TRIGGER CONFIG READ** | Requests trigger settings block (0x2C bytes). | ✅ CONFIRMED | +| `02` | `1E` | **EVENT HEADER READ** | Reads event header by index. Contains timestamp and sample rate info. | ✅ CONFIRMED | +| `02` | `0A` | **WAVEFORM HEADER READ** | Reads waveform header keyed by timestamp (0x30 bytes/page). | ✅ CONFIRMED | +| `02` | `0C` | **FULL WAVEFORM RECORD** | Downloads complete waveform record (0xD2 bytes/page). Contains project strings, PPV floats, channel labels. | ✅ CONFIRMED | +| `02` | `5A` | **BULK WAVEFORM STREAM** | Initiates bulk download of raw ADC sample data. Keyed by timestamp. Large multi-page transfer. | ✅ CONFIRMED | +| `02` | `24` | **WAVEFORM PAGE A?** | Paged waveform read, channel group A. | 🔶 INFERRED | +| `02` | `25` | **WAVEFORM PAGE B?** | Paged waveform read, channel group B. | 🔶 INFERRED | +| `02` | `1F` | **EVENT ADVANCE / ACK** | Sent after waveform download completes. Possibly advances record pointer or closes event. | 🔶 INFERRED | + +### 5.2 Response Commands (S3 → Blastware) + +Responses use the **bitwise complement** of the request CMD byte. Pattern: `CMD + RSP = 0xFF` (approximately). More precisely, response bytes observed: + +| Request CMD | Response CMD | Notes | Certainty | +|---|---|---|---| +| `02` + `5B` | `10 02` + `A4` | Poll response | ✅ CONFIRMED | +| `02` + `15` | `10 02` + `EA` | Serial number response | ✅ CONFIRMED | +| `02` + `01` | `10 02` + `FE` | Full config response | ✅ CONFIRMED | +| `02` + `08` | `10 02` + `F7` | Event index response | ✅ CONFIRMED | +| `02` + `06` | `10 02` + `F9` | Channel config response | ✅ CONFIRMED | +| `02` + `1C` | `10 02` + `E3` | Trigger config response | ✅ CONFIRMED | +| `02` + `1E` | `10 02` + `E1` | Event header response | ✅ CONFIRMED | +| `02` + `0A` | `10 02` + `F5` | Waveform header response | ✅ CONFIRMED | +| `02` + `0C` | `10 02` + `F3` | Full waveform record response | ✅ CONFIRMED | +| `02` + `5A` | `10 02` + `A5` | Bulk waveform stream response | ✅ CONFIRMED | +| `02` + `1F` | `10 02` + `E0` | Event advance response | 🔶 INFERRED | + +> 🔶 **INFERRED pattern:** Response SUB byte appears to be `(0xFF - request SUB)`. E.g., `0xFF - 0x5B = 0xA4`. This holds for all observed pairs and can be used to predict unknown response codes. + +--- + +## 6. Session Startup Sequence + +``` +1. Device powers on / resets +2. S3 → "Operating System" (raw ASCII string, no frame, no ACK) +3. BW → 0x41 + POLL (CMD 5B) +4. S3 → 0x41 + POLL RESPONSE (CMD A4, length=0x30) +5. BW → 0x41 + POLL (CMD 5B, offset=0x30) +6. S3 → 0x41 + POLL RESPONSE with data: "Instantel" + "MiniMate Plus" +7. [Poll loop repeats 3-5× during init] +8. BW issues CMD 06 → channel config +9. BW issues CMD 15 → serial number +10. BW issues CMD 01 → full config block +11. BW issues CMD 08 → event index +12. BW issues CMD 1E → first event header +13. BW issues CMD 0A → waveform header (timestamp-keyed) +14. BW issues CMD 0C → full waveform record download +15. BW issues CMD 1F → advance to next event +16. [Repeat 12-15 for each stored event] +17. BW issues CMD 5A → bulk raw waveform stream +18. Poll loop resumes (CMD 5B keepalive every ~80ms) +``` + +--- + +## 7. Known Data Payloads + +### 7.1 Poll Response (CMD A4) — Device Identity Block + +Triggered by two-step read with SUB `5B`. +Data payload (0x30 bytes) contains: + +``` +Offset 0x00: 0x08 — string length prefix +Offset 0x01: "Instantel" — manufacturer name (null-padded to 20 bytes) +Offset 0x15: "MiniMate Plus" — model name (null-padded to 20 bytes) +``` + +Raw example: +``` +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 (CMD EA) + +Data payload (0x0A bytes): + +``` +"BE18189" (7 ASCII bytes, null-terminated) +79 11 20 (3 trailing bytes — purpose unknown) ❓ SPECULATIVE: possibly manufacture date or calibration ID +``` + +### 7.3 Full Config Response (CMD FE) — 0x98 bytes + +This is the richest single payload. Confirmed fields: + +``` +Offset 0x00: "BE18189\x00" — Serial number +Offset 0x08: 79 11 — Unknown (hardware revision?) ❓ +Offset 0x0A: 00 01 — Unknown flags +Offset 0x0C: 10 02 9C 00 — Sub-block pointer or version tag +Offset 0x14: 3F 80 00 00 — IEEE 754 float = 1.0 (channel scale factor, Tran) +Offset 0x18: 41 00 00 00 — IEEE 754 float = 8.0 (channel scale factor, unknown) +Offset 0x1C: 3F 80 00 00 ×6 — IEEE 754 float = 1.0 each (remaining channel scales) +Offset 0x34: "S338.17\x00" — Firmware version string +Offset 0x3C: "10.72\x00" — Secondary firmware/DSP version +Offset 0x42: 07 E7 — 0x07E7 = 2023 decimal? Year field ❓ +Offset 0x44: "Instantel\x00" — Manufacturer (repeated) +Offset 0x60: "Instantel\x00" — Manufacturer (second copy) +Offset 0x6D: "MiniMate Plus" — Model name +``` + +### 7.4 Event Index Response (CMD F7) — 0x58 bytes + +``` +Offset 0x00: 00 58 09 — Total records or index size ❓ +Offset 0x03: 00 00 00 01 — Possibly record count = 1 ❓ +Offset 0x07: 01 07 CB 00 06 1E — Timestamp 1 (see Section 8) +Offset 0x0D: 01 07 CB 00 14 00 — Timestamp 2 +Offset 0x13: 00 00 00 17 3B — Unknown ❓ +... +Offset 0x50: 10 02 FF DC — Sub-block pointer ❓ +``` + +### 7.5 Full Waveform Record (CMD F3) — 0xD2 bytes × 2 pages + +Contains all metadata for a stored blast event. Confirmed ASCII strings: + +``` +"Project:" — label +"I-70 at SR 51-75978 - Loc 1 - 4256 SR51 " — project description +"BE18189" — serial number +"Histogram" — record type label +"Tran" — Transverse channel label +"Vert" — Vertical channel label +"Long" — Longitudinal channel label +"MicL" — Microphone / air overpressure channel label +``` + +Peak values as IEEE 754 big-endian floats (event 1): + +``` +Tran: 3D BB 45 7A = 0.0916 in/s (or mm/s depending on unit config) +Vert: 3D B9 56 E1 = 0.0907 in/s +Long: 3D 75 C2 7C = 0.0605 in/s +MicL: 39 BE 18 B8 = 0.000145 (PSI or linear dB) ❓ units unconfirmed +``` + +Peak values (event 2): + +``` +Tran: 3D 56 CB B9 = 0.0521 in/s +Vert: 3C F5 C2 7C = 0.0300 in/s +Long: 3C F5 C2 7C = 0.0300 in/s +MicL: 39 64 1D AA = 0.0000875 +``` + +### 7.6 Bulk Waveform Stream (CMD A5) — Raw ADC Sample Records + +Each repeating sample record structure (🔶 INFERRED): + +``` +[CH_ID] [S0_HI] [S0_LO] [S1_HI] [S1_LO] ... [S8_HI] [S8_LO] [00 00] [01] [FLOAT_HI] [FLOAT_MID] [FLOAT_LO] + 01 00 0A 00 0B ... 43 xx xx +``` + +- `CH_ID` — Channel identifier (01 = Tran, 02 = Vert, etc.) 🔶 INFERRED +- 9× signed 16-bit big-endian ADC samples (~0x000A = baseline noise) +- Trailing IEEE 754 float — appears to be the peak value for this sample window +- `0x43` prefix on float = value in range 130–260 (scaled mm/s or in/s ×1000) ❓ + +Sample record appears to be **fixed width**. At 1024 sps, 9 samples ≈ 8.8ms per record. ❓ SPECULATIVE — sample rate unconfirmed from this data alone. + +--- + +## 8. Timestamp Format + +Timestamps appear in event headers and waveform keys. Observed example: + +``` +01 07 CB 00 06 1E +``` + +Best guess decode (🔶 INFERRED): + +``` +01 — record type / validity flag? +07 CB — 0x07CB = 1995 ← suspicious, may be a different epoch or BCD +00 — padding? +06 — month (June) +1E — day (30) +``` + +> ❓ **SPECULATIVE:** The year `0x07CB = 1995` seems wrong for modern equipment. Possible interpretations: +> - BCD encoded: `07 CB` → not valid BCD +> - Epoch offset from some base year +> - Or the full timestamp uses more bytes and the year field is elsewhere +> +> This needs more capture samples with known event dates to crack definitively. + +--- + +## 9. Out-of-Band / Non-Frame Messages + +Some messages arrive as **raw ASCII with no framing** at all: + +| Message | Direction | Trigger | Certainty | +|---|---|---|---| +| `"Operating System"` | S3 → BW | Device boot/reset/UART init | ✅ CONFIRMED | + +> These raw ASCII bursts indicate the device's firmware is printing a boot string to the UART before switching to binary protocol mode. Your implementation should handle these gracefully — ignore any non-`0x02` STX bytes during the connection phase until the poll handshake succeeds. + +--- + +## 10. Byte Escape / Stuffing + +> ❓ **SPECULATIVE / UNKNOWN** + +The payload bytes `0x02` and `0x03` (STX/ETX) appear naturally in data payloads. It is not yet confirmed whether the protocol uses any byte stuffing or escaping to handle these collisions. In many embedded protocols these are escaped as `0x10 0x02` and `0x10 0x03` (DLE stuffing). The byte `0x10` appears frequently in payloads and may be a DLE (Data Link Escape) byte. + +> **Recommendation:** When building the frame parser, watch for `0x10` preceding `0x02` or `0x03` inside a frame — this may indicate DLE stuffing is in use. + +--- + +## 11. Checksum Reference Implementation + +```python +def calc_checksum(payload: bytes) -> int: + """ + 8-bit sum of all payload bytes (between STX and CHKSUM), modulo 256. + Do NOT include STX (0x02), ACK (0x41), CHKSUM, or ETX (0x03). + """ + return sum(payload) & 0xFF + +def build_frame(payload: bytes) -> bytes: + """Build a complete frame: ACK + STX + payload + checksum + ETX""" + chk = calc_checksum(payload) + return bytes([0x41, 0x02]) + payload + bytes([chk, 0x03]) + +def parse_frame(raw: bytes) -> bytes | None: + """ + Extract and validate payload from a raw frame. + raw should start at STX (0x02) — strip leading 0x41 ACK before calling. + Returns payload bytes if checksum valid, None otherwise. + """ + if raw[0] != 0x02 or raw[-1] != 0x03: + return None + payload = raw[1:-2] + chk = raw[-2] + if calc_checksum(payload) != chk: + return None + return payload +``` + +--- + +## 12. Recommended Implementation Sequence + +Build in this order — each step is independently testable: + +1. **Serial framer** — buffered reader that accumulates bytes until ETX `0x03`, strips ACK, validates checksum +2. **`connect(port, baud=38400)`** — open port, wait for boot ASCII, send first POLL +3. **`identify()`** — CMD `5B` two-step read → returns manufacturer, model strings +4. **`get_serial()`** — CMD `15` two-step read → returns serial number string +5. **`get_config()`** — CMD `01` two-step read → returns full config dict (firmware, scaling, etc.) +6. **`get_event_count()`** — CMD `08` two-step read → returns number of stored events +7. **`get_event_header(index)`** — CMD `1E` read → returns timestamp, sample rate +8. **`get_event_record(timestamp)`** — CMD `0C` paginated read → returns PPV dict per channel +9. **`download_waveform(timestamp)`** — CMD `5A` bulk stream → returns raw ADC arrays per channel +10. **`set_*()`** write commands — not yet captured, requires additional sniffing + +--- + +## 13. Device Under Test + +| Field | Value | +|---|---| +| Manufacturer | Instantel | +| Model | MiniMate Plus | +| Serial Number | BE18189 | +| Firmware | S338.17 | +| DSP/Secondary FW | 10.72 | +| Channels | Tran, Vert, Long, MicL (4 channels) | +| Sample Rate | ~1024 sps (❓ INFERRED) | +| Bridge Setup | COM5 (Blastware) ↔ COM4 (Device), 38400 baud | + +--- + +## 14. Open Questions / Still Needs Cracking + +| Question | Priority | +|---|---| +| Exact timestamp encoding (year field especially) | HIGH | +| Whether DLE `0x10` byte stuffing is used for STX/ETX in payload | HIGH | +| Meaning of `79 11` bytes following serial number | MEDIUM | +| Write/set commands for device configuration | MEDIUM | +| Channel ID mapping in CMD `5A` stream (which byte = which sensor) | MEDIUM | +| Meaning of `0x07 E7` field in config block | LOW | +| Full trigger configuration field mapping | LOW | +| Whether `CMD 24`/`CMD 25` are distinct from `CMD 5A` or redundant | LOW | + +--- + +*Document generated from live RS-232 bridge captures. All findings are reverse-engineered. No Instantel proprietary documentation was referenced.*