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,
"trigger_level_geo": cc.trigger_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
"max_range_geo_enum": cc.max_range_geo_enum if cc else None, # 0x01=10in/s, 0x00=1.25in/s (unconfirmed)
"geo_adc_scale": cc.geo_adc_scale if cc else None, # hw scale factor (in/s)/V
"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,
"client": cc.client 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)
trigger_level_geo: Optional[float] = None,
alarm_level_geo: Optional[float] = None,
max_range_geo: Optional[float] = None,
# Project / operator strings
project: Optional[str] = None,
client_name: Optional[str] = None,
@@ -876,10 +875,6 @@ class MiniMateClient:
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)
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 : str
@@ -923,7 +918,6 @@ class MiniMateClient:
record_time=record_time,
trigger_level_geo=trigger_level_geo,
alarm_level_geo=alarm_level_geo,
max_range_geo=max_range_geo,
project=project,
client_name=client_name,
operator=operator,
@@ -1660,7 +1654,6 @@ def _encode_compliance_config(
record_time: Optional[float] = None,
trigger_level_geo: Optional[float] = None,
alarm_level_geo: Optional[float] = None,
max_range_geo: Optional[float] = None,
project: Optional[str] = None,
client_name: 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
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.
It must NOT be written — do not add it back as a parameter.
String field locations (64-byte slots, label+22 format):
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)
# ── Numeric: channel block (Tran label + unit-string guard) ───────────────
# NOTE: max_range_geo (float32 at tran_pos+28) is the ADC-to-velocity scale factor
# (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,
)
# 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))
if _needs_channel:
_tran = buf.find(b"Tran", 44)
@@ -1905,7 +1890,7 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
except Exception as 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,
# ~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
@@ -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 + 46 : tran_pos + 50] == b"/s\x00\x00"
):
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.geo_range = data[tran_pos + 20] # range selector (0x01=Normal 10in/s, 0x00=Sensitive 1.25in/s — unconfirmed)
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]
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.max_range_geo_enum, config.max_range_geo,
config.geo_range, config.geo_adc_scale,
)
elif tran_pos >= 0:
log.warning(
+3 -3
View File
@@ -346,14 +346,14 @@ class ComplianceConfig:
# full per-channel data would require structured Channel objects.
trigger_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)
# 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
# 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)
+2 -8
View File
@@ -289,8 +289,8 @@ def _serialise_compliance_config(cc: Optional["ComplianceConfig"]) -> Optional[d
"sample_rate": cc.sample_rate,
"trigger_level_geo": cc.trigger_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
"max_range_geo_enum": cc.max_range_geo_enum, # 0x01=Normal 10in/s, 0x00=Extended 1.25in/s (unconfirmed)
"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)
"setup_name": cc.setup_name,
"project": cc.project,
"client": cc.client,
@@ -843,10 +843,6 @@ class DeviceConfigBody(BaseModel):
------------------------------------------------
trigger_level_geo : Trigger threshold in in/s (e.g. 0.5).
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 : Project description.
@@ -861,7 +857,6 @@ class DeviceConfigBody(BaseModel):
# Threshold parameters
trigger_level_geo: Optional[float] = None
alarm_level_geo: Optional[float] = None
max_range_geo: Optional[float] = None
# Project / operator strings
project: Optional[str] = None
client_name: Optional[str] = None
@@ -920,7 +915,6 @@ def device_config(
record_time=body.record_time,
trigger_level_geo=body.trigger_level_geo,
alarm_level_geo=body.alarm_level_geo,
max_range_geo=body.max_range_geo,
project=body.project,
client_name=body.client_name,
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>
<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>
<!-- Project / operator strings -->
@@ -1037,7 +1032,7 @@ let unitInfo = null;
let eventList = [];
let currentEvent = 0;
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 CHANNEL_COLORS = { Tran:'#58a6ff', Vert:'#3fb950', Long:'#d29922', Mic:'#bc8cff' };
@@ -1189,7 +1184,7 @@ function populateDeviceBar() {
qs('di-project').textContent = cc.project || '—';
qs('di-client').textContent = cc.client || '—';
qs('di-operator').textContent = cc.operator || '—';
geoRange = cc.max_range_geo ?? 6.206;
geoAdcScale = cc.geo_adc_scale ?? 6.206;
}
// ── Monitoring ─────────────────────────────────────────────────────────────────
@@ -1331,7 +1326,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.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 || '—'],
];
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.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.max_range_geo != null) qs('cfg-max-range',cc.max_range_geo.toFixed(4));
if (cc.project) qs('cfg-project', cc.project);
if (cc.client) qs('cfg-client', cc.client);
if (cc.operator) qs('cfg-operator', cc.operator);
@@ -1375,7 +1369,7 @@ function populateConfigFromDeviceInfo() {
}
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']
.forEach(id => { const el = qs(id); el.tagName === 'SELECT' ? el.selectedIndex = 0 : el.value = ''; });
setCfgStatus('');
@@ -1413,8 +1407,6 @@ 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 mr = qs('cfg-max-range').value;
if (mr) body.max_range_geo = parseFloat(mr);
const proj = qs('cfg-project').value.trim();
if (proj) body.project = proj;
const cli = qs('cfg-client').value.trim();
@@ -1565,7 +1557,7 @@ function renderWaveform(data) {
let plotData, peakLabel, yUnit, ttFmt, tickFmt;
if (isGeo) {
const scale = geoRange / 32767;
const scale = geoAdcScale / 32767;
plotData = samples.map(s => s * scale);
// Use the device-recorded peak from the 0C waveform record — authoritative
// and matches Blastware. Computing from raw samples can catch rogue
+3 -3
View File
@@ -240,7 +240,7 @@
let charts = {};
let lastData = 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 currentEventIndex = 0;
@@ -278,7 +278,7 @@
throw new Error(err.detail || resp.statusText);
}
unitInfo = await resp.json();
geoRange = unitInfo.compliance_config?.max_range_geo ?? 10.0;
geoAdcScale = unitInfo.compliance_config?.geo_adc_scale ?? 10.0;
} catch (e) {
setStatus(`Error: ${e.message}`, 'error');
btn.disabled = false;
@@ -457,7 +457,7 @@
if (isGeo) {
// Geo channels: counts × (range / 32767) → in/s
const scale = geoRange / 32767;
const scale = geoAdcScale / 32767;
plotSamples = samples.map(c => c * scale);
const peakIns = Math.max(...plotSamples.map(Math.abs));
peakLabel = `${peakIns.toFixed(5)} in/s`;