fix: rename max_geo_range to ADC scale, and make it so its not user configurable.

fix: change max_geo_range_enum to geo_range with two options (normal and sensitive)
This commit is contained in:
2026-04-19 18:15:23 -04:00
parent b23cf4bb50
commit aa28495a43
6 changed files with 24 additions and 53 deletions
+2 -2
View File
@@ -561,8 +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, # hw constant 6.206053 "geo_adc_scale": cc.geo_adc_scale if cc else None, # hw scale factor (in/s)/V
"max_range_geo_enum": cc.max_range_geo_enum if cc else None, # 0x01=10in/s, 0x00=1.25in/s (unconfirmed) "geo_range": cc.geo_range if cc else None, # 0x01=Normal 10in/s, 0x00=Sensitive 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,
+9 -24
View File
@@ -852,7 +852,6 @@ 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,
max_range_geo: Optional[float] = None,
# Project / operator strings # Project / operator strings
project: Optional[str] = None, project: Optional[str] = None,
client_name: Optional[str] = None, client_name: Optional[str] = None,
@@ -876,10 +875,6 @@ 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 — ADC-to-velocity scale factor (= 1/sensitivity, in/s per V)
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
@@ -923,7 +918,6 @@ class MiniMateClient:
record_time=record_time, record_time=record_time,
trigger_level_geo=trigger_level_geo, trigger_level_geo=trigger_level_geo,
alarm_level_geo=alarm_level_geo, alarm_level_geo=alarm_level_geo,
max_range_geo=max_range_geo,
project=project, project=project,
client_name=client_name, client_name=client_name,
operator=operator, operator=operator,
@@ -1660,7 +1654,6 @@ 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,
max_range_geo: Optional[float] = None,
project: Optional[str] = None, project: Optional[str] = None,
client_name: Optional[str] = None, client_name: Optional[str] = None,
operator: Optional[str] = None, operator: Optional[str] = None,
@@ -1687,7 +1680,7 @@ def _encode_compliance_config(
(= 1/sensitivity, in/s per V) for the standard Instantel geophone. Confirmed (= 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. 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. 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. It must NOT be written — do not add it back as a parameter.
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
@@ -1728,17 +1721,9 @@ 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: max_range_geo (float32 at tran_pos+28) is the ADC-to-velocity scale factor # NOTE: tran_pos+28 (float32 = 6.206053) is the ADC-to-velocity scale factor
# (1/sensitivity = 6.206053 (in/s)/V — confirmed: 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).
# It is a hardware/firmware constant common to all MiniMate Plus S3 units. # Hardware/firmware constant — never written here.
# 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)) _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)
@@ -1905,7 +1890,7 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
except Exception as exc: except Exception as exc:
log.warning("compliance_config: project string extraction failed: %s", exc) log.warning("compliance_config: project string extraction failed: %s", exc)
# ── Channel block: trigger_level_geo, alarm_level_geo, max_range_geo ───── # ── Channel block: trigger_level_geo, alarm_level_geo, geo_range, geo_adc_scale ──
# 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
@@ -1930,14 +1915,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_enum = data[tran_pos + 20] # range selector enum (see above) config.geo_range = data[tran_pos + 20] # range selector (0x01=Normal 10in/s, 0x00=Sensitive 1.25in/s — unconfirmed)
config.max_range_geo = struct.unpack_from(">f", data, tran_pos + 28)[0] # hw constant 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]
log.debug( log.debug(
"compliance_config: trigger=%.4f alarm=%.4f range_enum=0x%02X hw_const=%.6f", "compliance_config: trigger=%.4f alarm=%.4f geo_range=0x%02X geo_adc_scale=%.6f",
config.trigger_level_geo, config.alarm_level_geo, config.trigger_level_geo, config.alarm_level_geo,
config.max_range_geo_enum, config.max_range_geo, config.geo_range, config.geo_adc_scale,
) )
elif tran_pos >= 0: elif tran_pos >= 0:
log.warning( log.warning(
+3 -3
View File
@@ -346,14 +346,14 @@ class ComplianceConfig:
# 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 # ADC-to-velocity scale factor (float32 at Tran+28) ✅ geo_adc_scale: Optional[float] = None # ADC-to-velocity scale factor (float32 at Tran+28) ✅
# = inverse sensitivity = 1/sensitivity (in/s per V) # = inverse sensitivity = 1/sensitivity (in/s per V)
# Formula (Interface Handbook §4.5): Range = 1.61133 V × scale_factor # Formula (Interface Handbook §4.5): Range = 1.61133 V × scale_factor
# → 1.61133 × 6.206053 = 10.000 in/s (Normal range) ✅ # → 1.61133 × 6.206053 = 10.000 in/s (Normal range) ✅
# 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 (use _encode_compliance_config). # NOT a user-configurable setting. Must NOT be written.
max_range_geo_enum: Optional[int] = None # max range selector: uint8 at Tran+20 geo_range: Optional[int] = None # range selector: uint8 at Tran+20
# hypothesis: 0x01 = Normal 10.000 in/s, 0x00 = Sensitive 1.25 in/s # 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) # reads 0x01 on all tested units — UNCONFIRMED (need 1.25 in/s capture)
+2 -8
View File
@@ -289,8 +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, # hw constant 6.206053 — informational only, do not write "geo_adc_scale": cc.geo_adc_scale, # hw scale factor (in/s)/V — informational only, do not write
"max_range_geo_enum": cc.max_range_geo_enum, # 0x01=Normal 10in/s, 0x00=Extended 1.25in/s (unconfirmed) "geo_range": cc.geo_range, # range selector: 0x01=Normal 10in/s, 0x00=Sensitive 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,
@@ -843,10 +843,6 @@ 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 : DEPRECATED was misidentified as the max range; it is actually a
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)
---------------------------- ----------------------------
project : Project description. project : Project description.
@@ -861,7 +857,6 @@ class DeviceConfigBody(BaseModel):
# Threshold parameters # Threshold parameters
trigger_level_geo: Optional[float] = None trigger_level_geo: Optional[float] = None
alarm_level_geo: Optional[float] = None alarm_level_geo: Optional[float] = None
max_range_geo: Optional[float] = None
# Project / operator strings # Project / operator strings
project: Optional[str] = None project: Optional[str] = None
client_name: Optional[str] = None client_name: Optional[str] = None
@@ -920,7 +915,6 @@ def device_config(
record_time=body.record_time, record_time=body.record_time,
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,
max_range_geo=body.max_range_geo,
project=body.project, project=body.project,
client_name=body.client_name, client_name=body.client_name,
operator=body.operator, operator=body.operator,
+5 -13
View File
@@ -831,11 +831,6 @@
<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>Max Range — Geo (in/s)</label>
<input type="number" id="cfg-max-range" step="0.001" min="0.001" placeholder="e.g. 6.206" />
<div class="hint">Full-scale calibration constant — only change if you have a cal cert</div>
</div>
</div> </div>
<!-- Project / operator strings --> <!-- Project / operator strings -->
@@ -1037,7 +1032,7 @@ let unitInfo = null;
let eventList = []; let eventList = [];
let currentEvent = 0; let currentEvent = 0;
let charts = {}; let charts = {};
let geoRange = 6.206; let geoAdcScale = 6.206;
const DBL_REF = 2.9e-9; // 20 µPa in psi — reference pressure for dBL const DBL_REF = 2.9e-9; // 20 µPa in psi — reference pressure for dBL
const CHANNEL_COLORS = { Tran:'#58a6ff', Vert:'#3fb950', Long:'#d29922', Mic:'#bc8cff' }; const CHANNEL_COLORS = { Tran:'#58a6ff', Vert:'#3fb950', Long:'#d29922', Mic:'#bc8cff' };
@@ -1189,7 +1184,7 @@ function populateDeviceBar() {
qs('di-project').textContent = cc.project || '—'; qs('di-project').textContent = cc.project || '—';
qs('di-client').textContent = cc.client || '—'; qs('di-client').textContent = cc.client || '—';
qs('di-operator').textContent = cc.operator || '—'; qs('di-operator').textContent = cc.operator || '—';
geoRange = cc.max_range_geo ?? 6.206; geoAdcScale = cc.geo_adc_scale ?? 6.206;
} }
// ── Monitoring ───────────────────────────────────────────────────────────────── // ── Monitoring ─────────────────────────────────────────────────────────────────
@@ -1331,7 +1326,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.max_range_geo != null ? `${cc.max_range_geo.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 || '—'],
]; ];
renderTable('compliance-table', complianceRows); renderTable('compliance-table', complianceRows);
@@ -1366,7 +1361,6 @@ 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.max_range_geo != null) qs('cfg-max-range',cc.max_range_geo.toFixed(4));
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);
@@ -1375,7 +1369,7 @@ function populateConfigFromDeviceInfo() {
} }
function clearConfigForm() { function clearConfigForm() {
['cfg-sample-rate','cfg-record-time','cfg-trigger','cfg-alarm','cfg-max-range', ['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']
.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('');
@@ -1413,8 +1407,6 @@ 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 mr = qs('cfg-max-range').value;
if (mr) body.max_range_geo = parseFloat(mr);
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();
@@ -1565,7 +1557,7 @@ function renderWaveform(data) {
let plotData, peakLabel, yUnit, ttFmt, tickFmt; let plotData, peakLabel, yUnit, ttFmt, tickFmt;
if (isGeo) { if (isGeo) {
const scale = geoRange / 32767; const scale = geoAdcScale / 32767;
plotData = samples.map(s => s * scale); plotData = samples.map(s => s * scale);
// Use the device-recorded peak from the 0C waveform record — authoritative // Use the device-recorded peak from the 0C waveform record — authoritative
// and matches Blastware. Computing from raw samples can catch rogue // and matches Blastware. Computing from raw samples can catch rogue
+3 -3
View File
@@ -240,7 +240,7 @@
let charts = {}; let charts = {};
let lastData = null; let lastData = null;
let unitInfo = null; let unitInfo = null;
let geoRange = 10.0; // in/s full-scale for geo channels; updated on connect let geoAdcScale = 10.0; // in/s full-scale for geo channels; updated on connect
let eventList = []; // populated from /device/events after connect let eventList = []; // populated from /device/events after connect
let currentEventIndex = 0; let currentEventIndex = 0;
@@ -278,7 +278,7 @@
throw new Error(err.detail || resp.statusText); throw new Error(err.detail || resp.statusText);
} }
unitInfo = await resp.json(); unitInfo = await resp.json();
geoRange = unitInfo.compliance_config?.max_range_geo ?? 10.0; geoAdcScale = unitInfo.compliance_config?.geo_adc_scale ?? 10.0;
} catch (e) { } catch (e) {
setStatus(`Error: ${e.message}`, 'error'); setStatus(`Error: ${e.message}`, 'error');
btn.disabled = false; btn.disabled = false;
@@ -457,7 +457,7 @@
if (isGeo) { if (isGeo) {
// Geo channels: counts × (range / 32767) → in/s // Geo channels: counts × (range / 32767) → in/s
const scale = geoRange / 32767; const scale = geoAdcScale / 32767;
plotSamples = samples.map(c => c * scale); plotSamples = samples.map(c => c * scale);
const peakIns = Math.max(...plotSamples.map(Math.abs)); const peakIns = Math.max(...plotSamples.map(Math.abs));
peakLabel = `${peakIns.toFixed(5)} in/s`; peakLabel = `${peakIns.toFixed(5)} in/s`;