doc: added priliminary protocol reference guide. WIP

This commit is contained in:
serversdwn
2026-02-25 17:12:53 -05:00
parent 5e1a532544
commit 004a2d87a3

View File

@@ -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 130260 (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.*