diff --git a/minimateplus/protocol.py b/minimateplus/protocol.py index b3a32c6..ad7a921 100644 --- a/minimateplus/protocol.py +++ b/minimateplus/protocol.py @@ -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