229 lines
8.5 KiB
Python
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
|