#!/usr/bin/env python3 """Host end of the X68000 serial gate. MAME's null_modem socket form CONNECTS outward, so this side listens. Once the guest's SERIAL.X is up it echoes every byte it receives, so sending a probe string and reading it back proves both directions of the link. The guest takes a while to appear: Human68k boots, then COMMAND.X runs AUTOEXEC.BAT, then SERIAL.X initialises. Rather than guess, this retransmits the probe periodically until the echo comes back or the deadline passes. """ import argparse import socket import sys import time def main(): ap = argparse.ArgumentParser() ap.add_argument("--port", type=int, default=6800) ap.add_argument("--probe", default="JOEYLIB-X68K") ap.add_argument("--timeout", type=float, default=420.0) ap.add_argument("--until", default="", help="listen-only: keep collecting until this text arrives") ap.add_argument("--listen-only", action="store_true", help="never transmit; pass if ANY bytes arrive. Isolates the\n guest TX path (e.g. keyboard -> jlSerialWrite) from echo.") args = ap.parse_args() probe = args.probe.encode("ascii") deadline = time.time() + args.timeout srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind(("127.0.0.1", args.port)) srv.listen(1) srv.settimeout(max(1.0, args.timeout)) print(f"X68SER listening on 127.0.0.1:{args.port}") try: conn, peer = srv.accept() except socket.timeout: print("X68SER FAIL: MAME never connected") return 1 print(f"X68SER connected from {peer}") conn.settimeout(2.0) received = bytearray() sent_total = 0 last_send = 0.0 while time.time() < deadline: # Retransmit periodically: the guest is not listening until SERIAL.X # has booted and opened the port, and we cannot see when that happens. if not args.listen_only and time.time() - last_send > 5.0: try: conn.sendall(probe) sent_total += len(probe) last_send = time.time() except OSError as exc: print(f"X68SER FAIL: send error {exc}") return 1 try: chunk = conn.recv(256) except socket.timeout: continue except OSError as exc: print(f"X68SER FAIL: recv error {exc}") return 1 if not chunk: print("X68SER FAIL: link closed by MAME") return 1 received += chunk print(f"X68SER rx {len(chunk)} bytes: {chunk!r}") if args.listen_only and args.until and args.until.encode() in received: print(f"X68SER PASS: guest transmitted {len(received)} bytes " f"unprompted: {bytes(received)!r}") return 0 if probe in received: elapsed = args.timeout - (deadline - time.time()) print(f"X68SER PASS: probe echoed back after {elapsed:.1f}s " f"(sent {sent_total} bytes, received {len(received)})") return 0 if args.listen_only and received: print(f"X68SER COLLECTED {len(received)} bytes: {bytes(received)!r}") return 0 print(f"X68SER FAIL: timeout. sent={sent_total} received={len(received)} " f"bytes: {bytes(received[:64])!r}") return 1 if __name__ == "__main__": sys.exit(main())