protocol: send 3-step data requests for full compliance config (SUB 1A)
Reverse-engineered the full Blastware 4-frame sequence for SUB 1A: A (probe): offset=0x0000, params[7]=0x64 B (data req 1): offset=0x0400, params[2]=0x00, params[7]=0x64 → bytes 0..1023 C (data req 2): offset=0x0400, params[2]=0x04, params[7]=0x64 → bytes 1024..2047 D (data req 3): offset=0x002A, params[2]=0x08, params[7]=0x64 → bytes 2048..2089 We were only sending A+D and getting 44 bytes (the last chunk). Now sends B, C, D in sequence; each E5 response has 11-byte echo header stripped, and chunks are concatenated. Devices that return all data in one frame (BE18189 style) are handled — timeouts on B/C are skipped gracefully and data from D still accumulates. Total expected: 0x0400 + 0x0400 + 0x002A = 0x082A = 2090 bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -436,67 +436,66 @@ class MiniMateProtocol:
|
||||
log.debug("read_compliance_config: 1A data request offset=0x2A params[2]=0x08")
|
||||
self._send(build_bw_frame(SUB_COMPLIANCE, 0x2A, _DATA_PARAMS))
|
||||
|
||||
# ── Accumulate E5 frames ──────────────────────────────────────────────
|
||||
# BE18189 sends one large frame; BE11529 may chunk into many small ones.
|
||||
# Loop until target bytes received or inter-frame gap expires.
|
||||
_TARGET = 0x082A # 2090 bytes — known total from BW captures
|
||||
_INTER_FRAME_TIMEOUT = 2.0 # seconds; give up if no new frame within this
|
||||
config = bytearray()
|
||||
frame_count = 0
|
||||
# ── Multi-request accumulation ────────────────────────────────────────
|
||||
#
|
||||
# Full BW sequence (confirmed from raw_bw captures 3-11-26):
|
||||
#
|
||||
# Frame B: offset=0x0400 params[2]=0x00 → requests cfg bytes 0..1023
|
||||
# Frame C: offset=0x0400 params[2]=0x04 → requests cfg bytes 1024..2047
|
||||
# Frame D: offset=0x002A params[2]=0x08 → requests cfg bytes 2048..2089
|
||||
#
|
||||
# Total: 0x0400 + 0x0400 + 0x002A = 0x082A = 2090 bytes.
|
||||
#
|
||||
# The "offset" field in B and C encodes the chunk length (0x0400 = 1024),
|
||||
# not a byte offset into the config. params[2] tracks cumulative pages
|
||||
# (0x00 → 0x04 → 0x08; each page = 256 bytes → 0x04 pages = 1024 bytes).
|
||||
#
|
||||
# Each request gets its own E5 response with an 11-byte echo header.
|
||||
# Devices that send the full block in a single frame (BE18189) may return
|
||||
# the entire config from the last request alone — we handle both cases by
|
||||
# trying each step and concatenating whatever arrives.
|
||||
|
||||
while len(config) < _TARGET:
|
||||
timeout = self._recv_timeout if frame_count == 0 else _INTER_FRAME_TIMEOUT
|
||||
# reset_parser=True only for the first frame; subsequent frames must not
|
||||
# discard bytes already buffered from the device's chunked response stream.
|
||||
try:
|
||||
data_rsp = self._recv_one(
|
||||
expected_sub=rsp_sub,
|
||||
timeout=timeout,
|
||||
reset_parser=(frame_count == 0),
|
||||
)
|
||||
except TimeoutError:
|
||||
if frame_count == 0:
|
||||
raise # hard fail — device never responded at all
|
||||
# Inter-frame gap expired — device finished sending
|
||||
log.warning(
|
||||
"read_compliance_config: inter-frame timeout after %d frames "
|
||||
"(%d / %d cfg bytes received)",
|
||||
frame_count, len(config), _TARGET,
|
||||
)
|
||||
break
|
||||
_DATA_PARAMS_B = bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00])
|
||||
_DATA_PARAMS_C = bytes([0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00])
|
||||
# _DATA_PARAMS_D already built above as _DATA_PARAMS
|
||||
|
||||
frame_count += 1
|
||||
_STEPS = [
|
||||
("B", 0x0400, _DATA_PARAMS_B),
|
||||
("C", 0x0400, _DATA_PARAMS_C),
|
||||
("D", 0x002A, _DATA_PARAMS), # _DATA_PARAMS built above
|
||||
]
|
||||
|
||||
if frame_count == 1:
|
||||
# First frame: strip 11-byte echo header (length + key echo + padding)
|
||||
chunk = data_rsp.data[11:]
|
||||
log.warning(
|
||||
"read_compliance_config: frame %d page=0x%04X "
|
||||
"data=%d cfg_chunk=%d (header stripped)",
|
||||
frame_count, data_rsp.page_key, len(data_rsp.data), len(chunk),
|
||||
)
|
||||
else:
|
||||
# Subsequent frames: log raw prefix to determine structure,
|
||||
# then strip the same 11-byte header (assumption — verify from logs).
|
||||
raw = data_rsp.data
|
||||
log.warning(
|
||||
"read_compliance_config: frame %d page=0x%04X data=%d "
|
||||
"raw[0:16]=%s",
|
||||
frame_count, data_rsp.page_key, len(raw),
|
||||
raw[:16].hex() if len(raw) >= 16 else raw.hex(),
|
||||
)
|
||||
# Strip header if frame is long enough; otherwise accumulate all
|
||||
chunk = raw[11:] if len(raw) > 11 else raw
|
||||
config = bytearray()
|
||||
|
||||
config.extend(chunk)
|
||||
log.warning(
|
||||
"read_compliance_config: running total=%d / %d",
|
||||
len(config), _TARGET,
|
||||
for step_name, step_offset, step_params in _STEPS:
|
||||
log.debug(
|
||||
"read_compliance_config: sending frame %s offset=0x%04X params=%s",
|
||||
step_name, step_offset, step_params.hex(),
|
||||
)
|
||||
self._send(build_bw_frame(SUB_COMPLIANCE, step_offset, step_params))
|
||||
|
||||
try:
|
||||
data_rsp = self._recv_one(expected_sub=rsp_sub)
|
||||
except TimeoutError:
|
||||
log.warning(
|
||||
"read_compliance_config: frame %s — no E5 response (timeout); "
|
||||
"device may return all data on a later request",
|
||||
step_name,
|
||||
)
|
||||
continue
|
||||
|
||||
chunk = data_rsp.data[11:]
|
||||
log.warning(
|
||||
"read_compliance_config: frame %s page=0x%04X data=%d "
|
||||
"cfg_chunk=%d running_total=%d",
|
||||
step_name, data_rsp.page_key, len(data_rsp.data),
|
||||
len(chunk), len(config) + len(chunk),
|
||||
)
|
||||
config.extend(chunk)
|
||||
|
||||
log.warning(
|
||||
"read_compliance_config: done — %d frame(s), %d cfg bytes total",
|
||||
frame_count, len(config),
|
||||
"read_compliance_config: done — %d cfg bytes total",
|
||||
len(config),
|
||||
)
|
||||
|
||||
# Hex dump first 128 bytes for field mapping
|
||||
|
||||
Reference in New Issue
Block a user