import argparse import random import socket import time from dataclasses import dataclass from datetime import datetime from pathlib import Path from config.dependencies import build_wits_sender_dependencies BEGIN_MARK = "&&\r\n" END_MARK = "!!\r\n" @dataclass(frozen=True) class WitsData: ts: int wellid: str stknum: int recid: int seqid: int actual_date: float actual_time: float actual_ts: int actcod: int actod_label: str deptbitm: float deptbitv: float deptmeas: float deptvert: float blkpos: float ropa: float hkla: float hklx: float woba: float wobx: float torqa: float torqx: float rpma: int sppa: float chkp: float spm1: int spm2: int spm3: int tvolact: float tvolcact: float mfop: int mfoa: float mfia: float mdoa: float mdia: float mtoa: float mtia: float mcoa: float mcia: float stkc: int lagstks: int deptretm: float gasa: float space1: float space2: float space3: float space4: float space5: float WITS_FIELD_MAPPING = [ (1, "wellid", "string"), (2, "stknum", "int"), (3, "recid", "int"), (4, "seqid", "int"), (5, "actual_date", "float"), (6, "actual_time", "float"), (7, "actcod", "int"), (8, "deptbitm", "float"), (9, "deptbitv", "float"), (10, "deptmeas", "float"), (11, "deptvert", "float"), (12, "blkpos", "float"), (13, "ropa", "float"), (14, "hkla", "float"), (15, "hklx", "float"), (16, "woba", "float"), (17, "wobx", "float"), (18, "torqa", "float"), (19, "torqx", "float"), (20, "rpma", "int"), (21, "sppa", "float"), (22, "chkp", "float"), (23, "spm1", "int"), (24, "spm2", "int"), (25, "spm3", "int"), (26, "tvolact", "float"), (27, "tvolcact", "float"), (28, "mfop", "int"), (29, "mfoa", "float"), (30, "mfia", "float"), (31, "mdoa", "float"), (32, "mdia", "float"), (33, "mtoa", "float"), (34, "mtia", "float"), (35, "mcoa", "float"), (36, "mcia", "float"), (37, "stkc", "int"), (38, "lagstks", "int"), (39, "deptretm", "float"), (40, "gasa", "float"), (41, "space1", "float"), (42, "space2", "float"), (43, "space3", "float"), (44, "space4", "float"), (45, "space5", "float"), ] def rand_int(a, b): return random.randint(a, b) def rand_float(a, b, digits=2): return round(random.uniform(a, b), digits) def build_random_wits_data(device_code): now = datetime.now() ts_ms = int(time.time() * 1000) return WitsData( ts=ts_ms, wellid=device_code, stknum=rand_int(0, 500), recid=0, seqid=rand_int(1, 999999), actual_date=float(now.strftime("%Y%m%d")), actual_time=float(now.strftime("%H%M%S")), actual_ts=ts_ms, actcod=rand_int(0, 9), actod_label="AUTO", deptbitm=rand_float(0, 5000), deptbitv=rand_float(0, 5000), deptmeas=rand_float(0, 5000), deptvert=rand_float(0, 5000), blkpos=rand_float(0, 100), ropa=rand_float(0, 200), hkla=rand_float(0, 500), hklx=rand_float(0, 500), woba=rand_float(0, 200), wobx=rand_float(0, 200), torqa=rand_float(0, 200), torqx=rand_float(0, 200), rpma=rand_int(0, 300), sppa=rand_float(0, 5000), chkp=rand_float(0, 5000), spm1=rand_int(0, 200), spm2=rand_int(0, 200), spm3=rand_int(0, 200), tvolact=rand_float(0, 20000), tvolcact=rand_float(0, 20000), mfop=rand_int(0, 1000), mfoa=rand_float(0, 1000), mfia=rand_float(0, 1000), mdoa=rand_float(0, 1000), mdia=rand_float(0, 1000), mtoa=rand_float(0, 1000), mtia=rand_float(0, 1000), mcoa=rand_float(0, 1000), mcia=rand_float(0, 1000), stkc=rand_int(0, 200), lagstks=rand_int(0, 200), deptretm=rand_float(0, 5000), gasa=rand_float(0, 100), space1=rand_float(0, 10), space2=rand_float(0, 10), space3=rand_float(0, 10), space4=rand_float(0, 10), space5=rand_float(0, 10), ) def format_wits_value(value, kind): if kind == "string": return str(value) if kind == "int": return str(int(value)) return f"{float(value):.2f}" def build_wits_packet(data): lines = [] for index, field_name, kind in WITS_FIELD_MAPPING: value = getattr(data, field_name) lines.append(f"{index:02d}{format_wits_value(value, kind)}") return BEGIN_MARK + "\r\n".join(lines) + "\r\n" + END_MARK def normalize_packet(text): body = text.replace("\r\n", "\n").replace("\r", "\n") lines = [line.rstrip() for line in body.split("\n") if line.strip()] if lines and lines[0] == "&&": lines = lines[1:] if lines and lines[-1] == "!!": lines = lines[:-1] return BEGIN_MARK + "\r\n".join(lines) + "\r\n" + END_MARK def load_packet_from_file(path): return normalize_packet(Path(path).read_text(encoding="utf-8-sig")) def send_packet(host, port, timeout, packet): with socket.create_connection((host, port), timeout=timeout) as sock: sock.sendall(packet.encode("ascii", errors="strict")) def run_wits_sender(args, deps): wits_config = deps.config.wits device_code = deps.config.tms.device_code source_file = args.source_file or wits_config.source_file host = args.host or wits_config.host port = args.port or wits_config.port timeout = args.timeout or wits_config.timeout if not host or not port: raise ValueError("WITS target host/port is empty. Configure wits.host/wits.port or tms.server-ip/tms.server-port") print("WITS sender config:") print(f" host: {host}") print(f" port: {port}") print(f" timeout: {timeout}s") print(f" source-file: {source_file or '(generated)'}") print(f" interval: {args.interval}s") print(f" count: {args.count or 'forever'}") seq = 0 try: while True: seq += 1 if source_file: packet = load_packet_from_file(source_file) else: packet = build_wits_packet(build_random_wits_data(device_code)) send_packet(host, port, timeout, packet) print(f"TX WITS #{seq} -> {host}:{port}") print(packet) if args.count and seq >= args.count: break time.sleep(args.interval) except KeyboardInterrupt: pass def main(): ap = argparse.ArgumentParser(description="WITS TCP sender") ap.add_argument("--config", default="config.yaml", help="Path to config yaml") ap.add_argument("--host", default="", help="Override target host") ap.add_argument("--port", type=int, default=0, help="Override target port") ap.add_argument("--timeout", type=int, default=0, help="Override socket timeout") ap.add_argument("--source-file", default="", help="Send raw WITS packet from file") ap.add_argument("--interval", type=float, default=3.0, help="Send interval in seconds") ap.add_argument("--count", type=int, default=1, help="Send count (0 = forever)") args = ap.parse_args() deps = build_wits_sender_dependencies(args.config) run_wits_sender(args, deps) if __name__ == "__main__": main()