fix: max_geo_range correctly identified as ADC Scale factor number.

This commit is contained in:
2026-04-17 19:43:45 -04:00
parent 969010b983
commit b23cf4bb50
6 changed files with 81 additions and 42 deletions
+6 -3
View File
@@ -4,6 +4,8 @@ Ground-up Python replacement for **Blastware**, Instantel's Windows-only softwar
managing MiniMate Plus seismographs. Connects over direct RS-232 or cellular modem managing MiniMate Plus seismographs. Connects over direct RS-232 or cellular modem
(Sierra Wireless RV50 / RV55). Current version: **v0.12.1**. (Sierra Wireless RV50 / RV55). Current version: **v0.12.1**.
When new information about the protocol is discovered, please update the instantel_protocol_reference.md with the findings in addition to this document
--- ---
## Project layout ## Project layout
@@ -362,7 +364,8 @@ Do NOT use fixed absolute offsets for sample_rate or record_time.
| record_time | float32 BE at anchor + 10 | | record_time | float32 BE at anchor + 10 |
| trigger_level_geo | float32 BE, located in channel block | | trigger_level_geo | float32 BE, located in channel block |
| alarm_level_geo | float32 BE, adjacent to trigger_level_geo | | alarm_level_geo | float32 BE, adjacent to trigger_level_geo |
| max_range_geo | float32 BE, adjacent to alarm_level_geo — **⚠ value and units UNKNOWN** (reads 6.206053 but doesn't match either UI range option; may not be the ADC full-scale — see GitHub issue) | | geo_hardware_constant (adc_scale_factor) | float32 BE at channel_label+28 — reads **6.206053** on BOTH tested units (BE11529 and BE18189); identical across all geo channels (Tran/Vert/Long) and all captures. **Confirmed 2026-04-17 from Interface Handbook §4.5**: this is the **ADC-to-velocity scale factor** = 1/sensitivity = (in/s per V). Firmware uses it as: `PPV (in/s) = ADC_voltage × 6.206053`. Cross-check: `1.61133 V (ADC full-scale) × 6.206053 = 10.000 in/s` (Normal range ✅). Stored field name: `max_range_geo`. Do NOT write this field — it is a hardware/firmware constant for the Instantel standard geophone. |
| max_range_geo | **uint8 at channel_label+20** — reads `0x01` on all tested captures (both units, all geo channels). Hypothesis: `0x01` = Normal 10.000 in/s, `0x00` = Sensitive 1.25 in/s. Unconfirmed — need a capture with 1.25 in/s range to verify. |
| setup_name | ASCII, null-padded, in cfg body | | setup_name | ASCII, null-padded, in cfg body |
| project / client / operator / sensor_location | ASCII, label-value pairs | | project / client / operator / sensor_location | ASCII, label-value pairs |
@@ -710,7 +713,7 @@ offsets in the raw 1A/E5 payload. Only fields with `✅` have confirmed offsets
- Geophone Type: Standard Triaxial / 4.5 Hz (bool/enum) - Geophone Type: Standard Triaxial / 4.5 Hz (bool/enum)
- Geophone Channels: Enable all geophones (bool), Trigger Source (bool) - Geophone Channels: Enable all geophones (bool), Trigger Source (bool)
- Chan 1-3 Trigger Level (float, in/s) ✅ (`trigger_level_geo`) - Chan 1-3 Trigger Level (float, in/s) ✅ (`trigger_level_geo`)
- Chan 1-3 Maximum Range: Normal 10.000 / 1.25 in/s (enum) ❓ (`max_range_geo` — offset found, reads 6.206053 which matches neither UI value; units and meaning unknown — do NOT use as ADC full-scale) - Chan 1-3 Maximum Range: Normal 10.000 / 1.25 in/s (enum) ❓ (`uint8` at `channel_label+20`; reads `0x01` on both tested units, both set to Normal 10.000 in/s; hypothesis: `0x01` = Normal (Gain=1, 10 in/s), `0x00` = Sensitive (Gain=8, 1.25 in/s) — UNCONFIRMED, need 1.25 in/s capture to verify). **Note: the float32 at `channel_label+28` (= 6.206053) is NOT this field** — it is the ADC-to-velocity scale factor (1/sensitivity, (in/s)/V); confirmed 2026-04-17 via Interface Handbook §4.5: 1.61133 V × 6.206053 = 10.000 in/s.
- Microphone Channels: Enable all microphones (bool), Trigger Source (bool) - Microphone Channels: Enable all microphones (bool), Trigger Source (bool)
- Chan 4 Trigger Level (dB or psi depending on units) - Chan 4 Trigger Level (dB or psi depending on units)
@@ -943,4 +946,4 @@ call-home.
- Locate "Sensor Check" byte in compliance config (need capture with Disabled vs Before-monitoring) - Locate "Sensor Check" byte in compliance config (need capture with Disabled vs Before-monitoring)
- Modem manager — push RV50/RV55 configs via Sierra Wireless API - Modem manager — push RV50/RV55 configs via Sierra Wireless API
- RV55 DCD/DTR issue — newer RV55 firmware doesn't assert DCD by default; units don't - RV55 DCD/DTR issue — newer RV55 firmware doesn't assert DCD by default; units don't
resume monitoring after call-home disconnect (`--restart-monitoring` flag deferred) resume monitoring after call-home disconnect (`--restart-monitoring` flag deferred)
+2 -1
View File
@@ -561,7 +561,8 @@ def _device_info_to_dict(d: DeviceInfo) -> dict:
"record_time": cc.record_time if cc else None, "record_time": cc.record_time if cc else None,
"trigger_level_geo": cc.trigger_level_geo if cc else None, "trigger_level_geo": cc.trigger_level_geo if cc else None,
"alarm_level_geo": cc.alarm_level_geo if cc else None, "alarm_level_geo": cc.alarm_level_geo if cc else None,
"max_range_geo": cc.max_range_geo if cc else None, "max_range_geo": cc.max_range_geo if cc else None, # hw constant 6.206053
"max_range_geo_enum": cc.max_range_geo_enum if cc else None, # 0x01=10in/s, 0x00=1.25in/s (unconfirmed)
"project": cc.project if cc else None, "project": cc.project if cc else None,
"client": cc.client if cc else None, "client": cc.client if cc else None,
"operator": cc.operator if cc else None, "operator": cc.operator if cc else None,
+20 -11
View File
@@ -36,7 +36,7 @@
| 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 | **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 0255 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 | §7.4 Event Index Block | **UPDATED:** Backlight confirmed as uint8 range 0255 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 | 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 → originally speculated as internal ADC full-scale constant, but this is NOT confirmed. Using it as ADC full-scale produces ~9× PPV overread. Meaning unknown. Downgraded to LOW 2026-03-02, re-escalated to HIGH 2026-04-16. | | 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 → originally speculated as internal ADC full-scale constant, but was NOT confirmed at this time. Using it directly as the range produces ~9× PPV overread. Meaning unknown. Downgraded to LOW 2026-03-02, re-escalated to HIGH 2026-04-16. **RESOLVED 2026-04-17 — see §7.6.2 and changelog entry.** |
| 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 | `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 | **NEW items added:** Trigger sample width (default=2), Auto Window (1-9 sec), Aux Trigger (enabled/disabled) — all confirmed settings from operator manual not yet mapped in protocol. |
| 2026-03-02 | §14 Open Questions | Monitoring LCD Cycle resolved — removed from open questions. | | 2026-03-02 | §14 Open Questions | Monitoring LCD Cycle resolved — removed from open questions. |
@@ -92,7 +92,7 @@
| 2026-04-06 | §7.8.4 | **NEW — 5A end-of-stream signalling confirmed.** After streaming all waveform chunks, the device sends exactly **1 raw byte** in response to the next chunk request, then goes silent for the full recv timeout. This byte is NOT a complete DLE-framed A5 response — the frame parser accumulates it as `bytes_fed=1` and never assembles a frame. This is the device's natural end-of-stream signal. Handling: on TimeoutError, if `bytes_fed > 0` AND prior chunks were received, treat as graceful end and proceed to the termination frame. A `bytes_fed=0` timeout with no prior chunks is a genuine transport failure and must still raise. | | 2026-04-06 | §7.8.4 | **NEW — 5A end-of-stream signalling confirmed.** After streaming all waveform chunks, the device sends exactly **1 raw byte** in response to the next chunk request, then goes silent for the full recv timeout. This byte is NOT a complete DLE-framed A5 response — the frame parser accumulates it as `bytes_fed=1` and never assembles a frame. This is the device's natural end-of-stream signal. Handling: on TimeoutError, if `bytes_fed > 0` AND prior chunks were received, treat as graceful end and proceed to the termination frame. A `bytes_fed=0` timeout with no prior chunks is a genuine transport failure and must still raise. |
| 2026-04-06 | §7.8.4 | **NEW — 5A chunk timing and count (empirical, BE11529 at 1024 sps).** Each chunk response arrives within ~1 second over TCP/cellular. A 9,306-sample event (≈9.1 s at 1024 sps) produces **35 chunks** before end-of-stream. Chunks 116 have varying data lengths (10361123 bytes); chunks 1735 are uniformly 1036 bytes each (post-event silence, all-zero ADC samples). Safe recv timeout for chunk loop: **10 s** (10× typical response time). Default transport timeout (120 s) results in a ~2-minute stall per event at end-of-stream. | | 2026-04-06 | §7.8.4 | **NEW — 5A chunk timing and count (empirical, BE11529 at 1024 sps).** Each chunk response arrives within ~1 second over TCP/cellular. A 9,306-sample event (≈9.1 s at 1024 sps) produces **35 chunks** before end-of-stream. Chunks 116 have varying data lengths (10361123 bytes); chunks 1735 are uniformly 1036 bytes each (post-event silence, all-zero ADC samples). Safe recv timeout for chunk loop: **10 s** (10× typical response time). Default transport timeout (120 s) results in a ~2-minute stall per event at end-of-stream. |
| 2026-04-06 | §7.8.3 | **KNOWN ISSUE — `_decode_a5_waveform` hardcoded fi==9 skip.** The decoder contains `elif fi == 9: continue` which was written for the 9-frame original blast capture where frame 9 was a device terminator. For streams with >9 frames (current device produces 35+), frame index 9 is live waveform data — this skip discards ~1,070 bytes (~133 sample-sets) per event. The terminator is now detected via `page_key == 0x0000`, not by frame index. The fi==9 skip should be removed. | | 2026-04-06 | §7.8.3 | **KNOWN ISSUE — `_decode_a5_waveform` hardcoded fi==9 skip.** The decoder contains `elif fi == 9: continue` which was written for the 9-frame original blast capture where frame 9 was a device terminator. For streams with >9 frames (current device produces 35+), frame index 9 is live waveform data — this skip discards ~1,070 bytes (~133 sample-sets) per event. The terminator is now detected via `page_key == 0x0000`, not by frame index. The fi==9 skip should be removed. |
| 2026-04-06 | §7.8 | **⚠ PARTIALLY INVALIDATED — ADC count-to-physical-unit conversion.** Raw waveform samples are signed 16-bit integers (counts). Conversion formula `value = counts × (range / 32767)` is believed correct, but the `range` value is UNKNOWN. The compliance config field labeled `max_range_geo` reads 6.206053 (bytes `40 C6 97 FD`), which does NOT match either user-selectable range shown in Blastware UI (1.25 or 10.000 in/s). The meaning and units of the 6.206053 value are unresolved — it may not be the ADC full-scale at all. See open question in §14. | | 2026-04-06 | §7.8 | **⚠ PARTIALLY INVALIDATED — ADC count-to-physical-unit conversion.** Raw waveform samples are signed 16-bit integers (counts). Conversion formula `value = counts × (range / 32767)` is believed correct, but the `range` value was UNKNOWN at time of writing. **UPDATED 2026-04-17:** `max_range_geo` = 6.206053 is confirmed as the ADC-to-velocity scale factor (inverse sensitivity, (in/s)/V). The correct conversion is therefore: `PPV (in/s) = counts × (1.61133 / 32767) × 6.206053` = `counts × 4.982e-5` in/s per count. The earlier ~9× overread from using 6.206053 directly as the range was because the range IS 1.61133 × 6.206053 = 10.000 in/s, not 6.206053. See §7.6.2 for the confirmed field layout. |
| 2026-04-08 | §5.1, §7.10, §12 | **NEW — Monitoring commands confirmed.** SUB 0x1C (monitor status), 0x96 (start monitoring), 0x97 (stop monitoring) all confirmed from 4-8-26/2ndtry capture. SESSION_RESET (`41 03`) required before POLL to wake a monitoring unit. | | 2026-04-08 | §5.1, §7.10, §12 | **NEW — Monitoring commands confirmed.** SUB 0x1C (monitor status), 0x96 (start monitoring), 0x97 (stop monitoring) all confirmed from 4-8-26/2ndtry capture. SESSION_RESET (`41 03`) required before POLL to wake a monitoring unit. |
| 2026-04-09 | §7.10 | **CORRECTED — monitoring flag and battery/memory offsets.** `section[1] == 0x10` is the monitoring flag (100% accurate across 144 data frames in 2ndtry capture). Previous note claiming `section[6]` was wrong — section[6] has device-specific non-binary values (0xea/0x07). Battery/memory offsets corrected: `section[-10:-8]` (battery×100), `section[-8:-4]` (memory_total), `section[-4:]` (memory_free). NOTE: `frame.data` has checksum stripped by parser — earlier offsets of `[-11:-9]`/`[-9:-5]`/`[-5:-1]` were wrong because they assumed a trailing checksum byte that isn't there. | | 2026-04-09 | §7.10 | **CORRECTED — monitoring flag and battery/memory offsets.** `section[1] == 0x10` is the monitoring flag (100% accurate across 144 data frames in 2ndtry capture). Previous note claiming `section[6]` was wrong — section[6] has device-specific non-binary values (0xea/0x07). Battery/memory offsets corrected: `section[-10:-8]` (battery×100), `section[-8:-4]` (memory_total), `section[-4:]` (memory_free). NOTE: `frame.data` has checksum stripped by parser — earlier offsets of `[-11:-9]`/`[-9:-5]`/`[-5:-1]` were wrong because they assumed a trailing checksum byte that isn't there. |
| 2026-04-08 | §7.10 | **NEW — SUBs 0x0E (channel sensor data) and 0x98 (trigger test) observed** in 4-8-26/sensor-check capture (Blastware "Unit Channel Test" comms check). SUB 0x0E: 2-step read with channel selector in `params[6:8]`, data length 0x0A per channel, RSP SUB = 0xF1. SUB 0x98: single probe frame with `params[0] = 0xFF`, RSP SUB = 0x67; sent twice per test cycle. Not yet implemented in SFM. | | 2026-04-08 | §7.10 | **NEW — SUBs 0x0E (channel sensor data) and 0x98 (trigger test) observed** in 4-8-26/sensor-check capture (Blastware "Unit Channel Test" comms check). SUB 0x0E: 2-step read with channel selector in `params[6:8]`, data length 0x0A per channel, RSP SUB = 0xF1. SUB 0x98: single probe frame with `params[0] = 0xFF`, RSP SUB = 0x67; sent twice per test cycle. Not yet implemented in SFM. |
@@ -103,6 +103,7 @@
| 2026-04-11 | §5.1 | **CONFIRMED — SUB 0x06 (CHANNEL CONFIG READ) now confirmed as event storage range.** Two-step read, data offset = 0x24 (36 bytes). Token=0xFE at params[7]. Last 8 bytes of response: first stored event key (bytes 8:4) and last stored event key (bytes 4:). Both equal `01110000` when device memory is empty. Used by Blastware to verify erase completion. | | 2026-04-11 | §5.1 | **CONFIRMED — SUB 0x06 (CHANNEL CONFIG READ) now confirmed as event storage range.** Two-step read, data offset = 0x24 (36 bytes). Token=0xFE at params[7]. Last 8 bytes of response: first stored event key (bytes 8:4) and last stored event key (bytes 4:). Both equal `01110000` when device memory is empty. Used by Blastware to verify erase completion. |
| 2026-04-11 | §7.11 (NEW) | **NEW — §7.11 Erase-All Protocol added.** Full wire sequence, SUB 0x06 storage range payload layout, post-erase key counter reset (resets to `0x01110000`). Confirmed from 4-11-26 MITM capture of live Blastware ACH session. | | 2026-04-11 | §7.11 (NEW) | **NEW — §7.11 Erase-All Protocol added.** Full wire sequence, SUB 0x06 storage range payload layout, post-erase key counter reset (resets to `0x01110000`). Confirmed from 4-11-26 MITM capture of live Blastware ACH session. |
| 2026-04-11 | §14.6 | **RESOLVED — ACH Session Lifecycle is no longer "Future".** `bridges/ach_server.py` fully implements inbound ACH: POLL handshake, device info, event download. State tracked via `ach_state.json` (key-based, with `max_downloaded_key` for post-erase detection). `--clear-after-download` flag added for the standard delete-after-upload workflow. | | 2026-04-11 | §14.6 | **RESOLVED — ACH Session Lifecycle is no longer "Future".** `bridges/ach_server.py` fully implements inbound ACH: POLL handshake, device info, event download. State tracked via `ach_state.json` (key-based, with `max_downloaded_key` for post-erase detection). `--clear-after-download` flag added for the standard delete-after-upload workflow. |
| 2026-04-17 | §7.6.2, §14 | **RESOLVED — Float 6.206053 at channel_label+28 is the ADC-to-velocity scale factor.** Confirmed from Series III Interface Handbook §4.5 formula: `Range (×1) = 1.61133 V / Sensitivity (V/unit)`. For the standard Instantel geophone at Normal range (10.000 in/s): Sensitivity = 1.61133 / 10 = 0.161133 V/(in/s). The stored value is the **inverse sensitivity** = 1/0.161133 = **6.206053 (in/s)/V**. Cross-check: 1.61133 V × 6.206053 = 10.000 in/s ✅. The firmware uses it as: `PPV (in/s) = ADC_voltage (V) × 6.206053`. Value is identical on all Instantel standard geophones — it is a hardware/firmware constant, NOT a user-configurable setting. Do NOT write this field. Open question §14 item "Max Geo Range float 6.2061" is now **RESOLVED**. |
--- ---
@@ -528,7 +529,7 @@ The SUB `1A` read response (`E5`) and SUB `71` write block contain per-channel t
| Field | Example bytes | Decoded | Certainty | | Field | Example bytes | Decoded | Certainty |
|---|---|---|---| |---|---|---|---|
| `[00 00]` | `00 00` | Separator / padding | 🔶 INFERRED | | `[00 00]` | `00 00` | Separator / padding | 🔶 INFERRED |
| Max range float | `40 C6 97 FD` | 6.206**value confirmed, meaning and units UNKNOWN** (does NOT match UI range options 1.25/10.000 in/s; not confirmed as ADC full-scale) | ❓ UNKNOWN | | ADC scale factor | `40 C6 97 FD` | **6.206053 (in/s)/V — CONFIRMED 2026-04-17.** This is the inverse sensitivity of the standard Instantel geophone = 1/0.161133. Interface Handbook §4.5: `Range = 1.61133 V × 6.206053 = 10.000 in/s`. Used by firmware: `PPV (in/s) = ADC_voltage × 6.206053`. Hardware constant — do NOT write. | ✅ CONFIRMED |
| `[00 00]` | `00 00` | Separator / padding | 🔶 INFERRED | | `[00 00]` | `00 00` | Separator / padding | 🔶 INFERRED |
| **Trigger level** | `3F 19 99 9A` | **0.600 in/s** — IEEE 754 BE float | ✅ CONFIRMED | | **Trigger level** | `3F 19 99 9A` | **0.600 in/s** — IEEE 754 BE float | ✅ CONFIRMED |
| Unit string | `69 6E 2E 00` | `"in.\0"` | ✅ CONFIRMED | | Unit string | `69 6E 2E 00` | `"in.\0"` | ✅ CONFIRMED |
@@ -655,7 +656,7 @@ offset size type value (Tran example) meaning
+10 2 uint16 0x0015 = 21 unknown +10 2 uint16 0x0015 = 21 unknown
+12 4 bytes 03 02 04 01 flags (recording mode etc.) +12 4 bytes 03 02 04 01 flags (recording mode etc.)
+16 4 uint32 0x00000003 record time in seconds ✅ CONFIRMED +16 4 uint32 0x00000003 record time in seconds ✅ CONFIRMED
+1A 4 float32 6.2061 ❓ UNKNOWN field — value 6.2061 confirmed; meaning/units unresolved (NOT confirmed as max range or ADC full-scale) +1A 4 float32 6.206053 ✅ CONFIRMED 2026-04-17 — ADC-to-velocity scale factor (= 1/sensitivity = (in/s)/V). Interface Handbook §4.5: Range = 1.61133 V × 6.206053 = 10.000 in/s (Normal range). Firmware uses: PPV (in/s) = ADC_voltage × 6.206053. Hardware constant — identical on all tested units. Do NOT write.
+1E 2 00 00 padding +1E 2 00 00 padding
+20 4 float32 0.6000 trigger level ✅ CONFIRMED +20 4 float32 0.6000 trigger level ✅ CONFIRMED
+24 4 char[4] "in.\0" / "psi\0" unit string (geo vs mic) +24 4 char[4] "in.\0" / "psi\0" unit string (geo vs mic)
@@ -1235,13 +1236,19 @@ TimeoutError caught:
Chunks with uniform 1,036-byte payload (chunks 1735 in the observed event) contain all-zero ADC samples — the device continues recording silence until the configured record time expires before terminating the stream. Chunks with uniform 1,036-byte payload (chunks 1735 in the observed event) contain all-zero ADC samples — the device continues recording silence until the configured record time expires before terminating the stream.
**ADC count-to-physical conversion — ⚠ SCALING UNKNOWN:** **ADC count-to-physical conversion — ✅ CONFIRMED 2026-04-17:**
Raw samples are signed 16-bit integers (32,768 to +32,767). Source: Interface Handbook §4.5.
**CONFIRMED 2026-04-17** — The `max_range_geo` field (float32 = 6.206053, bytes `40 C6 97 FD`) is the **ADC-to-velocity scale factor** (inverse sensitivity, (in/s)/V) for the standard Instantel geophone, confirmed from Interface Handbook §4.5. The correct conversion formula is:
Raw samples are signed 16-bit integers (32,768 to +32,767). The conversion formula is believed to be:
``` ```
value_in_s (in/s) = counts × (geo_range / 32767) PPV (in/s) = ADC_voltage (V) × 6.206053
= counts × (1.61133 / 32767) × 6.206053
= counts × 4.982e-5 (in/s per count at full scale)
``` ```
However, the correct value of `geo_range` is **unknown**. The compliance config field `max_range_geo` reads 6.206053 (`40 C6 97 FD`) which does NOT match either user-selectable range (1.25 or 10.000 in/s) and produces ~9× too large PPV values compared to the on-device 0C record. Do not use 6.206053 or 10.000 as the scale factor until this is resolved. See §14 open question. Mic channel uses psi units with its own range (also unresolved).
where `geo_range = 1.61133 V × 6.206053 = 10.000 in/s` is the Normal (Gain=1) full-scale range. The earlier ~9× overread was caused by mistakenly using 6.206053 as the range directly — it is actually the scale factor, and the range itself is `ADC_fullscale × scale_factor = 1.61133 × 6.206053 = 10.000 in/s`. Mic channel uses psi units with its own range (still unresolved).
**Known decoder issue — fi==9 hardcoded skip:** **Known decoder issue — fi==9 hardcoded skip:**
@@ -1267,7 +1274,8 @@ Fields visible in the Blastware "Compliance Setup" dialog. ✅ = byte offset co
| Geophone — Enable all | bool | ❓ | | Geophone — Enable all | bool | ❓ |
| Geophone — Trigger Source | bool | ❓ | | Geophone — Trigger Source | bool | ❓ |
| Chan 1-3 Trigger Level | float, in/s | ✅ `trigger_level_geo` | | Chan 1-3 Trigger Level | float, in/s | ✅ `trigger_level_geo` |
| Chan 1-3 Maximum Range | Normal 10.000 / 1.25 in/s | ❓ `max_range_geo` offset found, value=6.206053 — does NOT match UI values; meaning unknown | | Chan 1-3 Maximum Range (range selector enum) | Normal 10.000 / 1.25 in/s | ❓ `max_range_geo_enum` — uint8 at Tran+20; reads `0x01` on both tested units (Normal range); hypothesis `0x01`=Normal, `0x00`=Sensitive — UNCONFIRMED, need 1.25 in/s capture |
| Chan 1-3 ADC Scale Factor | 6.206053 (in/s)/V | ✅ `max_range_geo` at Tran+28 — **CONFIRMED 2026-04-17.** Inverse sensitivity = 1/0.161133. Interface Handbook §4.5: 1.61133 V × 6.206053 = 10.000 in/s. Hardware constant — do NOT write. |
| Microphone — Enable all | bool | ❓ | | Microphone — Enable all | bool | ❓ |
| Microphone — Trigger Source | bool | ❓ | | Microphone — Trigger Source | bool | ❓ |
| Chan 4 Trigger Level | float, dB or psi | ❓ | | Chan 4 Trigger Level | float, dB or psi | ❓ |
@@ -1933,7 +1941,7 @@ The `.bin` files produced by `s3_bridge` are **not raw wire bytes**. The logger
| **Auxiliary Trigger read location****RESOLVED:** SUB `FE` offset `0x0109`, uint8, `0x00`=disabled, `0x01`=enabled. Confirmed 2026-03-11 via controlled toggle capture. | RESOLVED | 2026-03-02 | Resolved 2026-03-11 | | **Auxiliary Trigger read location****RESOLVED:** SUB `FE` offset `0x0109`, uint8, `0x00`=disabled, `0x01`=enabled. Confirmed 2026-03-11 via controlled toggle capture. | RESOLVED | 2026-03-02 | Resolved 2026-03-11 |
| **Auxiliary Trigger write path** — Write command not yet captured in a clean session. Inner frame handshake visible in A4 (multiple WRITE_CONFIRM_RESPONSE SUBs appear, TRIGGER_CONFIG_RESPONSE removed), but the BW→S3 write command itself was in a partial session. Likely SUB `15` or similar. Deferred for clean capture. | LOW | 2026-03-11 | NEW | | **Auxiliary Trigger write path** — Write command not yet captured in a clean session. Inner frame handshake visible in A4 (multiple WRITE_CONFIRM_RESPONSE SUBs appear, TRIGGER_CONFIG_RESPONSE removed), but the BW→S3 write command itself was in a partial session. Likely SUB `15` or similar. Deferred for clean capture. | LOW | 2026-03-11 | NEW |
| ~~**SUB `6E` response to SUB `1C`**~~~~RESOLVED 2026-04-08: This was a misidentification.~~ The `1C → 6E` "exception" was misread — likely an inner A4 sub-frame. Confirmed from 4-8-26 capture (338 frames): SUB 0x1C always → 0xE3. No exceptions to the `0xFF SUB` rule are known. | RESOLVED | 2026-04-08 | CLOSED | | ~~**SUB `6E` response to SUB `1C`**~~~~RESOLVED 2026-04-08: This was a misidentification.~~ The `1C → 6E` "exception" was misread — likely an inner A4 sub-frame. Confirmed from 4-8-26 capture (338 frames): SUB 0x1C always → 0xE3. No exceptions to the `0xFF SUB` rule are known. | RESOLVED | 2026-04-08 | CLOSED |
| **Max Geo Range float 6.2061** — offset confirmed in channel block (`+1A`, `40 C6 97 FD`). Meaning and units are UNKNOWN. Value does NOT match either user-selectable range (1.25 / 10.0 in/s). Using it as ADC full-scale produces ~9× PPV overread vs on-device 0C values. Not simply metric vs imperial (25.4 factor doesn't reconcile). Needs investigation: examine surrounding channel block bytes, compare with a Blastware waveform CSV export to back-calculate the correct scale. Upgraded to HIGH priority. | HIGH | 2026-02-26 | Upgraded 2026-04-16 | | ~~**Max Geo Range float 6.2061**~~**RESOLVED 2026-04-17.** Confirmed as the **ADC-to-velocity scale factor** = inverse sensitivity = 1/0.161133 = **6.206053 (in/s)/V**. Source: Interface Handbook §4.5 formula `Range = 1.61133 V / Sensitivity`. For standard Instantel geo at Normal (Gain=1) range: Sensitivity = 1.61133/10 = 0.161133 V/(in/s), scale = 6.206053. Firmware: `PPV (in/s) = ADC_voltage × 6.206053`. The earlier ~9× overread was from using 6.206053 directly as range instead of as scale factor (range = 1.61133 V × 6.206053 = 10.000 in/s). Hardware constant — do NOT write. | RESOLVED | 2026-02-26 | Resolved 2026-04-17 |
| MicL channel units — **RESOLVED: psi**, confirmed from `.set` file unit string `"psi\0"` | RESOLVED | 2026-03-01 | | | 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 | | | 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 | | | Power save offset — **RESOLVED: +53 in event index data**, uint8, minutes | RESOLVED | 2026-03-02 | |
@@ -1962,7 +1970,8 @@ The `.bin` files produced by `s3_bridge` are **not raw wire bytes**. The logger
| Trigger Level (Mic) | §3.8.6 | Channel block, float | float32 BE | 100148 dB in 1 dB steps | | Trigger Level (Mic) | §3.8.6 | Channel block, float | float32 BE | 100148 dB in 1 dB steps |
| Alarm Level (Mic) | §3.9.10 | Channel block, float | float32 BE | higher than mic trigger | | Alarm Level (Mic) | §3.9.10 | Channel block, float | float32 BE | higher than mic trigger |
| Record Time | §3.8.9 | cfg anchor+10, float32 BE (wire); `.set` +16, uint32 LE (file) | float32 BE (wire) | 1105 s; confirmed 3→`40400000`, 5→`40A00000`, 8→`41000000`, 13→`41500000`. Use anchor §7.6.1/§7.6.3 — NOT fixed offset. | | Record Time | §3.8.9 | cfg anchor+10, float32 BE (wire); `.set` +16, uint32 LE (file) | float32 BE (wire) | 1105 s; confirmed 3→`40400000`, 5→`40A00000`, 8→`41000000`, 13→`41500000`. Use anchor §7.6.1/§7.6.3 — NOT fixed offset. |
| Max Geo Range | §3.8.4 | Channel block, float | float32 BE | ❓ UNKNOWN — value 6.2061 confirmed at offset, but meaning/units unresolved. Does NOT equal 1.25 or 10.0 in/s. Do NOT use as ADC full-scale. | | ADC Scale Factor (max_range_geo) | §3.8.4 / Interface Handbook §4.5 | Channel block, Tran+28, float32 BE | float32 BE = 6.206053 | ✅ CONFIRMED 2026-04-17 — inverse sensitivity (in/s)/V. `Range = 1.61133 V × 6.206053 = 10.000 in/s`. Firmware: `PPV (in/s) = ADC_voltage × 6.206053`. Hardware constant, identical on all units. Do NOT write. |
| Max Geo Range Enum (max_range_geo_enum) | §3.8.4 | Channel block, Tran+20, uint8 | uint8 | ❓ UNCONFIRMED — reads `0x01` on both tested units (Normal range). Hypothesis: `0x01`=Normal (Gain=1, 10 in/s), `0x00`=Sensitive (Gain=8, 1.25 in/s). Need 1.25 in/s capture to verify. |
| Microphone Units | §3.9.7 | Inline unit string | char[4] | `"psi\0"`, `"pa.\0"`, `"dB\0\0"` | | Microphone Units | §3.9.7 | Inline unit string | char[4] | `"psi\0"`, `"pa.\0"`, `"dB\0\0"` |
| Sample Rate | §3.8.2 | cfg anchor2, uint16 BE — anchor=`\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00` in cfg[40:100] | uint16 BE | Normal=1024, Fast=2048, Faster=4096 ✅ CONFIRMED 2026-04-01 (BE11529 S338.17). Anchor required — see §7.6.3 DLE jitter. | | Sample Rate | §3.8.2 | cfg anchor2, uint16 BE — anchor=`\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00` in cfg[40:100] | uint16 BE | Normal=1024, Fast=2048, Faster=4096 ✅ CONFIRMED 2026-04-01 (BE11529 S338.17). Anchor required — see §7.6.3 DLE jitter. |
| Record Mode | §3.8.1 | Unknown | — | Single Shot, Continuous, Manual, Histogram, Histogram Combo | | Record Mode | §3.8.1 | Unknown | — | Single Shot, Continuous, Manual, Histogram, Histogram Combo |
+35 -20
View File
@@ -876,8 +876,10 @@ class MiniMateClient:
Trigger/alarm thresholds (geo channels, in/s): Trigger/alarm thresholds (geo channels, in/s):
trigger_level_geo : float trigger threshold (e.g. 0.5) trigger_level_geo : float trigger threshold (e.g. 0.5)
alarm_level_geo : float alarm threshold (e.g. 1.0) alarm_level_geo : float alarm threshold (e.g. 1.0)
max_range_geo : float full-scale calibration constant (e.g. 6.206) max_range_geo : float ADC-to-velocity scale factor (= 1/sensitivity, in/s per V)
rarely changed only set if you know what you're doing e.g. 6.206053 for the standard Instantel geophone.
This is a hardware/firmware constant do NOT write it.
Accepted for API compatibility but silently ignored.
Project / operator strings (max 41 ASCII characters each): Project / operator strings (max 41 ASCII characters each):
project : str project : str
@@ -1679,9 +1681,13 @@ def _encode_compliance_config(
record_time float32 BE at anchor_pos + 6 record_time float32 BE at anchor_pos + 6
Channel block (anchored on b"Tran" with unit-string guard): Channel block (anchored on b"Tran" with unit-string guard):
max_range_geo float32 BE at tran_pos + 28
trigger_level_geo float32 BE at tran_pos + 34 trigger_level_geo float32 BE at tran_pos + 34
alarm_level_geo float32 BE at tran_pos + 42 alarm_level_geo float32 BE at tran_pos + 42
NOTE: tran_pos + 28 (float32 = 6.206053) is the ADC-to-velocity scale factor
(= 1/sensitivity, in/s per V) for the standard Instantel geophone. Confirmed
from Interface Handbook §4.5: Range = 1.61133 V × 6.206053 = 10.000 in/s.
This is a hardware/firmware constant common to all MiniMate Plus S3 units.
It must NOT be written. max_range_geo is accepted as a parameter but silently ignored.
String field locations (64-byte slots, label+22 format): String field locations (64-byte slots, label+22 format):
b"Project:" value at label_pos + 22, max 41 chars + null b"Project:" value at label_pos + 22, max 41 chars + null
@@ -1722,9 +1728,18 @@ def _encode_compliance_config(
log.debug("_encode_compliance_config: record_time=%.3f -> offset %d", record_time, _anc + 6) log.debug("_encode_compliance_config: record_time=%.3f -> offset %d", record_time, _anc + 6)
# ── Numeric: channel block (Tran label + unit-string guard) ─────────────── # ── Numeric: channel block (Tran label + unit-string guard) ───────────────
_needs_channel = any( # NOTE: max_range_geo (float32 at tran_pos+28) is the ADC-to-velocity scale factor
v is not None for v in (trigger_level_geo, alarm_level_geo, max_range_geo) # (1/sensitivity = 6.206053 (in/s)/V — confirmed: 1.61133 V × 6.206053 = 10.000 in/s).
) # It is a hardware/firmware constant common to all MiniMate Plus S3 units.
# It must NOT be written. The parameter is accepted for API compatibility but silently ignored.
if max_range_geo is not None:
log.warning(
"_encode_compliance_config: max_range_geo=%s ignored — "
"tran_pos+28 is the ADC scale factor (6.206053 (in/s)/V), a hardware constant "
"that must not be overwritten",
max_range_geo,
)
_needs_channel = any(v is not None for v in (trigger_level_geo, alarm_level_geo))
if _needs_channel: if _needs_channel:
_tran = buf.find(b"Tran", 44) _tran = buf.find(b"Tran", 44)
_valid = ( _valid = (
@@ -1737,12 +1752,9 @@ def _encode_compliance_config(
if not _valid: if not _valid:
log.warning( log.warning(
"_encode_compliance_config: 'Tran' channel block not found or unit " "_encode_compliance_config: 'Tran' channel block not found or unit "
"guard failed — trigger/alarm/max_range will not be written" "guard failed — trigger/alarm will not be written"
) )
else: else:
if max_range_geo is not None:
struct.pack_into(">f", buf, _tran + 28, max_range_geo)
log.debug("_encode_compliance_config: max_range_geo=%.4f -> offset %d", max_range_geo, _tran + 28)
if trigger_level_geo is not None: if trigger_level_geo is not None:
struct.pack_into(">f", buf, _tran + 34, trigger_level_geo) struct.pack_into(">f", buf, _tran + 34, trigger_level_geo)
log.debug("_encode_compliance_config: trigger_level_geo=%.4f -> offset %d", trigger_level_geo, _tran + 34) log.debug("_encode_compliance_config: trigger_level_geo=%.4f -> offset %d", trigger_level_geo, _tran + 34)
@@ -1800,7 +1812,7 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
Channel block layout ( confirmed 2026-04-02 from 3-11-26 E5 frame 78 Channel block layout ( confirmed 2026-04-02 from 3-11-26 E5 frame 78
and 1-2-26 A5 frame 77): and 1-2-26 A5 frame 77):
"Tran" label at tran_pos "Tran" label at tran_pos
tran_pos + 28 = max_range float32_BE (e.g. 6.206053 in/s) tran_pos + 28 = scale_factor float32_BE (= 1/sensitivity = 6.206053 (in/s)/V ADC scale; NOT a UI setting)
tran_pos + 34 = trigger_level float32_BE (e.g. 0.600000 in/s) tran_pos + 34 = trigger_level float32_BE (e.g. 0.600000 in/s)
tran_pos + 38 = "in.\\x00" (unit string anchor) tran_pos + 38 = "in.\\x00" (unit string anchor)
tran_pos + 42 = alarm_level float32_BE (e.g. 1.250000 in/s) tran_pos + 42 = alarm_level float32_BE (e.g. 1.250000 in/s)
@@ -1897,14 +1909,15 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
# The channel block is only present in the full cfg (frame D delivered, # The channel block is only present in the full cfg (frame D delivered,
# ~2126 bytes). Layout confirmed 2026-04-02 from both E5 frame 78 of the # ~2126 bytes). Layout confirmed 2026-04-02 from both E5 frame 78 of the
# 3-11-26 compliance-config capture and A5 frame 77 of the 1-2-26 event # 3-11-26 compliance-config capture and A5 frame 77 of the 1-2-26 event
# download capture: # download capture. Cross-checked 2026-04-17 across both BE11529 and BE18189.
# #
# "Tran" label at tran_pos (+0 to +3) # "Tran" label at tran_pos (+0 to +3)
# max_range float32_BE at tran_pos + 28 (e.g. 6.206053 in/s) # max_range_enum uint8 at tran_pos + 20 (range selector: 0x01=10in/s, 0x00=1.25in/s — unconfirmed)
# trigger float32_BE at tran_pos + 34 (e.g. 0.600000 in/s) # adc_scale float32_BE at tran_pos + 28 (= 1/sensitivity = 6.206053 (in/s)/V; confirmed: 1.61133 V × 6.206053 = 10.000 in/s Normal range; hardware constant — do NOT write)
# "in.\x00" unit string at tran_pos + 38 ✅ confirmed # trigger float32_BE at tran_pos + 34 (e.g. 0.600000 in/s) ✅
# alarm float32_BE at tran_pos + 42 (e.g. 1.250000 in/s) # "in.\x00" unit string at tran_pos + 38 ✅ confirmed
# "/s\x00\x00" unit string at tran_pos + 46 ✅ confirmed # alarm float32_BE at tran_pos + 42 (e.g. 1.250000 in/s) ✅
# "/s\x00\x00" unit string at tran_pos + 46 ✅ confirmed
# #
# Unit strings serve as layout anchors — if they match, the float offsets # Unit strings serve as layout anchors — if they match, the float offsets
# are reliable. Skip "Tran2" (a later repeated label) via the +4 check. # are reliable. Skip "Tran2" (a later repeated label) via the +4 check.
@@ -1917,12 +1930,14 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
and data[tran_pos + 38 : tran_pos + 42] == b"in.\x00" and data[tran_pos + 38 : tran_pos + 42] == b"in.\x00"
and data[tran_pos + 46 : tran_pos + 50] == b"/s\x00\x00" and data[tran_pos + 46 : tran_pos + 50] == b"/s\x00\x00"
): ):
config.max_range_geo = struct.unpack_from(">f", data, tran_pos + 28)[0] config.max_range_geo_enum = data[tran_pos + 20] # range selector enum (see above)
config.max_range_geo = struct.unpack_from(">f", data, tran_pos + 28)[0] # hw constant
config.trigger_level_geo = struct.unpack_from(">f", data, tran_pos + 34)[0] config.trigger_level_geo = struct.unpack_from(">f", data, tran_pos + 34)[0]
config.alarm_level_geo = struct.unpack_from(">f", data, tran_pos + 42)[0] config.alarm_level_geo = struct.unpack_from(">f", data, tran_pos + 42)[0]
log.debug( log.debug(
"compliance_config: trigger=%.4f alarm=%.4f max_range=%.4f in/s", "compliance_config: trigger=%.4f alarm=%.4f range_enum=0x%02X hw_const=%.6f",
config.trigger_level_geo, config.alarm_level_geo, config.max_range_geo, config.trigger_level_geo, config.alarm_level_geo,
config.max_range_geo_enum, config.max_range_geo,
) )
elif tran_pos >= 0: elif tran_pos >= 0:
log.warning( log.warning(
+13 -4
View File
@@ -269,7 +269,7 @@ class ChannelConfig:
label: str # e.g. "Tran", "Vert", "Long", "MicL" ✅ label: str # e.g. "Tran", "Vert", "Long", "MicL" ✅
trigger_level: float # in/s (geo) or psi (MicL) ✅ trigger_level: float # in/s (geo) or psi (MicL) ✅
alarm_level: float # in/s (geo) or psi (MicL) ✅ alarm_level: float # in/s (geo) or psi (MicL) ✅
max_range: float # full-scale calibration constant (e.g. 6.206) 🔶 max_range: float # hardware/firmware sensitivity constant (e.g. 6.206053) ✅ confirmed same on all units
unit_label: str # e.g. "in./s" or "psi" ✅ unit_label: str # e.g. "in./s" or "psi" ✅
@@ -344,9 +344,18 @@ class ComplianceConfig:
# Trigger/alarm levels (✅ CONFIRMED per-channel at §7.6) # Trigger/alarm levels (✅ CONFIRMED per-channel at §7.6)
# For now we store the first geo channel (Transverse) as representatives; # For now we store the first geo channel (Transverse) as representatives;
# full per-channel data would require structured Channel objects. # full per-channel data would require structured Channel objects.
trigger_level_geo: Optional[float] = None # in/s (first geo channel) trigger_level_geo: Optional[float] = None # in/s (first geo channel)
alarm_level_geo: Optional[float] = None # in/s (first geo channel) alarm_level_geo: Optional[float] = None # in/s (first geo channel)
max_range_geo: Optional[float] = None # in/s full-scale range max_range_geo: Optional[float] = None # ADC-to-velocity scale factor (float32 at Tran+28) ✅
# = inverse sensitivity = 1/sensitivity (in/s per V)
# Formula (Interface Handbook §4.5): Range = 1.61133 V × scale_factor
# → 1.61133 × 6.206053 = 10.000 in/s (Normal range) ✅
# Firmware uses: PPV (in/s) = ADC_voltage (V) × 6.206053
# Identical on BE11529 and BE18189 — same Instantel geophone hardware.
# NOT a user-configurable setting. Must NOT be written (use _encode_compliance_config).
max_range_geo_enum: Optional[int] = None # max range selector: uint8 at Tran+20
# hypothesis: 0x01 = Normal 10.000 in/s, 0x00 = Sensitive 1.25 in/s
# reads 0x01 on all tested units — UNCONFIRMED (need 1.25 in/s capture)
# Project/setup strings (sourced from E5 / SUB 71 write payload) # Project/setup strings (sourced from E5 / SUB 71 write payload)
# These are the FULL project metadata from compliance config, # These are the FULL project metadata from compliance config,
+5 -3
View File
@@ -289,7 +289,8 @@ def _serialise_compliance_config(cc: Optional["ComplianceConfig"]) -> Optional[d
"sample_rate": cc.sample_rate, "sample_rate": cc.sample_rate,
"trigger_level_geo": cc.trigger_level_geo, "trigger_level_geo": cc.trigger_level_geo,
"alarm_level_geo": cc.alarm_level_geo, "alarm_level_geo": cc.alarm_level_geo,
"max_range_geo": cc.max_range_geo, "max_range_geo": cc.max_range_geo, # hw constant 6.206053 — informational only, do not write
"max_range_geo_enum": cc.max_range_geo_enum, # 0x01=Normal 10in/s, 0x00=Extended 1.25in/s (unconfirmed)
"setup_name": cc.setup_name, "setup_name": cc.setup_name,
"project": cc.project, "project": cc.project,
"client": cc.client, "client": cc.client,
@@ -842,8 +843,9 @@ class DeviceConfigBody(BaseModel):
------------------------------------------------ ------------------------------------------------
trigger_level_geo : Trigger threshold in in/s (e.g. 0.5). trigger_level_geo : Trigger threshold in in/s (e.g. 0.5).
alarm_level_geo : Alarm threshold in in/s (e.g. 1.0). alarm_level_geo : Alarm threshold in in/s (e.g. 1.0).
max_range_geo : Full-scale calibration constant (e.g. 6.206). max_range_geo : DEPRECATED was misidentified as the max range; it is actually a
Rarely changed only set if you know what you're doing. hardware/firmware constant (6.206053) the same on all units and
must not be written. Passing this field is a no-op (ignored with warning).
Project / operator strings (max 41 ASCII characters each) Project / operator strings (max 41 ASCII characters each)
---------------------------- ----------------------------