#!/usr/bin/env python3 """Dump K1 persisted W25Q field log via J-Link RTT (`log flash dump` / `log flash scan`).""" from __future__ import annotations import argparse import os import sys import time from datetime import datetime sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from rtt_log_dump import ( # noqa: E402 connect_jlink, find_rtt_control_block, import_deps, send_command, wait_rtt_ready, ) DUMP_CMD = b"log flash dump\n" SCAN_CMD = b"log flash scan 65536\n" def dump_flash_log(out_path: str, device: str, timeout_s: float, cmd: bytes = DUMP_CMD) -> None: jlink = connect_jlink(device) lines: list[str] = [] try: cb = find_rtt_control_block(jlink) if cb is None: raise SystemExit("SEGGER RTT control block not found in SRAM") print(f"RTT CB @ 0x{cb:08X}") jlink.rtt_start(cb) wait_rtt_ready(jlink) for _ in range(5): stale = jlink.rtt_read(0, 4096) if stale: text = bytes(stale).decode("utf-8", errors="replace") if text.strip(): print("RTT0 stale:", text.strip()[:200]) time.sleep(0.05) print(f"Sending: {cmd!r}") send_command(jlink, cmd) buffer = b"" deadline = time.time() + timeout_s while time.time() < deadline: chunk = jlink.rtt_read(0, 8192) if chunk: buffer += bytes(chunk) if len(buffer) >= 4096 and (len(buffer) % 8192) < 300: print(f" received {len(buffer)} bytes...") if b"LOG_FLASH_DUMP_END" in buffer: break else: time.sleep(0.01) if b"LOG_FLASH_DUMP_END" not in buffer: preview = buffer[-500:].decode("utf-8", errors="replace") if buffer else "" raise SystemExit( "Timeout waiting for LOG_FLASH_DUMP_END.\n" f"got {len(buffer)} bytes. tail={preview!r}" ) text = buffer.decode("utf-8", errors="replace") capture = False for line in text.splitlines(): if line.strip() == "LOG_FLASH_DUMP_BEGIN": capture = True continue if line.strip() == "LOG_FLASH_DUMP_END": break if capture: lines.append(line) header = ( f"# K1 flash log dump {datetime.now().isoformat(timespec='seconds')}\n" f"# device={device}\n" f"# cmd={cmd.decode('ascii', errors='replace').strip()}\n" ) body = "\n".join(lines) + ("\n" if lines else "") with open(out_path, "w", encoding="utf-8", newline="\n") as f: f.write(header) f.write(body) print(f"Saved {len(lines)} lines to {out_path}") print(f"Full path: {os.path.abspath(out_path)}") finally: try: jlink.rtt_stop() except Exception: pass jlink.close() def main() -> None: parser = argparse.ArgumentParser(description="Dump K1 Flash field log via RTT") parser.add_argument("--out", default="crash.log") parser.add_argument("--device", default="AT32F403AC") parser.add_argument("--timeout", type=float, default=300.0) parser.add_argument( "--scan", action="store_true", help="Ignore write pointer; scan first 64KB data for recoverable lines", ) args = parser.parse_args() import_deps() cmd = SCAN_CMD if args.scan else DUMP_CMD dump_flash_log(args.out, args.device, args.timeout, cmd=cmd) if __name__ == "__main__": main()