diff --git a/CHANGELOG.md b/CHANGELOG.md index 5076959..ff3627d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 anchor−8 (E5 read) / anchor−7 (write); enum: 0x00=Single Shot, + 0x01=Continuous, 0x03=Histogram, 0x04=Histogram+Continuous + - `histogram_interval_sec`: uint16 BE seconds at anchor−4; 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 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index d4740d6..eb56aca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ Ground-up Python replacement for **Blastware**, Instantel's Windows-only software for 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 @@ -372,8 +372,8 @@ Do NOT use fixed absolute offsets for sample_rate or record_time. | record_time | float32 BE at anchor + 10 | | trigger_level_geo | float32 BE, located in channel block | | 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. | -| 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_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. | +| 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 | | 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 Channels: Enable all geophones (bool), Trigger Source (bool) - 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) - Chan 4 Trigger Level (dB or psi depending on units) diff --git a/docs/instantel_protocol_reference.md b/docs/instantel_protocol_reference.md index 73ff02b..4b05488 100644 --- a/docs/instantel_protocol_reference.md +++ b/docs/instantel_protocol_reference.md @@ -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-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.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 — Trigger Source | bool | ❓ | | 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 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 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 | ✅ `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 — Trigger Source | bool | ❓ | | 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 | 100–148 dB in 1 dB steps | | 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) | 1–105 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. | -| 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. | +| 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 (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"` | | Sample Rate | §3.8.2 | cfg anchor−2, 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[anchor−3]`, uint8. Read (E5 sf1): `data[anchor−4]`, uint8. Note: extra `0x10` byte at read `data[anchor−3]` shifts offset by 1 vs write. | uint8 | `0x00`=Single Shot, `0x01`=Continuous, `0x02`=unknown, `0x03`=Histogram, `0x04`=Histogram+Continuous. ✅ CONFIRMED 2026-04-20 | diff --git a/minimateplus/client.py b/minimateplus/client.py index 6e4d435..de3a850 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -854,6 +854,7 @@ class MiniMateClient: # Threshold parameters (geo channels, in/s) trigger_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: Optional[str] = None, client_name: Optional[str] = None, @@ -875,9 +876,11 @@ class MiniMateClient: sample_rate : int — samples/sec; valid values: 1024, 2048, 4096 record_time : float — record duration in seconds (e.g. 2.0, 3.0) - Trigger/alarm thresholds (geo channels, in/s): - trigger_level_geo : float — trigger threshold (e.g. 0.5) - alarm_level_geo : float — alarm threshold (e.g. 1.0) + Trigger/alarm thresholds and range (geo channels): + trigger_level_geo : float — trigger threshold in/s (e.g. 0.5) + 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 : str @@ -923,6 +926,7 @@ class MiniMateClient: histogram_interval_sec=histogram_interval_sec, trigger_level_geo=trigger_level_geo, alarm_level_geo=alarm_level_geo, + geo_range=geo_range, project=project, client_name=client_name, operator=operator, @@ -1660,6 +1664,7 @@ def _encode_compliance_config( record_time: Optional[float] = None, trigger_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, project: Optional[str] = None, client_name: Optional[str] = None, @@ -1687,11 +1692,16 @@ def _encode_compliance_config( record_time → float32 BE at anchor_pos + 6 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 + "in.\\x00" → unit string at tran_pos + 38 (layout guard) 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. + "/s\\x00\\x00" → unit string at tran_pos + 46 (layout guard) + NOTE: tran_pos+28 (float32 = 6.206053) is the ADC-to-velocity scale factor + (= 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. 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) # ── Numeric: channel block (Tran label + unit-string guard) ─────────────── - # NOTE: tran_pos+28 (float32 = 6.206053) is the ADC-to-velocity scale factor - # (1/sensitivity, (in/s)/V — Interface Handbook §4.5: 1.61133 V × 6.206053 = 10.000 in/s). - # Hardware/firmware constant — never written here. - _needs_channel = any(v is not None for v in (trigger_level_geo, alarm_level_geo)) + # NOTE: tran_pos+24 (write format) or tran_pos+28 (E5 read format) is the + # ADC-to-velocity scale factor (6.206053, hardware constant — never written). + # geo_range is written to ALL THREE geo channel blocks (Tran, Vert, Long), + # 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: _tran = buf.find(b"Tran", 44) _valid = ( @@ -1766,7 +1777,7 @@ def _encode_compliance_config( if not _valid: log.warning( "_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: if trigger_level_geo is not None: @@ -1775,6 +1786,19 @@ def _encode_compliance_config( if alarm_level_geo is not None: 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) + 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) ─────────────────── 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. # # "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; confirmed: 1.61133 V × 6.206053 = 10.000 in/s Normal range; hardware constant — do NOT write) + # adc_scale float32_BE at tran_pos + 28 (= 1/sensitivity = 6.206053 (in/s)/V; 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) ✅ - # "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) ✅ - # "/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 # 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 + 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.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] diff --git a/minimateplus/models.py b/minimateplus/models.py index a3d144f..6cd8e74 100644 --- a/minimateplus/models.py +++ b/minimateplus/models.py @@ -361,9 +361,11 @@ class ComplianceConfig: # 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. - geo_range: Optional[int] = None # 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) + geo_range: Optional[int] = None # range/sensitivity selector — CONFIRMED 2026-04-20 + # 0x00 = Normal 10.000 in/s (standard gain) + # 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) # These are the FULL project metadata from compliance config, diff --git a/sfm/server.py b/sfm/server.py index 912e95d..fa9e696 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -292,7 +292,7 @@ def _serialise_compliance_config(cc: Optional["ComplianceConfig"]) -> Optional[d "trigger_level_geo": cc.trigger_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_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, "project": cc.project, "client": cc.client, @@ -842,10 +842,11 @@ class DeviceConfigBody(BaseModel): sample_rate : Samples per second. Valid values: 1024, 2048, 4096. 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). 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 : Project description. @@ -859,9 +860,10 @@ class DeviceConfigBody(BaseModel): sample_rate: Optional[int] = None record_time: Optional[float] = None 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 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: Optional[str] = None client_name: Optional[str] = None @@ -923,6 +925,7 @@ def device_config( histogram_interval_sec=body.histogram_interval_sec, trigger_level_geo=body.trigger_level_geo, alarm_level_geo=body.alarm_level_geo, + geo_range=body.geo_range, project=body.project, client_name=body.client_name, operator=body.operator, diff --git a/sfm/sfm_webapp.html b/sfm/sfm_webapp.html index a575857..a6c8f72 100644 --- a/sfm/sfm_webapp.html +++ b/sfm/sfm_webapp.html @@ -856,6 +856,16 @@
Alarm flagged when any geo channel exceeds this level
+
+ + +
Geophone sensitivity (applies to Tran / Vert / Long channels)
+
+ @@ -1354,6 +1364,7 @@ function populateDeviceTab() { ['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` : '—'], ['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` : '—'], ['Setup Name', cc.setup_name || '—'], ]; @@ -1389,8 +1400,9 @@ function populateConfigFromDeviceInfo() { if (cc.sample_rate) qs('cfg-sample-rate', String(cc.sample_rate)); if (cc.histogram_interval_sec != null) qs('cfg-histogram-interval', String(cc.histogram_interval_sec)); 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.alarm_level_geo != null) qs('cfg-alarm', cc.alarm_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.geo_range != null) qs('cfg-geo-range', String(cc.geo_range)); if (cc.project) qs('cfg-project', cc.project); if (cc.client) qs('cfg-client', cc.client); if (cc.operator) qs('cfg-operator', cc.operator); @@ -1400,7 +1412,8 @@ function populateConfigFromDeviceInfo() { function clearConfigForm() { ['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 = ''; }); setCfgStatus(''); } @@ -1441,6 +1454,8 @@ async function writeConfig() { if (trig) body.trigger_level_geo = parseFloat(trig); const alarm = qs('cfg-alarm').value; 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(); if (proj) body.project = proj; const cli = qs('cfg-client').value.trim();