merge protocol-exp 0.12.3 to main #5

Merged
serversdown merged 14 commits from protocol-exp into main 2026-04-21 00:22:25 -04:00
7 changed files with 110 additions and 34 deletions
Showing only changes of commit b6ffdcfa87 - Show all commits
+28
View File
@@ -4,6 +4,34 @@ All notable changes to seismo-relay are documented here.
--- ---
## v0.12.2 — 2026-04-20
### Added / Fixed
- **Geophone sensitivity / maximum range field confirmed** — 4-20-26 geo sensitivity
captures (1.25 in/s vs 10 in/s) diffed across all three SUB 71 write chunks and both
E5 read payloads. The `geo_range` uint8 field per channel is now fully confirmed:
- E5 read offset: `channel_label + 33`; SUB 71 write offset: `channel_label + 29`
- `0x00` = Normal 10.000 in/s (standard gain); `0x01` = Sensitive 1.250 in/s (high gain)
- **Correction:** previous hypothesis (`channel_label+20`, `0x01`=Normal) was wrong.
`channel_label+20` reads `0x01` on ALL captures regardless of range — not this field.
- `_decode_compliance_config_into`: read offset corrected from `tran_pos+20``tran_pos+33`
- `_encode_compliance_config`: added `geo_range` parameter; writes to Tran/Vert/Long at `+29`
- `apply_config`: added `geo_range` parameter
- `POST /device/config`: added `geo_range` to `DeviceConfigBody`
- Web UI Config tab: added "Maximum Range — Geo" select (Normal / Sensitive)
- Web UI Device tab: added "Max Range (geo)" row to compliance table
- **`recording_mode` + `histogram_interval_sec` confirmed and implemented** (4-20-26 captures)
- `recording_mode`: uint8 at anchor8 (E5 read) / anchor7 (write); enum: 0x00=Single Shot,
0x01=Continuous, 0x03=Histogram, 0x04=Histogram+Continuous
- `histogram_interval_sec`: uint16 BE seconds at anchor4; same offset in read & write;
valid: 2, 5, 15, 60, 300, 900 (matching Blastware dropdown: 2s, 5s, 15s, 1m, 5m, 15m)
- Both fields added to `ComplianceConfig`, `_decode_compliance_config_into`,
`_encode_compliance_config`, `apply_config`, REST API body, and web UI
---
## v0.12.1 — 2026-04-16 ## v0.12.1 — 2026-04-16
### Added ### Added
+4 -4
View File
@@ -2,7 +2,7 @@
Ground-up Python replacement for **Blastware**, Instantel's Windows-only software for Ground-up Python replacement for **Blastware**, Instantel's Windows-only software for
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.2**.
When new information about the protocol is discovered, please update the instantel_protocol_reference.md with the findings in addition to this document When new information about the protocol is discovered, please update the instantel_protocol_reference.md with the findings in addition to this document
@@ -372,8 +372,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 |
| 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. | | geo_hardware_constant (adc_scale_factor) | float32 BE at **channel_label+28** in both read (E5) and write (SUB 71) payloads — 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 ✅). Do NOT write this field — it is a hardware/firmware constant. |
| 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. | | geo_range (sensitivity selector) | **uint8 at channel_label+33** in both read (E5) and write (SUB 71) payloads — **CONFIRMED 2026-04-20** from 4-20-26 geo sensitivity captures: `0x00` = Normal 10.000 in/s (standard gain), `0x01` = Sensitive 1.250 in/s (high gain). Present in all three geo channel blocks (Tran, Vert, Long). **NOTE: `channel_label+20` reads `0x01` on ALL captures regardless of range setting — it is NOT this field.** Note: the "SUB 71 write offset = +29" that appears in earlier analysis was an artifact of incorrect BW-style destuffing applied to write frame data — write frame data is RAW, so the literal `0x10` bytes in the channel block header are preserved, and the offset is the same as in the E5 read payload. |
| 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 |
@@ -746,7 +746,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) (`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. - Chan 1-3 Maximum Range: Normal 10.000 / 1.25 in/s (enum) (`geo_range` uint8; **CONFIRMED 2026-04-20** from 4-20-26 geo sensitivity captures: offset = `channel_label+33` in both E5 read and SUB 71 write payloads (same bytes, round-tripped verbatim); `0x00` = Normal 10.000 in/s, `0x01` = Sensitive 1.250 in/s; applied to Tran/Vert/Long channel blocks). **IMPORTANT: `channel_label+20` reads `0x01` on ALL captures and is NOT this field** — it is a constant flag. The float32 at `channel_label+28` = 6.206053 is the ADC-to-velocity scale factor (hardware constant, do NOT write).
- 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)
+5 -4
View File
@@ -105,6 +105,7 @@
| 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**. | | 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**. |
| 2026-04-20 | §7.6.4 (NEW), §7.9, Appendix B | **CONFIRMED — Recording Mode byte location.** Three targeted captures (4-20-26) confirmed `recording_mode` at **`cfg[5]`** in the SUB 71 write payload (3-chunk compliance write). Method: single Blastware session, one initial E5 config pull, then three sequential "Send to unit" writes changing Recording Mode only. Diff of SUB 71 chunk-1 payloads: only `cfg[5]` and `cfg[1024]` changed; `cfg[1024]` delta exactly equals `cfg[5]` delta (chunk running checksum). In the E5 read response (sub-frame 1, page=0x0010), the field is at **`data[17]`** (= **anchor 4** from the 10-byte anchor), one position earlier than in the write payload due to an extra `0x10` byte at `data[18]` present only in the read format. Enum: `0x00`=Single Shot, `0x01`=Continuous, `0x03`=Histogram, `0x04`=Histogram+Continuous. `0x02` value not yet observed. See §7.6.4 for full details. | | 2026-04-20 | §7.6.4 (NEW), §7.9, Appendix B | **CONFIRMED — Recording Mode byte location.** Three targeted captures (4-20-26) confirmed `recording_mode` at **`cfg[5]`** in the SUB 71 write payload (3-chunk compliance write). Method: single Blastware session, one initial E5 config pull, then three sequential "Send to unit" writes changing Recording Mode only. Diff of SUB 71 chunk-1 payloads: only `cfg[5]` and `cfg[1024]` changed; `cfg[1024]` delta exactly equals `cfg[5]` delta (chunk running checksum). In the E5 read response (sub-frame 1, page=0x0010), the field is at **`data[17]`** (= **anchor 4** from the 10-byte anchor), one position earlier than in the write payload due to an extra `0x10` byte at `data[18]` present only in the read format. Enum: `0x00`=Single Shot, `0x01`=Continuous, `0x03`=Histogram, `0x04`=Histogram+Continuous. `0x02` value not yet observed. See §7.6.4 for full details. |
| 2026-04-20 | §7.6.2, §7.9, Appendix B | **CONFIRMED — Geophone maximum range / sensitivity selector byte location.** Two targeted captures (4-20-26, geo sensitivity folder): one at Normal 10.000 in/s, one at Sensitive 1.250 in/s. E5 read payload diff: exactly 3 bytes differ at channel_label+33 for Tran/Vert/Long. Values: `0x00`=Normal 10.000 in/s, `0x01`=Sensitive 1.250 in/s. Same offset applies to the SUB 71 write payload (which is the same 2126-byte E5-format buffer round-tripped verbatim). **`channel_label+20` reads `0x01` in ALL captures regardless of range setting — it is NOT this field.** Previous hypothesis (uint8 at Tran+20, 0x01=Normal) was WRONG. Stored as `geo_range` in `ComplianceConfig`. Encoded to all three geo channel blocks (Tran/Vert/Long) at label+33. |
--- ---
@@ -1322,8 +1323,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 (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 Maximum Range (range selector) | Normal 10.000 / 1.25 in/s | `geo_range` uint8 **CONFIRMED 2026-04-20.** Offset = Tran+33 (same in E5 read and SUB 71 write — 2126-byte buffer is round-tripped verbatim). `0x00`=Normal 10 in/s, `0x01`=Sensitive 1.25 in/s. Applied to Tran/Vert/Long. **`Tran+20` is NOT this field** (constant 0x01 on all captures). |
| 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. | | Chan 1-3 ADC Scale Factor | 6.206053 (in/s)/V | ✅ `geo_adc_scale` float32**CONFIRMED 2026-04-17.** Offset = Tran+28 (same in E5 read and SUB 71 write). 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 | ❓ |
@@ -2018,8 +2019,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. |
| 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. | | ADC Scale Factor (geo_adc_scale) | §3.8.4 / Interface Handbook §4.5 | Channel block, Tran+28 (same in E5 read and SUB 71 write), 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. | | Max Geo Range (geo_range) | §3.8.4 | Channel block, Tran+33 (same in E5 read and SUB 71 write), uint8; applied to Tran/Vert/Long | uint8 | CONFIRMED 2026-04-20 — `0x00`=Normal 10.000 in/s, `0x01`=Sensitive 1.250 in/s. **NOTE: `Tran+20` reads `0x01` on ALL captures regardless of range — it is NOT this field.** |
| 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 | Write: `cfg[anchor3]`, uint8. Read (E5 sf1): `data[anchor4]`, uint8. Note: extra `0x10` byte at read `data[anchor3]` shifts offset by 1 vs write. | uint8 | `0x00`=Single Shot, `0x01`=Continuous, `0x02`=unknown, `0x03`=Histogram, `0x04`=Histogram+Continuous. ✅ CONFIRMED 2026-04-20 | | Record Mode | §3.8.1 | Write: `cfg[anchor3]`, uint8. Read (E5 sf1): `data[anchor4]`, uint8. Note: extra `0x10` byte at read `data[anchor3]` shifts offset by 1 vs write. | uint8 | `0x00`=Single Shot, `0x01`=Continuous, `0x02`=unknown, `0x03`=Histogram, `0x04`=Histogram+Continuous. ✅ CONFIRMED 2026-04-20 |
+43 -16
View File
@@ -854,6 +854,7 @@ class MiniMateClient:
# Threshold parameters (geo channels, in/s) # Threshold parameters (geo channels, in/s)
trigger_level_geo: Optional[float] = None, trigger_level_geo: Optional[float] = None,
alarm_level_geo: Optional[float] = None, alarm_level_geo: Optional[float] = None,
geo_range: Optional[int] = None, # 0x00=Normal 10in/s, 0x01=Sensitive 1.25in/s
# Project / operator strings # Project / operator strings
project: Optional[str] = None, project: Optional[str] = None,
client_name: Optional[str] = None, client_name: Optional[str] = None,
@@ -875,9 +876,11 @@ class MiniMateClient:
sample_rate : int samples/sec; valid values: 1024, 2048, 4096 sample_rate : int samples/sec; valid values: 1024, 2048, 4096
record_time : float record duration in seconds (e.g. 2.0, 3.0) record_time : float record duration in seconds (e.g. 2.0, 3.0)
Trigger/alarm thresholds (geo channels, in/s): Trigger/alarm thresholds and range (geo channels):
trigger_level_geo : float trigger threshold (e.g. 0.5) trigger_level_geo : float trigger threshold in/s (e.g. 0.5)
alarm_level_geo : float alarm threshold (e.g. 1.0) alarm_level_geo : float alarm threshold in/s (e.g. 1.0)
geo_range : int 0x00=Normal 10.000 in/s, 0x01=Sensitive 1.250 in/s
(written to Tran/Vert/Long channel blocks)
Project / operator strings (max 41 ASCII characters each): Project / operator strings (max 41 ASCII characters each):
project : str project : str
@@ -923,6 +926,7 @@ class MiniMateClient:
histogram_interval_sec=histogram_interval_sec, histogram_interval_sec=histogram_interval_sec,
trigger_level_geo=trigger_level_geo, trigger_level_geo=trigger_level_geo,
alarm_level_geo=alarm_level_geo, alarm_level_geo=alarm_level_geo,
geo_range=geo_range,
project=project, project=project,
client_name=client_name, client_name=client_name,
operator=operator, operator=operator,
@@ -1660,6 +1664,7 @@ def _encode_compliance_config(
record_time: Optional[float] = None, record_time: Optional[float] = None,
trigger_level_geo: Optional[float] = None, trigger_level_geo: Optional[float] = None,
alarm_level_geo: Optional[float] = None, alarm_level_geo: Optional[float] = None,
geo_range: Optional[int] = None, # 0x00=Normal 10in/s, 0x01=Sensitive 1.25in/s
histogram_interval_sec: Optional[int] = None, histogram_interval_sec: Optional[int] = None,
project: Optional[str] = None, project: Optional[str] = None,
client_name: Optional[str] = None, client_name: Optional[str] = None,
@@ -1687,11 +1692,16 @@ 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):
geo_range uint8 at tran_pos + 33 (confirmed 2026-04-20)
0x00 = Normal 10.000 in/s, 0x01 = Sensitive 1.250 in/s
Written to Tran, Vert, Long channel blocks (all three).
adc_scale_factor float32 BE at tran_pos + 28 (= 6.206053; do NOT write)
trigger_level_geo float32 BE at tran_pos + 34 trigger_level_geo float32 BE at tran_pos + 34
"in.\\x00" unit string at tran_pos + 38 (layout guard)
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 "/s\\x00\\x00" unit string at tran_pos + 46 (layout guard)
(= 1/sensitivity, in/s per V) for the standard Instantel geophone. Confirmed NOTE: tran_pos+28 (float32 = 6.206053) is the ADC-to-velocity scale factor
from Interface Handbook §4.5: Range = 1.61133 V × 6.206053 = 10.000 in/s. (= 1/sensitivity, (in/s)/V Interface Handbook §4.5: 1.61133 V × 6.206053 = 10.000 in/s).
This is a hardware/firmware constant common to all MiniMate Plus S3 units. This is a hardware/firmware constant common to all MiniMate Plus S3 units.
It must NOT be written do not add it back as a parameter. It must NOT be written do not add it back as a parameter.
@@ -1750,10 +1760,11 @@ 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) ───────────────
# NOTE: tran_pos+28 (float32 = 6.206053) is the ADC-to-velocity scale factor # NOTE: tran_pos+24 (write format) or tran_pos+28 (E5 read format) is the
# (1/sensitivity, (in/s)/V — Interface Handbook §4.5: 1.61133 V × 6.206053 = 10.000 in/s). # ADC-to-velocity scale factor (6.206053, hardware constant — never written).
# Hardware/firmware constant — never written here. # geo_range is written to ALL THREE geo channel blocks (Tran, Vert, Long),
_needs_channel = any(v is not None for v in (trigger_level_geo, alarm_level_geo)) # confirmed from 4-20-26 captures showing the byte at label+29 in each block.
_needs_channel = any(v is not None for v in (trigger_level_geo, alarm_level_geo, geo_range))
if _needs_channel: if _needs_channel:
_tran = buf.find(b"Tran", 44) _tran = buf.find(b"Tran", 44)
_valid = ( _valid = (
@@ -1766,7 +1777,7 @@ 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 will not be written" "guard failed — trigger/alarm/geo_range will not be written"
) )
else: else:
if trigger_level_geo is not None: if trigger_level_geo is not None:
@@ -1775,6 +1786,19 @@ def _encode_compliance_config(
if alarm_level_geo is not None: if alarm_level_geo is not None:
struct.pack_into(">f", buf, _tran + 42, alarm_level_geo) struct.pack_into(">f", buf, _tran + 42, alarm_level_geo)
log.debug("_encode_compliance_config: alarm_level_geo=%.4f -> offset %d", alarm_level_geo, _tran + 42) log.debug("_encode_compliance_config: alarm_level_geo=%.4f -> offset %d", alarm_level_geo, _tran + 42)
if geo_range is not None:
# Write geo_range to all three geo channel blocks (Tran, Vert, Long).
# Field at label+33 in the E5-format compliance bytes (same in read and write
# since the 2126-byte payload is round-tripped verbatim).
# 0x00 = Normal 10.000 in/s, 0x01 = Sensitive 1.250 in/s.
for _ch_label in (b"Tran", b"Vert", b"Long"):
_ch = buf.find(_ch_label, 44)
if _ch >= 0 and buf[_ch + 4 : _ch + 5] != b"2" and _ch + 34 <= len(buf):
buf[_ch + 33] = geo_range & 0xFF
log.debug(
"_encode_compliance_config: geo_range=0x%02X -> %s+33 offset %d",
geo_range, _ch_label.decode(), _ch + 33,
)
# ── ASCII strings (64-byte slot, value at label_pos+22) ─────────────────── # ── ASCII strings (64-byte slot, value at label_pos+22) ───────────────────
def _set_string(label: bytes, value: Optional[str]) -> None: def _set_string(label: bytes, value: Optional[str]) -> None:
@@ -1958,12 +1982,15 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
# download capture. Cross-checked 2026-04-17 across both BE11529 and BE18189. # 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_enum uint8 at tran_pos + 20 (range selector: 0x01=10in/s, 0x00=1.25in/s — unconfirmed) # adc_scale float32_BE at tran_pos + 28 (= 1/sensitivity = 6.206053 (in/s)/V; hardware constant — do NOT write)
# 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) # geo_range uint8 at tran_pos + 33 CONFIRMED 2026-04-20
# 0x00 = Normal 10.000 in/s, 0x01 = Sensitive 1.250 in/s
# Same offset in E5 read and SUB 71 write (bytes are round-tripped verbatim).
# NOTE: tran_pos+20 reads 0x01 on ALL captures — constant flag, NOT range field.
# trigger float32_BE at tran_pos + 34 (e.g. 0.600000 in/s) ✅ # trigger float32_BE at tran_pos + 34 (e.g. 0.600000 in/s) ✅
# "in.\x00" unit string at tran_pos + 38 ✅ confirmed # "in.\x00" unit string at tran_pos + 38 ✅ confirmed (layout guard)
# alarm float32_BE at tran_pos + 42 (e.g. 1.250000 in/s) ✅ # alarm float32_BE at tran_pos + 42 (e.g. 1.250000 in/s) ✅
# "/s\x00\x00" unit string at tran_pos + 46 ✅ confirmed # "/s\x00\x00" unit string at tran_pos + 46 ✅ confirmed (layout guard)
# #
# 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.
@@ -1976,7 +2003,7 @@ 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.geo_range = data[tran_pos + 20] # range selector (0x01=Normal 10in/s, 0x00=Sensitive 1.25in/s — unconfirmed) config.geo_range = data[tran_pos + 33] # CONFIRMED 2026-04-20: 0x00=Normal 10in/s, 0x01=Sensitive 1.25in/s
config.geo_adc_scale = struct.unpack_from(">f", data, tran_pos + 28)[0] # hw scale factor (in/s)/V — do NOT write config.geo_adc_scale = struct.unpack_from(">f", data, tran_pos + 28)[0] # hw scale factor (in/s)/V — do NOT write
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]
+5 -3
View File
@@ -361,9 +361,11 @@ class ComplianceConfig:
# Firmware uses: PPV (in/s) = ADC_voltage (V) × 6.206053 # Firmware uses: PPV (in/s) = ADC_voltage (V) × 6.206053
# Identical on BE11529 and BE18189 — same Instantel geophone hardware. # Identical on BE11529 and BE18189 — same Instantel geophone hardware.
# NOT a user-configurable setting. Must NOT be written. # NOT a user-configurable setting. Must NOT be written.
geo_range: Optional[int] = None # range selector: uint8 at Tran+20 geo_range: Optional[int] = None # range/sensitivity selector — CONFIRMED 2026-04-20
# hypothesis: 0x01 = Normal 10.000 in/s, 0x00 = Sensitive 1.25 in/s # 0x00 = Normal 10.000 in/s (standard gain)
# reads 0x01 on all tested units — UNCONFIRMED (need 1.25 in/s capture) # 0x01 = Sensitive 1.250 in/s (high gain)
# Offset: Tran+33 in both E5 read and SUB 71 write payloads
# (same 2126-byte buffer is round-tripped; applied to Tran/Vert/Long)
# 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,
+7 -4
View File
@@ -292,7 +292,7 @@ def _serialise_compliance_config(cc: Optional["ComplianceConfig"]) -> Optional[d
"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,
"geo_adc_scale": cc.geo_adc_scale, # hw scale factor (in/s)/V — informational only, do not write "geo_adc_scale": cc.geo_adc_scale, # hw scale factor (in/s)/V — informational only, do not write
"geo_range": cc.geo_range, # range selector: 0x01=Normal 10in/s, 0x00=Sensitive 1.25in/s (unconfirmed) "geo_range": cc.geo_range, # CONFIRMED 2026-04-20: 0x00=Normal 10in/s, 0x01=Sensitive 1.25in/s
"setup_name": cc.setup_name, "setup_name": cc.setup_name,
"project": cc.project, "project": cc.project,
"client": cc.client, "client": cc.client,
@@ -842,10 +842,11 @@ class DeviceConfigBody(BaseModel):
sample_rate : Samples per second. Valid values: 1024, 2048, 4096. sample_rate : Samples per second. Valid values: 1024, 2048, 4096.
record_time : Record duration in seconds (e.g. 1.0, 2.0, 3.0). record_time : Record duration in seconds (e.g. 1.0, 2.0, 3.0).
Trigger / alarm thresholds (geo channels, in/s) Trigger / alarm thresholds and range (geo channels)
------------------------------------------------ ----------------------------------------------------
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).
geo_range : Geophone range/sensitivity. 0=Normal 10.000 in/s, 1=Sensitive 1.250 in/s.
Project / operator strings (max 41 ASCII characters each) Project / operator strings (max 41 ASCII characters each)
---------------------------- ----------------------------
project : Project description. project : Project description.
@@ -859,9 +860,10 @@ class DeviceConfigBody(BaseModel):
sample_rate: Optional[int] = None sample_rate: Optional[int] = None
record_time: Optional[float] = None record_time: Optional[float] = None
histogram_interval_sec: Optional[int] = None # seconds: 2, 5, 15, 60, 300, 900 (mode-gated) histogram_interval_sec: Optional[int] = None # seconds: 2, 5, 15, 60, 300, 900 (mode-gated)
# Threshold parameters # Threshold parameters / geo range
trigger_level_geo: Optional[float] = None trigger_level_geo: Optional[float] = None
alarm_level_geo: Optional[float] = None alarm_level_geo: Optional[float] = None
geo_range: Optional[int] = None # 0=Normal 10.000 in/s, 1=Sensitive 1.250 in/s
# Project / operator strings # Project / operator strings
project: Optional[str] = None project: Optional[str] = None
client_name: Optional[str] = None client_name: Optional[str] = None
@@ -923,6 +925,7 @@ def device_config(
histogram_interval_sec=body.histogram_interval_sec, histogram_interval_sec=body.histogram_interval_sec,
trigger_level_geo=body.trigger_level_geo, trigger_level_geo=body.trigger_level_geo,
alarm_level_geo=body.alarm_level_geo, alarm_level_geo=body.alarm_level_geo,
geo_range=body.geo_range,
project=body.project, project=body.project,
client_name=body.client_name, client_name=body.client_name,
operator=body.operator, operator=body.operator,
+16 -1
View File
@@ -856,6 +856,16 @@
<div class="hint">Alarm flagged when any geo channel exceeds this level</div> <div class="hint">Alarm flagged when any geo channel exceeds this level</div>
</div> </div>
<div class="cfg-field">
<label>Maximum Range — Geo</label>
<select id="cfg-geo-range">
<option value="">— unchanged —</option>
<option value="0">Normal — 10.000 in/s</option>
<option value="1">Sensitive — 1.250 in/s</option>
</select>
<div class="hint">Geophone sensitivity (applies to Tran / Vert / Long channels)</div>
</div>
</div> </div>
<!-- Project / operator strings --> <!-- Project / operator strings -->
@@ -1354,6 +1364,7 @@ function populateDeviceTab() {
['Record Time', cc.record_time != null ? `${cc.record_time.toFixed(2)} s` : '—'], ['Record Time', cc.record_time != null ? `${cc.record_time.toFixed(2)} s` : '—'],
['Trigger Level (geo)', cc.trigger_level_geo != null ? `${cc.trigger_level_geo.toFixed(4)} in/s` : '—'], ['Trigger Level (geo)', cc.trigger_level_geo != null ? `${cc.trigger_level_geo.toFixed(4)} in/s` : '—'],
['Alarm Level (geo)', cc.alarm_level_geo != null ? `${cc.alarm_level_geo.toFixed(4)} in/s` : '—'], ['Alarm Level (geo)', cc.alarm_level_geo != null ? `${cc.alarm_level_geo.toFixed(4)} in/s` : '—'],
['Max Range (geo)', cc.geo_range != null ? (cc.geo_range === 0 ? 'Normal — 10.000 in/s' : cc.geo_range === 1 ? 'Sensitive — 1.250 in/s' : `0x${cc.geo_range.toString(16).padStart(2,'0')}`) : '—'],
['ADC Scale Factor (geo)', cc.geo_adc_scale != null ? `${cc.geo_adc_scale.toFixed(4)} in/s` : '—'], ['ADC Scale Factor (geo)', cc.geo_adc_scale != null ? `${cc.geo_adc_scale.toFixed(4)} in/s` : '—'],
['Setup Name', cc.setup_name || '—'], ['Setup Name', cc.setup_name || '—'],
]; ];
@@ -1391,6 +1402,7 @@ function populateConfigFromDeviceInfo() {
if (cc.record_time != null) qs('cfg-record-time', cc.record_time.toFixed(1)); if (cc.record_time != null) qs('cfg-record-time', cc.record_time.toFixed(1));
if (cc.trigger_level_geo != null) qs('cfg-trigger', cc.trigger_level_geo.toFixed(4)); if (cc.trigger_level_geo != null) qs('cfg-trigger', cc.trigger_level_geo.toFixed(4));
if (cc.alarm_level_geo != null) qs('cfg-alarm', cc.alarm_level_geo.toFixed(4)); if (cc.alarm_level_geo != null) qs('cfg-alarm', cc.alarm_level_geo.toFixed(4));
if (cc.geo_range != null) qs('cfg-geo-range', String(cc.geo_range));
if (cc.project) qs('cfg-project', cc.project); if (cc.project) qs('cfg-project', cc.project);
if (cc.client) qs('cfg-client', cc.client); if (cc.client) qs('cfg-client', cc.client);
if (cc.operator) qs('cfg-operator', cc.operator); if (cc.operator) qs('cfg-operator', cc.operator);
@@ -1400,7 +1412,8 @@ function populateConfigFromDeviceInfo() {
function clearConfigForm() { function clearConfigForm() {
['cfg-sample-rate','cfg-record-time','cfg-trigger','cfg-alarm', ['cfg-sample-rate','cfg-record-time','cfg-trigger','cfg-alarm',
'cfg-project','cfg-client','cfg-operator','cfg-seis-loc','cfg-notes'] 'cfg-project','cfg-client','cfg-operator','cfg-seis-loc','cfg-notes',
'cfg-recording-mode','cfg-histogram-interval','cfg-geo-range']
.forEach(id => { const el = qs(id); el.tagName === 'SELECT' ? el.selectedIndex = 0 : el.value = ''; }); .forEach(id => { const el = qs(id); el.tagName === 'SELECT' ? el.selectedIndex = 0 : el.value = ''; });
setCfgStatus(''); setCfgStatus('');
} }
@@ -1441,6 +1454,8 @@ async function writeConfig() {
if (trig) body.trigger_level_geo = parseFloat(trig); if (trig) body.trigger_level_geo = parseFloat(trig);
const alarm = qs('cfg-alarm').value; const alarm = qs('cfg-alarm').value;
if (alarm) body.alarm_level_geo = parseFloat(alarm); if (alarm) body.alarm_level_geo = parseFloat(alarm);
const gr = qs('cfg-geo-range').value;
if (gr !== '') body.geo_range = parseInt(gr, 10);
const proj = qs('cfg-project').value.trim(); const proj = qs('cfg-project').value.trim();
if (proj) body.project = proj; if (proj) body.project = proj;
const cli = qs('cfg-client').value.trim(); const cli = qs('cfg-client').value.trim();