SXXXXXXX_PyMsc/tests/test_server_transmit_monitor.py
2025-12-10 11:47:46 +01:00

229 lines
8.5 KiB
Python

"""Test helper: listen on the server's configured send port and log outgoing frames.
This test is diagnostic-only: it listens for a short period and writes human
readable summaries to `logs/server_tx_monitor.log`. It does not fail the test
suite if nothing is received (so it's safe to run while the real server is down).
Run manually (PowerShell):
& .venv\Scripts\python.exe -m pytest -q tests/test_server_transmit_monitor.py
"""
import socket
import time
import os
import struct
import ctypes
from pymsc.core.app_controller import AppController
from pymsc.lib1553.structures import Udp1553Header, Udp1553Message
from pymsc.lib1553.constants import Marker
def _ensure_logs_dir(path):
try:
os.makedirs(path, exist_ok=True)
except Exception:
pass
def _decode_markers(data):
"""Return list of dicts for each CTRL_BEGIN marker found in data."""
res = []
ctrl_begin = struct.pack('<H', Marker.CTRL_BEGIN)
ctrl_end = struct.pack('<H', Marker.CTRL_END)
i = 0
while True:
pos = data.find(ctrl_begin, i)
if pos == -1:
break
try:
cw_raw = struct.unpack_from('<H', data, pos + 2)[0]
wc_field = cw_raw & 0x1F
sa = (cw_raw >> 5) & 0x1F
tr = (cw_raw >> 10) & 0x1
rt = (cw_raw >> 11) & 0x1F
payload_len = wc_field * 2
inv_pos = pos + 8 + payload_len
inverted = None
ctrl = None
if inv_pos + 2 <= len(data):
inverted = struct.unpack_from('<H', data, inv_pos)[0]
if inv_pos + 4 <= len(data):
ctrl = struct.unpack_from('<H', data, inv_pos + 2)[0]
res.append({
'pos': pos,
'cw_raw': cw_raw,
'sa': sa,
'tr': tr,
'rt': rt,
'wc': wc_field,
'payload_len': payload_len,
'inverted': inverted,
'ctrl_end': ctrl,
})
except Exception:
pass
i = pos + 1
return res
def test_server_transmit_monitor_short():
"""Listen briefly on the configured send port and log frames.
This test writes to `logs/server_tx_monitor.log`. It never fails the run
so it can be used interactively while the server is up.
"""
c = AppController()
# Listen on both configured send and receive ports to catch any transmissions
send_port = getattr(c, 'udp_send_port', 51553)
recv_port = getattr(c, 'udp_recv_port', 61553)
ports = [send_port, recv_port]
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
logs_dir = os.path.join(repo_root, 'logs')
_ensure_logs_dir(logs_dir)
out_path = os.path.join(logs_dir, 'server_tx_monitor.log')
# Try to bind UDP sockets for all ports we care about
socks = []
for port in ports:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# allow quick reuse where possible
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', port))
s.settimeout(0.5)
socks.append((port, s))
except OSError as e:
with open(out_path, 'a', encoding='utf-8') as f:
f.write(f"[{time.strftime('%H:%M:%S')}] Could not bind to port {port}: {e}\n")
try:
s.close()
except Exception:
pass
if not socks:
# nothing we can do; bail out
with open(out_path, 'a', encoding='utf-8') as f:
f.write(f"[{time.strftime('%H:%M:%S')}] No ports available for monitoring.\n")
return
stop_at = time.time() + 8.0 # listen for ~8 seconds
seen = []
with open(out_path, 'a', encoding='utf-8') as f:
f.write(f"[{time.strftime('%H:%M:%S')}] Starting transmit monitor on ports: {', '.join(str(p) for p,_ in socks)}\n")
while time.time() < stop_at:
for port, s in list(socks):
try:
data, addr = s.recvfrom(4096)
except socket.timeout:
continue
t = time.strftime('%H:%M:%S')
f.write(f"[{t}] RX from {addr[0]}:{addr[1]} len={len(data)} bytes (port={port})\n")
# attempt to decode Udp1553Header if present
try:
header_size = ctypes.sizeof(Udp1553Header)
if len(data) >= header_size:
hdr = Udp1553Header.from_buffer_copy(data[:header_size])
f.write(f" Header: msg_counter={hdr.msg_counter} o_type=0x{hdr.o_type:04X}\n")
except Exception as e:
f.write(f" Header parse error: {e}\n")
# scan for markers and CWs
markers = _decode_markers(data)
if not markers:
f.write(" No CTRL_BEGIN markers found\n")
else:
for m in markers:
expected_inv = (~m['cw_raw']) & 0xFFFF
f.write(
f" MARKER pos={m['pos']} SA={m['sa']} TR={m['tr']} RT={m['rt']} WC={m['wc']} "
f"payload_len={m['payload_len']} inverted=0x{(m['inverted'] or 0):04X} ctrl_end={m['ctrl_end']} expected_inv=0x{expected_inv:04X}\n"
)
f.flush()
seen.append((t, addr, len(data)))
# test must not fail; it's diagnostic
assert True
def test_server_transmit_active_controller():
"""Start an AppController locally, initialize radar and capture what it transmits.
This test creates an AppController instance, assigns ephemeral ports so it
doesn't conflict with any running server, starts it, calls
`initialize_radar()` and listens for outgoing frames on the controller's
configured send port. Captured data is written to
`logs/server_tx_monitor_active.log`.
"""
# pick ephemeral ports
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 0))
send_port = s.getsockname()[1]
s.close()
r = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
r.bind(('127.0.0.1', 0))
recv_port = r.getsockname()[1]
r.close()
c = AppController()
c.udp_send_port = send_port
c.udp_recv_port = recv_port
c.radar_dest_ip = '127.0.0.1'
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
logs_dir = os.path.join(repo_root, 'logs')
_ensure_logs_dir(logs_dir)
out_path = os.path.join(logs_dir, 'server_tx_monitor_active.log')
# start controller
c.start()
time.sleep(0.1)
# bind a socket to receive what the controller will send
monitor = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
monitor.bind(('127.0.0.1', send_port))
monitor.settimeout(0.5)
except OSError as e:
with open(out_path, 'a', encoding='utf-8') as f:
f.write(f"[{time.strftime('%H:%M:%S')}] Could not bind monitor to port {send_port}: {e}\n")
c.stop()
return
# ask controller to initialize radar (this will schedule periodic sends)
try:
c.initialize_radar()
except Exception as e:
with open(out_path, 'a', encoding='utf-8') as f:
f.write(f"[{time.strftime('%H:%M:%S')}] initialize_radar() raised: {e}\n")
stop_at = time.time() + 4.0
received = 0
with open(out_path, 'a', encoding='utf-8') as f:
f.write(f"[{time.strftime('%H:%M:%S')}] Active monitor listening on port {send_port}\n")
while time.time() < stop_at:
try:
data, addr = monitor.recvfrom(4096)
except socket.timeout:
continue
t = time.strftime('%H:%M:%S')
f.write(f"[{t}] RX len={len(data)} from {addr[0]}:{addr[1]}\n")
markers = _decode_markers(data)
if markers:
for m in markers:
expected_inv = (~m['cw_raw']) & 0xFFFF
f.write(f" MARKER SA={m['sa']} TR={m['tr']} RT={m['rt']} WC={m['wc']} payload_len={m['payload_len']} expected_inv=0x{expected_inv:04X}\n")
else:
f.write(" No markers found\n")
f.flush()
received += 1
monitor.close()
c.stop()
# record whether we saw any frames
with open(out_path, 'a', encoding='utf-8') as f:
f.write(f"[{time.strftime('%H:%M:%S')}] Active monitor finished, packets_received={received}\n")
# don't fail if none received; this is diagnostic
assert True