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")
|
log.debug("read_compliance_config: 1A data request offset=0x2A params[2]=0x08")
|
||||||
self._send(build_bw_frame(SUB_COMPLIANCE, 0x2A, _DATA_PARAMS))
|
self._send(build_bw_frame(SUB_COMPLIANCE, 0x2A, _DATA_PARAMS))
|
||||||
|
|
||||||
# ── Accumulate E5 frames ──────────────────────────────────────────────
|
# ── Multi-request accumulation ────────────────────────────────────────
|
||||||
# BE18189 sends one large frame; BE11529 may chunk into many small ones.
|
#
|
||||||
# Loop until target bytes received or inter-frame gap expires.
|
# Full BW sequence (confirmed from raw_bw captures 3-11-26):
|
||||||
_TARGET = 0x082A # 2090 bytes — known total from BW captures
|
#
|
||||||
_INTER_FRAME_TIMEOUT = 2.0 # seconds; give up if no new frame within this
|
# 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.
|
||||||
|
|
||||||
|
_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
|
||||||
|
|
||||||
|
_STEPS = [
|
||||||
|
("B", 0x0400, _DATA_PARAMS_B),
|
||||||
|
("C", 0x0400, _DATA_PARAMS_C),
|
||||||
|
("D", 0x002A, _DATA_PARAMS), # _DATA_PARAMS built above
|
||||||
|
]
|
||||||
|
|
||||||
config = bytearray()
|
config = bytearray()
|
||||||
frame_count = 0
|
|
||||||
|
|
||||||
while len(config) < _TARGET:
|
for step_name, step_offset, step_params in _STEPS:
|
||||||
timeout = self._recv_timeout if frame_count == 0 else _INTER_FRAME_TIMEOUT
|
log.debug(
|
||||||
# reset_parser=True only for the first frame; subsequent frames must not
|
"read_compliance_config: sending frame %s offset=0x%04X params=%s",
|
||||||
# discard bytes already buffered from the device's chunked response stream.
|
step_name, step_offset, step_params.hex(),
|
||||||
|
)
|
||||||
|
self._send(build_bw_frame(SUB_COMPLIANCE, step_offset, step_params))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data_rsp = self._recv_one(
|
data_rsp = self._recv_one(expected_sub=rsp_sub)
|
||||||
expected_sub=rsp_sub,
|
|
||||||
timeout=timeout,
|
|
||||||
reset_parser=(frame_count == 0),
|
|
||||||
)
|
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
if frame_count == 0:
|
|
||||||
raise # hard fail — device never responded at all
|
|
||||||
# Inter-frame gap expired — device finished sending
|
|
||||||
log.warning(
|
log.warning(
|
||||||
"read_compliance_config: inter-frame timeout after %d frames "
|
"read_compliance_config: frame %s — no E5 response (timeout); "
|
||||||
"(%d / %d cfg bytes received)",
|
"device may return all data on a later request",
|
||||||
frame_count, len(config), _TARGET,
|
step_name,
|
||||||
)
|
)
|
||||||
break
|
continue
|
||||||
|
|
||||||
frame_count += 1
|
|
||||||
|
|
||||||
if frame_count == 1:
|
|
||||||
# First frame: strip 11-byte echo header (length + key echo + padding)
|
|
||||||
chunk = data_rsp.data[11:]
|
chunk = data_rsp.data[11:]
|
||||||
log.warning(
|
log.warning(
|
||||||
"read_compliance_config: frame %d page=0x%04X "
|
"read_compliance_config: frame %s page=0x%04X data=%d "
|
||||||
"data=%d cfg_chunk=%d (header stripped)",
|
"cfg_chunk=%d running_total=%d",
|
||||||
frame_count, data_rsp.page_key, len(data_rsp.data), len(chunk),
|
step_name, data_rsp.page_key, len(data_rsp.data),
|
||||||
|
len(chunk), len(config) + 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.extend(chunk)
|
config.extend(chunk)
|
||||||
log.warning(
|
|
||||||
"read_compliance_config: running total=%d / %d",
|
|
||||||
len(config), _TARGET,
|
|
||||||
)
|
|
||||||
|
|
||||||
log.warning(
|
log.warning(
|
||||||
"read_compliance_config: done — %d frame(s), %d cfg bytes total",
|
"read_compliance_config: done — %d cfg bytes total",
|
||||||
frame_count, len(config),
|
len(config),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Hex dump first 128 bytes for field mapping
|
# Hex dump first 128 bytes for field mapping
|
||||||
|
|||||||
Reference in New Issue
Block a user