#!/usr/bin/env python3 """Tiny TCP<->serial bridge for MUST PV1800 monitoring. Listens on 127.0.0.1:$PROXY_PORT, forwards every byte to/from $PROXY_TTY using pyserial. Multiple sequential TCP clients are served one at a time. """ import os, sys, threading, time import serial, socket PORT = int(os.environ.get("PROXY_PORT", "9700")) TTY = os.environ.get("PROXY_TTY", "/dev/ttyUSB0") BAUD = int(os.environ.get("PROXY_BAUD", "19200")) def log(msg): print(f"[must-proxy] {msg}", file=sys.stderr, flush=True) try: # Pre-flush: read-and-discard whatever stale bytes are sitting in the # kernel TTY buffer from previous runs. Without this, those bytes get # delivered to the first client and corrupt its first read. pre = serial.Serial(TTY, BAUD, timeout=0.2) pre.reset_input_buffer() pre.reset_output_buffer() flushed = 0 quiet_for = 0.0 pre_start = time.monotonic() while time.monotonic() - pre_start < 3.0 and quiet_for < 0.3: chunk = pre.read(512) if chunk: flushed += len(chunk) quiet_for = 0.0 else: quiet_for += 0.05 time.sleep(0.05) pre.close() if flushed: log(f"flushed {flushed} stale bytes from kernel TTY buffer") ser = serial.Serial(TTY, BAUD, timeout=0.05) ser.reset_input_buffer() ser.reset_output_buffer() log(f"opened {TTY} @ {BAUD} fd={ser.fd}") except Exception as e: log(f"could not open {TTY}: {e}") sys.exit(1) def tty_to_tcp(client): while True: try: data = ser.read(256) if data: client.sendall(data) except Exception: return srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind(("127.0.0.1", PORT)) srv.listen(5) log(f"listening on 127.0.0.1:{PORT}") while True: client, addr = srv.accept() log(f"client {addr}") threading.Thread(target=tty_to_tcp, args=(client,), daemon=True).start() try: while True: data = client.recv(4096) if not data: break ser.write(data) ser.flush() except Exception as e: log(f"tcp err: {e}") try: client.close() except Exception: pass log(f"client closed")