#!/usr/bin/env python3 """Send one Sony RCP-TX7 candidate serial frame. Use carefully. This is for controlled experiments on the host-to-RCP line through an RS-232 adapter. It can either send a complete hex frame or build the observed 6-byte frame shape and checksum from fields. """ from __future__ import annotations import argparse import sys import time try: import serial except ImportError: print( "Missing dependency: pyserial\n" "Install it with: python -m pip install pyserial", file=sys.stderr, ) raise SystemExit(2) def parse_hex_bytes(text: str) -> bytes: normalized = text.replace(",", " ").replace("0x", "").replace("0X", "") parts = normalized.split() try: data = bytes(int(part, 16) for part in parts) except ValueError as exc: raise argparse.ArgumentTypeError(f"invalid hex byte list: {text}") from exc if not data: raise argparse.ArgumentTypeError("hex frame cannot be empty") if any(part and int(part, 16) > 0xFF for part in parts): raise argparse.ArgumentTypeError("hex values must be bytes") return data def build_frame(prefix1: int, prefix2: int, command: int, state: int, value: int) -> bytes: body = bytes([prefix1, prefix2, command, state, value]) checksum = 0x5A for byte in body: checksum ^= byte return body + bytes([checksum]) def hex_preview(data: bytes) -> str: return " ".join(f"{byte:02X}" for byte in data) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Send an explicit or checksum-built 6-byte RCP-TX7 frame." ) parser.add_argument("--port", required=True, help="serial port, for example COM3") parser.add_argument("--baud", type=int, default=38400) parser.add_argument( "--frame", type=parse_hex_bytes, help="complete hex frame, for example \"00 00 00 00 80 DA\"", ) parser.add_argument("--prefix1", type=lambda s: int(s, 0), default=0) parser.add_argument("--prefix2", type=lambda s: int(s, 0), default=0) parser.add_argument("--command", type=lambda s: int(s, 0), default=0) parser.add_argument("--state", type=lambda s: int(s, 0), default=0) parser.add_argument("--value", type=lambda s: int(s, 0), default=0x80) parser.add_argument("--repeat", type=int, default=1) parser.add_argument("--interval", type=float, default=0.2) parser.add_argument( "--dry-run", action="store_true", help="print the frame but do not open the serial port", ) return parser.parse_args() def validate_byte(name: str, value: int) -> None: if not 0 <= value <= 0xFF: raise SystemExit(f"{name} must be a byte, got {value!r}") def main() -> int: args = parse_args() if args.frame: frame = args.frame else: for name in ("prefix1", "prefix2", "command", "state", "value"): validate_byte(name, getattr(args, name)) frame = build_frame(args.prefix1, args.prefix2, args.command, args.state, args.value) print(f"Frame: {hex_preview(frame)}") if args.dry_run: return 0 with serial.Serial( port=args.port, baudrate=args.baud, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=0.2, write_timeout=1.0, rtscts=False, dsrdtr=False, xonxoff=False, ) as ser: for index in range(args.repeat): ser.write(frame) ser.flush() print(f"Sent {index + 1}/{args.repeat}") if index + 1 < args.repeat: time.sleep(args.interval) return 0 if __name__ == "__main__": raise SystemExit(main())