feat: mapped record_mode protocol

This commit is contained in:
2026-04-20 15:49:31 -04:00
parent f10c5c1b86
commit e04114fd6c
4 changed files with 119 additions and 11 deletions
+46 -3
View File
@@ -847,6 +847,7 @@ class MiniMateClient:
self,
*,
# Recording parameters
recording_mode: Optional[int] = None,
sample_rate: Optional[int] = None,
record_time: Optional[float] = None,
# Threshold parameters (geo channels, in/s)
@@ -869,6 +870,7 @@ class MiniMateClient:
Configurable fields
-------------------
Recording parameters:
recording_mode : int — 0x00=Single Shot, 0x01=Continuous, 0x03=Histogram, 0x04=Histogram+Continuous
sample_rate : int — samples/sec; valid values: 1024, 2048, 4096
record_time : float — record duration in seconds (e.g. 2.0, 3.0)
@@ -914,6 +916,7 @@ class MiniMateClient:
# 2. Patch the compliance buffer and build the 2128-byte write payload
compliance_data = _encode_compliance_config(
compliance_raw,
recording_mode=recording_mode,
sample_rate=sample_rate,
record_time=record_time,
trigger_level_geo=trigger_level_geo,
@@ -1650,6 +1653,7 @@ def _extract_project_strings(data: bytes) -> Optional[ProjectInfo]:
def _encode_compliance_config(
raw: bytes,
*,
recording_mode: Optional[int] = None,
sample_rate: Optional[int] = None,
record_time: Optional[float] = None,
trigger_level_geo: Optional[float] = None,
@@ -1670,6 +1674,11 @@ def _encode_compliance_config(
DLE-jitter shifts):
Anchor: b'\\xbe\\x80\\x00\\x00\\x00\\x00' (confirmed stable, both BE11529 and BE18189)
recording_mode → uint8 at anchor_pos - 7 (write payload; one byte earlier than read format)
Values: 0x00=Single Shot, 0x01=Continuous, 0x03=Histogram, 0x04=Histogram+Continuous
NOTE: In the E5 read response the field is at anchor_pos - 8 due to an extra
0x10 byte at anchor_pos - 7 present only in the read format. The write
payload does NOT have this extra byte — use anchor_pos - 7 here.
sample_rate → uint16 BE at anchor_pos - 6
record_time → float32 BE at anchor_pos + 6
@@ -1702,10 +1711,18 @@ def _encode_compliance_config(
buf = bytearray(raw)
# ── Numeric: sample_rate + record_time (anchor-relative) ─────────────────
# ── Numeric: recording_mode + sample_rate + record_time (anchor-relative) ──
_ANC = b'\xbe\x80\x00\x00\x00\x00'
_anc = buf.find(_ANC, 0, 150)
if recording_mode is not None:
if _anc < 7:
log.warning("_encode_compliance_config: anchor not found — cannot write recording_mode")
else:
buf[_anc - 7] = recording_mode & 0xFF
log.debug("_encode_compliance_config: recording_mode=0x%02X -> offset %d",
recording_mode, _anc - 7)
if sample_rate is not None:
if _anc < 6:
log.warning("_encode_compliance_config: anchor not found — cannot write sample_rate")
@@ -1838,7 +1855,22 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
# record_time : float32 BE at anchor_pos + 6
_ANCHOR = b'\xbe\x80\x00\x00\x00\x00'
_anchor = data.find(_ANCHOR, 0, 150)
if _anchor >= 6 and _anchor + 10 <= len(data):
if _anchor >= 8 and _anchor + 10 <= len(data):
# Layout (E5 read format, relative to 6-byte anchor suffix):
# _anchor - 8 : recording_mode (uint8)
# _anchor - 7 : 0x10 (extra byte present in E5 read only; absent in SUB 71 write)
# _anchor - 6 : sample_rate_hi (uint8, MSB of uint16 BE)
# _anchor - 5 : sample_rate_lo (uint8, LSB of uint16 BE)
# _anchor - 4 : \x01\x2c\x00\x00 (10-byte anchor prefix)
# _anchor : \xbe\x80\x00\x00\x00\x00 (6-byte anchor suffix)
# _anchor + 6 : record_time (float32 BE)
try:
config.recording_mode = data[_anchor - 8]
log.debug(
"compliance_config: recording_mode = 0x%02X (anchor@%d)", config.recording_mode, _anchor
)
except Exception as exc:
log.warning("compliance_config: recording_mode extraction failed: %s", exc)
try:
config.sample_rate = struct.unpack_from(">H", data, _anchor - 6)[0]
log.debug(
@@ -1853,10 +1885,21 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
)
except Exception as exc:
log.warning("compliance_config: record_time extraction failed: %s", exc)
elif _anchor >= 6 and _anchor + 10 <= len(data):
# Fallback: anchor found but not enough bytes before it for recording_mode
log.warning("compliance_config: anchor too close to start (anchor@%d) — skipping recording_mode", _anchor)
try:
config.sample_rate = struct.unpack_from(">H", data, _anchor - 6)[0]
except Exception:
pass
try:
config.record_time = struct.unpack_from(">f", data, _anchor + 6)[0]
except Exception:
pass
else:
log.warning(
"compliance_config: anchor %s not found in cfg[0:150] (len=%d) "
"— sample_rate and record_time will be None",
"— sample_rate, record_time and recording_mode will be None",
_ANCHOR.hex(), len(data),
)
+6 -2
View File
@@ -338,8 +338,12 @@ class ComplianceConfig:
raw: Optional[bytes] = None # full 2090-byte payload (for debugging)
# Recording parameters (✅ CONFIRMED from §7.6)
record_time: Optional[float] = None # seconds (7.0, 10.0, 13.0, etc.)
sample_rate: Optional[int] = None # sps (1024, 2048, 4096, etc.) — NOT YET FOUND ❓
recording_mode: Optional[int] = None # uint8: 0x00=Single Shot, 0x01=Continuous,
# 0x03=Histogram, 0x04=Histogram+Continuous ✅ confirmed 2026-04-20
# Read (E5 sf1): data[anchor_pos - 4]
# Write (SUB 71 payload): cfg[anchor_pos - 3]
record_time: Optional[float] = None # seconds (7.0, 10.0, 13.0, etc.)
sample_rate: Optional[int] = None # sps (1024, 2048, 4096, etc.)
# Trigger/alarm levels (✅ CONFIRMED per-channel at §7.6)
# For now we store the first geo channel (Transverse) as representatives;