diff --git a/bridges/ach_server.py b/bridges/ach_server.py index 6cea729..65fb9e9 100644 --- a/bridges/ach_server.py +++ b/bridges/ach_server.py @@ -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, diff --git a/minimateplus/client.py b/minimateplus/client.py index 92e317c..6cc5edd 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -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( diff --git a/minimateplus/models.py b/minimateplus/models.py index 664fff7..67c8b7b 100644 --- a/minimateplus/models.py +++ b/minimateplus/models.py @@ -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) diff --git a/sfm/server.py b/sfm/server.py index f88ee34..5fbaf0b 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -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, diff --git a/sfm/sfm_webapp.html b/sfm/sfm_webapp.html index d59ea41..db3745b 100644 --- a/sfm/sfm_webapp.html +++ b/sfm/sfm_webapp.html @@ -831,11 +831,6 @@