summaryrefslogtreecommitdiff
path: root/stacks/tncs/tnc2c-tx-rx-test.py
diff options
context:
space:
mode:
Diffstat (limited to 'stacks/tncs/tnc2c-tx-rx-test.py')
-rw-r--r--stacks/tncs/tnc2c-tx-rx-test.py184
1 files changed, 184 insertions, 0 deletions
diff --git a/stacks/tncs/tnc2c-tx-rx-test.py b/stacks/tncs/tnc2c-tx-rx-test.py
new file mode 100644
index 0000000..76e639d
--- /dev/null
+++ b/stacks/tncs/tnc2c-tx-rx-test.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python3
+"""TX then RX diagnostic for TNC2C + CB - no HyBBX."""
+
+from __future__ import annotations
+
+import fcntl
+import os
+import struct
+import sys
+import time
+import termios
+
+DEV = "/dev/ttyS4"
+BAUD = termios.B19200
+
+FIRMWARE = (b"TheFirmware", b"NORD", b"Version 2.7", b"Checksum")
+
+
+def open_port() -> int:
+ fd = os.open(DEV, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
+ t = termios.tcgetattr(fd)
+ t[0] = t[1] = 0
+ t[2] = termios.CLOCAL | termios.CREAD | termios.CS8
+ t[3] = t[4] = t[5] = BAUD
+ t[6][termios.VMIN] = 0
+ t[6][termios.VTIME] = 5
+ termios.tcsetattr(fd, termios.TCSANOW, t)
+ termios.tcflush(fd, termios.TCIOFLUSH)
+ flags = struct.unpack("I", fcntl.ioctl(fd, 0x5415, struct.pack("I", 0)))[0]
+ flags |= 0x006
+ fcntl.ioctl(fd, 0x5416, struct.pack("I", flags))
+ return fd
+
+
+def modem(fd: int) -> str:
+ f = struct.unpack("I", fcntl.ioctl(fd, 0x5415, struct.pack("I", 0)))[0]
+ return f"RTS={int(bool(f&4))} CTS={int(bool(f&8))} DSR={int(bool(f&0x100))} DCD={int(bool(f&0x80))}"
+
+
+def read_sec(fd: int, sec: float) -> bytes:
+ end = time.time() + sec
+ chunks: list[bytes] = []
+ while time.time() < end:
+ try:
+ b = os.read(fd, 4096)
+ if b:
+ chunks.append(b)
+ except BlockingIOError:
+ time.sleep(0.02)
+ return b"".join(chunks)
+
+
+def write(fd: int, data: bytes) -> None:
+ os.write(fd, data)
+ termios.tcdrain(fd)
+
+
+def has_fw(data: bytes) -> bool:
+ lo = data.lower()
+ return any(m.lower() in lo for m in FIRMWARE)
+
+
+def kiss_escape(data: bytes) -> bytes:
+ out = bytearray()
+ for b in data:
+ if b == 0xC0:
+ out.extend((0xDB, 0xDC))
+ elif b == 0xDB:
+ out.extend((0xDB, 0xDD))
+ else:
+ out.append(b)
+ return bytes(out)
+
+
+def ax25_addr(call: str, last: bool) -> bytes:
+ call = call.upper().ljust(6)[:6]
+ raw = bytes((ord(c) << 1) for c in call)
+ return raw + bytes([((0 & 0x0F) << 1) | (0x01 if last else 0x00)])
+
+
+def ax25_crc(data: bytes) -> int:
+ crc = 0xFFFF
+ for b in data:
+ crc ^= b
+ for _ in range(8):
+ crc = (crc >> 1) ^ 0x8408 if crc & 1 else crc >> 1
+ return crc ^ 0xFFFF
+
+
+def ax25_ui(src: str, dst: str, info: bytes) -> bytes:
+ body = ax25_addr(dst, False) + ax25_addr(src, True) + b"\x03\xF0" + info
+ crc = ax25_crc(body)
+ return body + bytes((crc & 0xFF, crc >> 8))
+
+
+def kiss_frame(payload: bytes) -> bytes:
+ return b"\xC0\x00" + kiss_escape(payload) + b"\xC0"
+
+
+def main() -> int:
+ print("=" * 60)
+ print("TNC2C TX/RX Test")
+ print("=" * 60)
+
+ if os.system("pgrep -x hybbx >/dev/null 2>&1") == 0:
+ print("FAIL: hybbx is running - stop it first")
+ return 2
+
+ fd = open_port()
+ print(f"Device: {DEV} 19200 8N1 {modem(fd)}")
+
+ # --- Host check ---
+ print("\n[1] Host check")
+ passive = read_sec(fd, 2.0)
+ write(fd, b"kiss off\r")
+ time.sleep(0.5)
+ pre = read_sec(fd, 1.0)
+ write(fd, b"INFO\r")
+ time.sleep(0.3)
+ info = read_sec(fd, 4.0)
+ all_host = passive + pre + info
+ if has_fw(all_host):
+ print(" OK: firmware/host detected")
+ elif info.strip() in (b"INFO", b"INFO\r", b"INFO\r\n"):
+ print(" WARN: echo only - try ./tnc2c-host-reset.sh --kiss or ./tnc2c-boot-wait.sh --recover-only")
+ else:
+ print(f" INFO reply: {info[:120]!r}")
+
+ # --- TX ---
+ print("\n[2] TX (KISS UI TEST-0 -> CQ, Msg=TXRX)")
+ write(fd, b"kiss on\r")
+ time.sleep(0.5)
+ read_sec(fd, 0.3)
+ pkt = kiss_frame(ax25_ui("TEST-0", "CQ", b"TXRX"))
+ t0 = time.time()
+ write(fd, pkt)
+ time.sleep(3.0)
+ tx_rx = read_sec(fd, 1.0)
+ write(fd, b"kiss off\r")
+ time.sleep(0.3)
+
+ kiss_back = b"\xc0" in tx_rx or b"TXRX" in tx_rx
+ print(f" Sent: {len(pkt)} bytes KISS")
+ print(f" Serial RX: {len(tx_rx)} bytes")
+ if kiss_back:
+ print(" OK: KISS frame seen on serial")
+ else:
+ print(" WARN: no KISS echo on serial")
+ print(" -> CHECK NOW: LED2 PTT + modem tone on 2m CB radio")
+
+ # --- RX (passive, no TX) ---
+ print("\n[3] RX (passive 12s, kiss off - listening on radio/monitor)")
+ termios.tcflush(fd, termios.TCIOFLUSH)
+ write(fd, b"kiss off\r")
+ time.sleep(0.5)
+ read_sec(fd, 0.5)
+ print(" Listening 12s ... (RF noise/CD should keep LED3 on)")
+ rx_data = read_sec(fd, 12.0)
+ os.close(fd)
+
+ print(f" Passive RX: {len(rx_data)} bytes")
+ if rx_data:
+ printable = sum(1 for b in rx_data if 32 <= b < 127 or b in (9, 10, 13))
+ ratio = printable / len(rx_data)
+ print(f" Printable ratio: {ratio:.0%}")
+ if b"\xc0" in rx_data:
+ print(" OK: KISS frame(s) received (RF traffic or monitor)")
+ else:
+ print(f" Sample: {rx_data[:80]!r}")
+ else:
+ print(" (no serial traffic - normal on empty band)")
+ print(" -> Hardware RX: LED3 CD with noise/SQ open?")
+
+ print("\n" + "=" * 60)
+ print("Hardware checklist:")
+ print(" TX: LED2 (PTT) blinked? 2m CB heard modem tone?")
+ print(" RX: LED3 (CD) on with noise? SQ closed -> CD off?")
+ print(" DIN: Pin1=TX(white) Pin2=GND Pin3=PTT(red) Pin4=RX(tip)")
+ print("=" * 60)
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com