summaryrefslogtreecommitdiff
path: root/scripts/mock-ardopc.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/mock-ardopc.py')
-rwxr-xr-xscripts/mock-ardopc.py137
1 files changed, 137 insertions, 0 deletions
diff --git a/scripts/mock-ardopc.py b/scripts/mock-ardopc.py
new file mode 100755
index 0000000..0a2e110
--- /dev/null
+++ b/scripts/mock-ardopc.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+"""
+Minimal ARDOPC TCP mock for HyBBX plugin host-TCP local tests.
+Control port N, data port N+1. CRC-16 poly 0x8810 (ARDOP host family).
+"""
+from __future__ import annotations
+
+import socket
+import struct
+import sys
+import threading
+import time
+
+INT_POLY = 0x8810
+
+
+def crc16(data: bytes) -> int:
+ reg = 0xFFFF
+ for byte in data:
+ mask = 0x80
+ for _ in range(8):
+ bit = byte & mask
+ mask >>= 1
+ if reg & 0x8000:
+ if bit:
+ reg = (1 + (reg << 1)) & 0xFFFF
+ else:
+ reg = (reg << 1) & 0xFFFF
+ reg ^= INT_POLY
+ else:
+ if bit:
+ reg = (1 + (reg << 1)) & 0xFFFF
+ else:
+ reg = (reg << 1) & 0xFFFF
+ return reg
+
+
+def frame_cmd(text: str) -> bytes:
+ body = (text + "\r").encode("ascii")
+ c = crc16(body)
+ return body + struct.pack(">H", c)
+
+
+def parse_frames(buf: bytearray) -> list[bytes]:
+ out: list[bytes] = []
+ while True:
+ if b"\r" not in buf:
+ break
+ cr = buf.index(0x0D)
+ need = cr + 1 + 2
+ if len(buf) < need:
+ break
+ frame = bytes(buf[:need])
+ del buf[:need]
+ if crc16(frame[:-2]) == struct.unpack(">H", frame[-2:])[0]:
+ out.append(frame[:-2])
+ return out
+
+
+def serve_ctrl(port: int, log: list[str]) -> None:
+ 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(1)
+ srv.settimeout(8.0)
+ try:
+ conn, _ = srv.accept()
+ except socket.timeout:
+ srv.close()
+ return
+ conn.settimeout(2.0)
+ conn.sendall(frame_cmd("VERSION crdopc_1.0.0"))
+ buf = bytearray()
+ try:
+ while True:
+ try:
+ chunk = conn.recv(4096)
+ except TimeoutError:
+ break
+ if not chunk:
+ break
+ buf.extend(chunk)
+ for frame in parse_frames(buf):
+ cmd = frame.decode("ascii", errors="replace").strip()
+ log.append(f"ctrl<= {cmd}")
+ if cmd.startswith("INITIALIZE"):
+ conn.sendall(frame_cmd("RDY"))
+ elif cmd == "RDY":
+ pass
+ else:
+ conn.sendall(frame_cmd("RDY"))
+ finally:
+ conn.close()
+ srv.close()
+
+
+def serve_data(port: int, log: list[str]) -> None:
+ 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(1)
+ srv.settimeout(8.0)
+ try:
+ conn, _ = srv.accept()
+ except socket.timeout:
+ srv.close()
+ return
+ conn.settimeout(1.0)
+ try:
+ while True:
+ try:
+ chunk = conn.recv(4096)
+ except socket.timeout:
+ continue
+ if not chunk:
+ break
+ log.append(f"data<= {len(chunk)} bytes")
+ finally:
+ conn.close()
+ srv.close()
+
+
+def main() -> int:
+ port = int(sys.argv[1]) if len(sys.argv) > 1 else 18515
+ log: list[str] = []
+ t1 = threading.Thread(target=serve_ctrl, args=(port, log), daemon=True)
+ t2 = threading.Thread(target=serve_data, args=(port + 1, log), daemon=True)
+ t1.start()
+ t2.start()
+ time.sleep(6)
+ for line in log:
+ print(line)
+ return 0 if any("INITIALIZE" in x for x in log) else 1
+
+
+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