client: use anchor-relative offsets for record_time and sample_rate

Fixed a 1-byte offset jitter that produced garbage values when the
device was set to "faster" (4096 Sa/s) mode.

Root cause: 4096 = 0x1000, so the sample_rate bytes in the raw S3
frame are `10 10 00` (DLE-escaped).  After DLE unstuffing → `10 00`
(2 bytes vs 3 for 1024/2048), making frame C 1 byte shorter and
shifting all subsequent field offsets by -1.

Fix: locate the stable 10-byte anchor `01 2c 00 00 be 80 00 00 00 00`
(max-record-limit constant + first two alarm-level floats) and read:
  sample_rate  = uint16_BE at anchor - 2
  record_time  = float32_BE at anchor + 10

Offline-validated against all five logged hex dumps (1071 and 1070
byte cfg, record times 3/5/8 s, sample rates 1024 and 4096):
  all five: correct values with anchor approach.
This commit is contained in:
Brian Harrison
2026-04-01 16:45:40 -04:00
parent df51fe0668
commit ea4475c9ad

View File

@@ -584,18 +584,21 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
(frames B+C+D concatenated, echo headers stripped).
Confirmed field locations (BE11529 with 3-step read, duplicate detection):
- cfg[89] = setup_name: first long ASCII string in cfg[40:250]
- cfg[64] = record_time: float32 BE (BE11529 shows 3.0 s) ✅ candidate
- cfg[52] = sample_rate: uint16 BE (BE11529 shows 1024 Sa/s) ✅ candidate
- cfg[89] = setup_name: first long ASCII string in cfg[40:250]
- ANCHOR = b'\\x01\\x2c\\x00\\x00\\xbe\\x80\\x00\\x00\\x00\\x00' in cfg[40:100] ✅
- anchor - 2 = sample_rate uint16_BE (1024 normal / 2048 fast / 4096 faster)
- anchor + 10 = record_time float32_BE
- "Project:" needle → project string
- "Client:" needle → client string
- "User Name:" needle → operator string
- "Seis Loc:" needle → sensor_location string
- "Extended Notes" needle → notes string
⚠️ record_time and sample_rate offsets are confirmed candidates from
diagnostic scan but have NOT yet been validated by changing device settings
and re-reading. Mark as ✅ once validated.
Anchor approach is required because a DLE byte in the sample_rate field
(4096 = 0x1000 → stored as 10 10 00 in raw S3 frame → unstuffed to 10 00,
1 byte shorter than 04 00 or 08 00) causes frame C to be 1 byte shorter
for "faster" mode, shifting all subsequent offsets by 1. The 10-byte
anchor is stable across all modes.
Modifies info.compliance_config in-place.
"""
@@ -623,16 +626,36 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
except Exception as exc:
log.warning("compliance_config: setup_name extraction failed: %s", exc)
# ── Record Time — float32 BE at cfg[64] ──────────────────────────────────
# Diagnostic confirmed: cfg[64] float32_BE = 3.0 on BE11529 (set to 3 s).
# Previous guess of 0x28 was wrong (that offset holds the "(L)" label string).
# ⚠️ Validate by changing device record time and re-reading — mark ✅ once confirmed.
try:
if len(data) > 68:
config.record_time = struct.unpack_from(">f", data, 64)[0]
log.debug("compliance_config: record_time = %.3f s", config.record_time)
except Exception as exc:
log.warning("compliance_config: record_time extraction failed: %s", exc)
# ── Record time + sample rate — anchor-relative ───────────────────────────
# The 10-byte anchor sits between sample_rate and record_time in the cfg.
# Absolute offsets are NOT reliable because sample_rate = 4096 (0x1000) is
# DLE-escaped in the raw S3 frame (10 10 00 → 10 00 after unstuffing),
# making frame C 1 byte shorter than for 1024/2048 and shifting everything.
# sample_rate: uint16_BE at anchor - 2
# record_time: float32_BE at anchor + 10
_ANCHOR = b'\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00'
_anchor = data.find(_ANCHOR, 40, 100)
if _anchor >= 2 and _anchor + 14 <= len(data):
try:
config.sample_rate = struct.unpack_from(">H", data, _anchor - 2)[0]
log.debug(
"compliance_config: sample_rate = %d Sa/s (anchor@%d)", config.sample_rate, _anchor
)
except Exception as exc:
log.warning("compliance_config: sample_rate extraction failed: %s", exc)
try:
config.record_time = struct.unpack_from(">f", data, _anchor + 10)[0]
log.debug(
"compliance_config: record_time = %.3f s (anchor@%d)", config.record_time, _anchor
)
except Exception as exc:
log.warning("compliance_config: record_time extraction failed: %s", exc)
else:
log.warning(
"compliance_config: anchor %s not found in cfg[40:100] (len=%d) "
"— sample_rate and record_time will be None",
_ANCHOR.hex(), len(data),
)
# ── Project strings ───────────────────────────────────────────────────────
try:
@@ -664,16 +687,6 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
except Exception as exc:
log.warning("compliance_config: project string extraction failed: %s", exc)
# ── Sample Rate — uint16 BE at cfg[52] ───────────────────────────────────
# Diagnostic confirmed: cfg[52] uint16_BE = 1024 on BE11529 (standard mode).
# ⚠️ Validate by changing device sample rate and re-reading — mark ✅ once confirmed.
try:
if len(data) > 54:
config.sample_rate = struct.unpack_from(">H", data, 52)[0]
log.debug("compliance_config: sample_rate = %d Sa/s", config.sample_rate)
except Exception as exc:
log.warning("compliance_config: sample_rate extraction failed: %s", exc)
info.compliance_config = config