543 lines
22 KiB
Markdown
543 lines
22 KiB
Markdown
# 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).
|
||
> All findings derived from live packet capture. No vendor documentation was used.
|
||
> **Certainty Ratings:** ✅ CONFIRMED | 🔶 INFERRED | ❓ SPECULATIVE
|
||
|
||
---
|
||
|
||
## 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). |
|
||
|
||
---
|
||
|
||
## 1. Physical Layer
|
||
|
||
| Parameter | Value | Certainty |
|
||
|---|---|---|
|
||
| Interface | RS-232 serial | ✅ CONFIRMED |
|
||
| Baud rate | 38400 | ✅ CONFIRMED (from bridge log header) |
|
||
| Data bits | 8 | ✅ CONFIRMED (standard for this baud/era) |
|
||
| Parity | None | 🔶 INFERRED (no parity errors observed) |
|
||
| Stop bits | 1 | 🔶 INFERRED (standard assumption) |
|
||
| Flow control | None (no RTS/CTS activity) | 🔶 INFERRED |
|
||
|
||
---
|
||
|
||
## 2. Frame Structure
|
||
> ⚠️ **2026-02-26 — CORRECTED:** Previous version incorrectly identified `0x41` as STX and `0x02`/`0x03` as bare frame delimiters. The protocol uses proper **DLE framing**. See below.
|
||
|
||
Every message follows this structure:
|
||
|
||
```
|
||
[ACK] [DLE+STX] [PAYLOAD...] [CHECKSUM] [DLE+ETX]
|
||
0x41 0x10 0x02 N bytes 1 byte 0x10 0x03
|
||
```
|
||
|
||
### Special Byte Definitions
|
||
|
||
| Token | Raw Bytes | Meaning | Certainty |
|
||
|---|---|---|---|
|
||
| ACK | `0x41` (ASCII `'A'`) | Acknowledgment / ready token. Standalone single byte. Sent before every frame by both sides. | ✅ CONFIRMED |
|
||
| DLE | `0x10` | Data Link Escape. Prefixes the next byte to give it special meaning. | ✅ CONFIRMED — 2026-02-26 |
|
||
| STX | `0x10 0x02` | DLE+STX = Start of frame (two-byte sequence) | ✅ CONFIRMED — 2026-02-26 |
|
||
| ETX | `0x10 0x03` | DLE+ETX = End of frame (two-byte sequence) | ✅ CONFIRMED — 2026-02-26 |
|
||
| CHECKSUM | 1 byte | 8-bit sum of de-stuffed payload bytes, modulo 256. Sits between payload and DLE+ETX. | ✅ CONFIRMED |
|
||
|
||
### DLE Byte Stuffing Rule
|
||
> ✅ CONFIRMED — 2026-02-26
|
||
|
||
Any `0x10` byte appearing **naturally in the payload data** is escaped by doubling it: `0x10` → `0x10 0x10`. This prevents the parser from confusing real data with frame control sequences.
|
||
|
||
- **Transmit:** Replace every `0x10` in payload with `0x10 0x10`
|
||
- **Receive:** Replace every `0x10 0x10` in the frame body with a single `0x10`
|
||
|
||
| Sequence on wire | Meaning |
|
||
|---|---|
|
||
| `0x10 0x02` | Frame START — only valid at beginning |
|
||
| `0x10 0x03` | Frame END |
|
||
| `0x10 0x10` | Escaped literal `0x10` byte in payload data |
|
||
| Any other `0x10 0xXX` | Protocol error / undefined |
|
||
|
||
### Frame Parser Notes
|
||
|
||
- The `0x41` ACK **always arrives in a separate `read()` call** before the frame body due to RS-232 inter-byte timing at 38400 baud. This is normal.
|
||
- Your parser must be **stateful and buffered** — read byte by byte, accumulate between DLE+STX and DLE+ETX. Never assume one `read()` = one frame.
|
||
- Checksum is computed on the **de-stuffed** payload, not the raw wire bytes.
|
||
- The ACK and DLE+STX are **not** included in the checksum.
|
||
|
||
### Checksum Verification Example
|
||
|
||
Raw frame on wire (with ACK and DLE framing):
|
||
```
|
||
41 10 02 | 10 10 00 5B 00 00 00 00 00 00 00 00 00 00 00 00 00 | 6B | 10 03
|
||
^ACK^^STX^ ^---------- stuffed payload (0x10→0x10 0x10) ------^ ^chk^ ^ETX^
|
||
```
|
||
|
||
After de-stuffing (`0x10 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 `0x10`. Device address or bus ID. Also stuffed on wire. | 🔶 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 |
|
||
|
||
> 🔶 **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 |
|
||
|
||
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.
|
||
|
||
| 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 |
|
||
|
||
---
|
||
|
||
## 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
|
||
79 11 20 — 3 trailing bytes (HW revision? calibration ID?) ❓
|
||
```
|
||
|
||
### 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 38 2E 31 37 00` | `"S338.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
|
||
|
||
```
|
||
Offset 0x00: 00 58 09 — Total index size or record count ❓
|
||
Offset 0x03: 00 00 00 01 — Possibly stored event count = 1 ❓
|
||
Offset 0x07: 01 07 CB 00 06 1E — Timestamp of event 1 (see §8)
|
||
Offset 0x0D: 01 07 CB 00 14 00 — Timestamp of event 2 (see §8)
|
||
Offset 0x13: 00 00 00 17 3B — Unknown ❓
|
||
Offset 0x50: 10 02 FF DC — Sub-block pointer or data segment header ❓
|
||
```
|
||
|
||
### 7.5 Full Waveform Record (SUB F3) — 0xD2 bytes × 2 pages
|
||
|
||
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
|
||
```
|
||
|
||
Peak values as IEEE 754 big-endian floats — event 1:
|
||
```
|
||
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.
|
||
|
||
### Parser State Machine
|
||
|
||
```
|
||
IDLE:
|
||
receive 0x41 → emit ACK event, stay IDLE
|
||
receive 0x10 → goto WAIT_STX
|
||
|
||
WAIT_STX:
|
||
receive 0x02 → frame started, goto IN_FRAME
|
||
receive anything → error, goto IDLE
|
||
|
||
IN_FRAME:
|
||
receive 0x10 → goto ESCAPE
|
||
receive any byte → append to buffer, stay IN_FRAME
|
||
|
||
ESCAPE:
|
||
receive 0x03 → frame complete — validate checksum, process buffer, goto IDLE
|
||
receive 0x10 → append single 0x10 to buffer, goto IN_FRAME (stuffed literal)
|
||
receive 0x02 → error (nested STX), goto IDLE
|
||
receive anything → error, goto IDLE
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Checksum Reference Implementation
|
||
> ⚠️ **Updated 2026-02-26** — Rewritten for correct DLE framing and byte stuffing.
|
||
|
||
```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 | S338.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. Open Questions / Still Needs Cracking
|
||
|
||
| Question | Priority | Added |
|
||
|---|---|---|
|
||
| Byte at timestamp offset 3 — hours, minutes, or padding? | MEDIUM | 2026-02-26 |
|
||
| Meaning of `79 11 20` trailing bytes in serial number response | MEDIUM | 2026-02-26 |
|
||
| Full channel ID mapping in SUB `5A` stream (01/02/03/04 → which sensor?) | MEDIUM | 2026-02-26 |
|
||
| Write / set commands for device configuration | MEDIUM | 2026-02-26 |
|
||
| Full trigger configuration field mapping (SUB `1C` response) | 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 |
|
||
| MicL channel units — PSI, dB linear, or dB(L)? | LOW | 2026-02-26 |
|
||
|
||
---
|
||
|
||
*All findings reverse-engineered from live RS-232 bridge captures. No Instantel proprietary documentation was referenced or used.*
|
||
*This is a living document — append changelog entries and timestamps as new findings are confirmed or corrected.*
|