tolto visualizzazione ppi da debug e rivista la visualizzazione dei dati in debug

This commit is contained in:
VALLONGOL 2025-10-24 15:37:18 +02:00
parent 0bc190a257
commit 243dc5732b
4 changed files with 241 additions and 132 deletions

View File

@ -169,6 +169,9 @@ class SimulationStateHub:
"""Clears all stored data for all targets.""" """Clears all stored data for all targets."""
with self._lock: with self._lock:
self._target_data.clear() self._target_data.clear()
# also clear heading caches
self._latest_real_heading.clear()
self._latest_raw_heading.clear()
def _initialize_target(self, target_id: int): def _initialize_target(self, target_id: int):
"""Internal helper to create the data structure for a new target.""" """Internal helper to create the data structure for a new target."""
@ -177,3 +180,17 @@ class SimulationStateHub:
"simulated": collections.deque(maxlen=self._history_size), "simulated": collections.deque(maxlen=self._history_size),
"real": collections.deque(maxlen=self._history_size), "real": collections.deque(maxlen=self._history_size),
} }
def remove_target(self, target_id: int):
"""Remove all stored data for a specific target id."""
with self._lock:
try:
tid = int(target_id)
if tid in self._target_data:
del self._target_data[tid]
if tid in self._latest_real_heading:
del self._latest_real_heading[tid]
if tid in self._latest_raw_heading:
del self._latest_raw_heading[tid]
except Exception:
pass

View File

@ -137,6 +137,19 @@ class DebugPayloadRouter:
try: try:
parsed_for_hub = SfpRisStatusPayload.from_buffer_copy(payload) parsed_for_hub = SfpRisStatusPayload.from_buffer_copy(payload)
ts_s = parsed_for_hub.scenario.timetag / 1000.0 ts_s = parsed_for_hub.scenario.timetag / 1000.0
# First: remove any targets that the server marked as inactive (flags == 0)
try:
for i, ris_t in enumerate(parsed_for_hub.tgt.tgt):
try:
if ris_t.flags == 0 and self._hub and hasattr(self._hub, 'remove_target'):
self._hub.remove_target(i)
except Exception:
pass
except Exception:
pass
# Add real states for active targets
for target in real_targets: for target in real_targets:
state_tuple = ( state_tuple = (
getattr(target, '_pos_x_ft', 0.0), getattr(target, '_pos_x_ft', 0.0),
@ -146,17 +159,19 @@ class DebugPayloadRouter:
self._hub.add_real_state( self._hub.add_real_state(
target_id=target.target_id, timestamp=ts_s, state=state_tuple target_id=target.target_id, timestamp=ts_s, state=state_tuple
) )
# Propagate heading information (if available) into the hub so # Propagate heading information (if available) into the hub so
# GUI builders that reconstruct lightweight Target objects # GUI builders that reconstruct lightweight Target objects
# from the hub can also pick up the last known heading. # from the hub can also pick up the last known heading.
try: try:
for target in real_targets: for target in real_targets:
if hasattr(self._hub, 'set_real_heading'): if hasattr(self._hub, 'set_real_heading'):
raw_val = getattr(target, '_raw_heading', None) raw_val = getattr(target, '_raw_heading', None)
self._hub.set_real_heading(target.target_id, getattr(target, 'current_heading_deg', 0.0), raw_value=raw_val) self._hub.set_real_heading(target.target_id, getattr(target, 'current_heading_deg', 0.0), raw_value=raw_val)
except Exception: except Exception:
# Never allow heading propagation to break payload handling # Never allow heading propagation to break payload handling
self._logger.debug("Failed to propagate heading to hub", exc_info=True) self._logger.debug("Failed to propagate heading to hub", exc_info=True)
if self._update_queue: if self._update_queue:
try: try:
self._update_queue.put_nowait([]) self._update_queue.put_nowait([])

View File

@ -11,6 +11,7 @@ import datetime
import os import os
import ctypes import ctypes
import math import math
import socket
from queue import Queue, Empty from queue import Queue, Empty
from typing import Dict, Optional, Any, List from typing import Dict, Optional, Any, List
@ -27,7 +28,7 @@ from target_simulator.core.sfp_structures import ImageLeaderData, SFPHeader
from target_simulator.gui.payload_router import DebugPayloadRouter from target_simulator.gui.payload_router import DebugPayloadRouter
from target_simulator.core.models import Target, Waypoint, ManeuverType, KNOTS_TO_FPS from target_simulator.core.models import Target, Waypoint, ManeuverType, KNOTS_TO_FPS
from target_simulator.core import command_builder from target_simulator.core import command_builder
from target_simulator.gui.ppi_display import PPIDisplay
DEF_TEST_ID = 1 DEF_TEST_ID = 1
DEF_TEST_RANGE = 30.0 DEF_TEST_RANGE = 30.0
@ -67,7 +68,6 @@ class SfpDebugWindow(tk.Toplevel):
self.payload_router.add_ris_target_listener(self._queue_ris_target_update) self.payload_router.add_ris_target_listener(self._queue_ris_target_update)
self.image_area_size = 150 self.image_area_size = 150
self._ppi_visible = False
self.ip_var = tk.StringVar(value="127.0.0.1") self.ip_var = tk.StringVar(value="127.0.0.1")
self.local_port_var = tk.StringVar(value="60002") self.local_port_var = tk.StringVar(value="60002")
self.server_port_var = tk.StringVar(value="60001") self.server_port_var = tk.StringVar(value="60001")
@ -81,7 +81,18 @@ class SfpDebugWindow(tk.Toplevel):
self.tgt_active_var = tk.BooleanVar(value=True) self.tgt_active_var = tk.BooleanVar(value=True)
self.tgt_traceable_var = tk.BooleanVar(value=True) self.tgt_traceable_var = tk.BooleanVar(value=True)
self.tgt_restart_var = tk.BooleanVar(value=False) self.tgt_restart_var = tk.BooleanVar(value=False)
self._master_mode_names = [ "idle_master_mode", "int_bit_master_mode", "gm_master_mode", "dbs_master_mode", "rws_master_mode", "vs_master_mode", "acm_master_mode", "tws_master_mode", "sea_low_master_mode", "sea_high_master_mode", "gmti_master_mode", "bcn_master_mode", "sam_master_mode", "ta_master_mode", "wa_master_mode", "stt_master_mode", "dtt_master_mode", "sstt_master_mode", "acq_master_mode", "ftt_master_mode", "agr_master_mode", "sar_master_mode", "invalid_master_mode_", "xtst_dummy_mode", "xtst_hw_validation_mode", "boot_master_mode", "master_mode_id_cardinality_", ] # View settings for RIS/status presentation
self.scenario_view_mode = tk.StringVar(value='simplified')
self.simplified_decimals = tk.IntVar(value=2)
self._master_mode_names = [
"idle_master_mode", "int_bit_master_mode", "gm_master_mode", "dbs_master_mode",
"rws_master_mode", "vs_master_mode", "acm_master_mode", "tws_master_mode",
"sea_low_master_mode", "sea_high_master_mode", "gmti_master_mode", "bcn_master_mode",
"sam_master_mode", "ta_master_mode", "wa_master_mode", "stt_master_mode",
"dtt_master_mode", "sstt_master_mode", "acq_master_mode", "ftt_master_mode",
"agr_master_mode", "sar_master_mode", "invalid_master_mode_", "xtst_dummy_mode",
"xtst_hw_validation_mode", "boot_master_mode", "master_mode_id_cardinality_",
]
self._create_widgets() self._create_widgets()
@ -109,10 +120,11 @@ class SfpDebugWindow(tk.Toplevel):
""" """
This method runs on the GUI thread and processes all queued updates. This method runs on the GUI thread and processes all queued updates.
""" """
# 1. Process target updates for the PPI # 1. Process target updates (no PPI here; still support queued updates)
try: try:
while not self.debug_update_queue.empty(): while not self.debug_update_queue.empty():
real_targets = self.debug_update_queue.get_nowait() real_targets = self.debug_update_queue.get_nowait()
# kept for compatibility; this window no longer shows a PPI
self.update_ppi_targets(real_targets) self.update_ppi_targets(real_targets)
except Empty: except Empty:
pass pass
@ -136,15 +148,18 @@ class SfpDebugWindow(tk.Toplevel):
import json import json
struct = json.loads(payload.decode("utf-8")) if isinstance(payload, (bytes, bytearray)) else payload struct = json.loads(payload.decode("utf-8")) if isinstance(payload, (bytes, bytearray)) else payload
# --- START OF NEW LOGIC ---
view_mode = self.scenario_view_mode.get() view_mode = self.scenario_view_mode.get()
decimals = self.simplified_decimals.get() decimals = self.simplified_decimals.get()
# Helper functions for conversion def to_deg(rad):
def to_deg(rad): return rad * 180.0 / math.pi return rad * 180.0 / math.pi
def m_s_to_ft_s(ms): return ms * 3.28084
def m_to_ft(m): return m * 3.28084 def m_s_to_ft_s(ms):
return ms * 3.28084
def m_to_ft(m):
return m * 3.28084
def decimal_deg_to_dms(deg, is_lat): def decimal_deg_to_dms(deg, is_lat):
d = abs(deg) d = abs(deg)
degrees = int(d) degrees = int(d)
@ -176,7 +191,7 @@ class SfpDebugWindow(tk.Toplevel):
elif field == 'mode' and value < len(self._master_mode_names): elif field == 'mode' and value < len(self._master_mode_names):
display_value = f"{value} ({self._master_mode_names[value].replace('_master_mode', '')})" display_value = f"{value} ({self._master_mode_names[value].replace('_master_mode', '')})"
elif isinstance(value, list): elif isinstance(value, list):
display_value = str(value) # Keep arrays as string display_value = str(value)
self.scenario_tree.insert("", tk.END, values=(field, display_value)) self.scenario_tree.insert("", tk.END, values=(field, display_value))
@ -192,7 +207,7 @@ class SfpDebugWindow(tk.Toplevel):
x_pos = f"{m_to_ft(t.get('x', 0.0)):.{decimals}f} ft" x_pos = f"{m_to_ft(t.get('x', 0.0)):.{decimals}f} ft"
y_pos = f"{m_to_ft(t.get('y', 0.0)):.{decimals}f} ft" y_pos = f"{m_to_ft(t.get('y', 0.0)):.{decimals}f} ft"
z_pos = f"{m_to_ft(t.get('z', 0.0)):.{decimals}f} ft" z_pos = f"{m_to_ft(t.get('z', 0.0)):.{decimals}f} ft"
else: # Raw mode else:
heading = f"{t.get('heading', 0.0):.6f}" heading = f"{t.get('heading', 0.0):.6f}"
x_pos = f"{t.get('x', 0.0):.3f}" x_pos = f"{t.get('x', 0.0):.3f}"
y_pos = f"{t.get('y', 0.0):.3f}" y_pos = f"{t.get('y', 0.0):.3f}"
@ -201,8 +216,6 @@ class SfpDebugWindow(tk.Toplevel):
vals = (i, flags_display, heading, x_pos, y_pos, z_pos) vals = (i, flags_display, heading, x_pos, y_pos, z_pos)
self.ris_tree.insert("", tk.END, values=vals) self.ris_tree.insert("", tk.END, values=vals)
# --- END OF NEW LOGIC ---
except Exception: except Exception:
self.logger.exception("Failed to update RIS tables from JSON payload.") self.logger.exception("Failed to update RIS tables from JSON payload.")
@ -238,27 +251,9 @@ class SfpDebugWindow(tk.Toplevel):
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self._create_notebook_tabs() self._create_notebook_tabs()
def _toggle_ppi(self):
try:
if self._ppi_visible:
self.ris_ppi_container.pack_forget()
self.ris_table_container.pack(fill=tk.BOTH, expand=True)
self._ppi_visible = False
self.ppi_toggle_btn.config(text="Show PPI Map")
else:
self.ris_table_container.pack_forget()
self.ris_ppi_container.pack(fill=tk.BOTH, expand=True)
self._ppi_visible = True
self.ppi_toggle_btn.config(text="Hide PPI Map")
except Exception:
self.logger.exception("Toggle PPI failed")
def update_ppi_targets(self, targets: List[Target]): def update_ppi_targets(self, targets: List[Target]):
try: # PPI removed from SFP Debug Window; keep for compatibility but no-op
if self.ris_ppi_widget: return
self.ris_ppi_widget.update_targets({"real": targets})
except Exception:
self.logger.exception("Failed to update RIS PPI targets")
def _create_connection_widgets(self, parent): def _create_connection_widgets(self, parent):
ttk.Label(parent, text="IP:").pack(side=tk.LEFT, padx=(4, 2)) ttk.Label(parent, text="IP:").pack(side=tk.LEFT, padx=(4, 2))
@ -304,99 +299,125 @@ class SfpDebugWindow(tk.Toplevel):
ttk.Button(quick_cmd_frame, text="pause", command=lambda: self._on_send_simple_command(command_builder.build_pause())).pack(side=tk.LEFT, padx=4) ttk.Button(quick_cmd_frame, text="pause", command=lambda: self._on_send_simple_command(command_builder.build_pause())).pack(side=tk.LEFT, padx=4)
ttk.Button(quick_cmd_frame, text="continue", command=lambda: self._on_send_simple_command(command_builder.build_continue())).pack(side=tk.LEFT, padx=4) ttk.Button(quick_cmd_frame, text="continue", command=lambda: self._on_send_simple_command(command_builder.build_continue())).pack(side=tk.LEFT, padx=4)
ttk.Button(quick_cmd_frame, text="tgtset (cur)", command=lambda: self._on_send_tgtset()).pack(side=tk.LEFT, padx=8) ttk.Button(quick_cmd_frame, text="tgtset (cur)", command=lambda: self._on_send_tgtset()).pack(side=tk.LEFT, padx=8)
self.ppi_toggle_btn = ttk.Button(quick_cmd_frame, text="Show PPI Map", command=self._toggle_ppi) # PPI widget and toggle removed from this debug window
self.ppi_toggle_btn.pack(side=tk.RIGHT, padx=4)
def _create_script_sender_widgets(self, parent):
ttk.Label(parent, text="Script to send:").pack(side=tk.LEFT, padx=(5, 2))
ttk.Entry(parent, textvariable=self.script_var, width=60).pack(side=tk.LEFT, expand=True, fill=tk.X, padx=(0, 5))
self.send_script_btn = ttk.Button(parent, text="Send script", command=self._on_send_script)
self.send_script_btn.pack(side=tk.LEFT, padx=5)
def _create_notebook_tabs(self): def _create_notebook_tabs(self):
self.log_tab = scrolledtext.ScrolledText(self.notebook, state=tk.DISABLED, wrap=tk.WORD, font=("Consolas", 9)) """Create the notebook tabs used by the SFP Debug Window.
self.notebook.add(self.log_tab, text="Raw Log")
if _IMAGE_LIBS_AVAILABLE: This method ensures the widgets referenced elsewhere (log_tab, raw_tab_text,
self.mfd_tab = self._create_image_tab("MFD Image") ris_tree, scenario_tree, history_tree, image tabs, bin/json views) exist.
self.notebook.add(self.mfd_tab["frame"], text="MFD Image") """
self.sar_tab = self._create_image_tab("SAR Image") # Raw packet tab
self.notebook.add(self.sar_tab["frame"], text="SAR Image") raw_frame = ttk.Frame(self.notebook)
self.raw_tab_text = scrolledtext.ScrolledText(raw_frame, height=12, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 9))
self.raw_tab_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.notebook.add(raw_frame, text="Raw")
# Log tab
log_frame = ttk.Frame(self.notebook)
self.log_tab = scrolledtext.ScrolledText(log_frame, height=12, state=tk.DISABLED, wrap=tk.WORD, font=("Consolas", 9))
self.log_tab.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.notebook.add(log_frame, text="Log")
# RIS tab - scenario and targets (side-by-side)
ris_frame = ttk.Frame(self.notebook) ris_frame = ttk.Frame(self.notebook)
# Controls above the paned area: view mode (raw/simplified) and decimals
controls = ttk.Frame(ris_frame)
controls.pack(side=tk.TOP, fill=tk.X, padx=5, pady=(4, 2))
ttk.Label(controls, text="View:").pack(side=tk.LEFT, padx=(2, 4))
self.scenario_view_mode_opt = ttk.OptionMenu(controls, self.scenario_view_mode, self.scenario_view_mode.get(), 'simplified', 'raw')
self.scenario_view_mode_opt.pack(side=tk.LEFT)
ttk.Label(controls, text="Decimals:").pack(side=tk.LEFT, padx=(12, 4))
self.dec_spin = ttk.Spinbox(controls, from_=0, to=6, textvariable=self.simplified_decimals, width=4)
self.dec_spin.pack(side=tk.LEFT)
# Use a paned window to place scenario (left) and targets (right) side-by-side
paned = ttk.Panedwindow(ris_frame, orient=tk.HORIZONTAL) paned = ttk.Panedwindow(ris_frame, orient=tk.HORIZONTAL)
paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
left = ttk.Frame(paned) left = ttk.Frame(paned)
right = ttk.Frame(paned)
paned.add(left, weight=1)
paned.add(right, weight=3)
# Scenario tree on the left with scrollbar
self.scenario_tree = ttk.Treeview(left, columns=("field", "value"), show="headings", height=12) self.scenario_tree = ttk.Treeview(left, columns=("field", "value"), show="headings", height=12)
self.scenario_tree.heading("field", text="Field") self.scenario_tree.heading("field", text="Field")
self.scenario_tree.heading("value", text="Value") self.scenario_tree.heading("value", text="Value")
self.scenario_tree.column("field", width=140, anchor="w") # Set sensible column widths and stretching
self.scenario_tree.column("value", width=160, anchor="w") self.scenario_tree.column("field", width=160, anchor=tk.W, stretch=False)
self.scenario_tree.pack(fill=tk.BOTH, expand=True) self.scenario_tree.column("value", width=220, anchor=tk.W, stretch=True)
paned.add(left, weight=1) scen_scroll = ttk.Scrollbar(left, orient=tk.VERTICAL, command=self.scenario_tree.yview)
right = ttk.Frame(paned) self.scenario_tree.configure(yscrollcommand=scen_scroll.set)
self.ris_table_container = ttk.Frame(right) scen_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.ris_ppi_container = ttk.Frame(right) self.scenario_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
cols = ("idx", "flags", "heading", "x", "y", "z")
self.ris_tree = ttk.Treeview(self.ris_table_container, columns=cols, show="headings", height=12) # RIS targets table on the right with vertical and horizontal scrollbars
for c, txt in zip(cols, ("#", "flags", "heading", "x", "y", "z")): cols = ("index", "flags", "heading", "x", "y", "z")
self.ris_tree.heading(c, text=txt) self.ris_tree = ttk.Treeview(right, columns=cols, show="headings", height=16)
self.ris_tree.column(c, width=70, anchor="center") self.ris_tree.heading("index", text="#")
self.ris_tree.pack(fill=tk.BOTH, expand=True) self.ris_tree.heading("flags", text="flags")
self.ris_table_container.pack(fill=tk.BOTH, expand=True) self.ris_tree.heading("heading", text="heading")
paned.add(right, weight=2) self.ris_tree.heading("x", text="x")
gm = getattr(self.master, "config_manager", None) self.ris_tree.heading("y", text="y")
trail_len = None self.ris_tree.heading("z", text="z")
if gm: # Column sizing
general = gm.get_general_settings() or {} self.ris_tree.column("index", width=40, anchor=tk.CENTER, stretch=False)
trail_len = general.get("ppi_trail_length") self.ris_tree.column("flags", width=60, anchor=tk.CENTER, stretch=False)
self.ris_ppi_widget = PPIDisplay(self.ris_ppi_container, max_range_nm=100, trail_length=trail_len) self.ris_tree.column("heading", width=80, anchor=tk.CENTER, stretch=False)
self.ris_ppi_widget.pack(fill=tk.BOTH, expand=True) self.ris_tree.column("x", width=120, anchor=tk.E, stretch=True)
btn_frame = ttk.Frame(ris_frame) self.ris_tree.column("y", width=120, anchor=tk.E, stretch=True)
btn_frame.pack(fill=tk.X, padx=5, pady=(0, 5)) self.ris_tree.column("z", width=100, anchor=tk.E, stretch=False)
self.scenario_view_mode = tk.StringVar(value="simplified") ris_v = ttk.Scrollbar(right, orient=tk.VERTICAL, command=self.ris_tree.yview)
mode_frame = ttk.Frame(btn_frame) ris_h = ttk.Scrollbar(right, orient=tk.HORIZONTAL, command=self.ris_tree.xview)
mode_frame.pack(side=tk.LEFT, padx=(4, 0)) self.ris_tree.configure(yscrollcommand=ris_v.set, xscrollcommand=ris_h.set)
ttk.Label(mode_frame, text="View:").pack(side=tk.LEFT, padx=(0, 6)) ris_v.pack(side=tk.RIGHT, fill=tk.Y)
ttk.Radiobutton(mode_frame, text="Raw", value="raw", variable=self.scenario_view_mode).pack(side=tk.LEFT) ris_h.pack(side=tk.BOTTOM, fill=tk.X)
ttk.Radiobutton(mode_frame, text="Simplified", value="simplified", variable=self.scenario_view_mode).pack(side=tk.LEFT) self.ris_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.simplified_decimals = tk.IntVar(value=4)
ttk.Label(mode_frame, text=" Decimals:").pack(side=tk.LEFT, padx=(8, 2)) self.notebook.add(ris_frame, text="RIS")
ttk.Spinbox(mode_frame, from_=0, to=8, width=3, textvariable=self.simplified_decimals).pack(side=tk.LEFT)
self.ris_save_csv_btn = ttk.Button(btn_frame, text="Save CSV", command=self._on_save_ris_csv) # History tab
self.ris_save_csv_btn.pack(side=tk.RIGHT) history_frame = ttk.Frame(self.notebook)
self.notebook.add(ris_frame, text="RIS Status") self.history_tree = ttk.Treeview(history_frame, columns=("time", "flow", "tid", "size"), show="headings", height=8)
raw_frame = ttk.Frame(self.notebook) self.history_tree.heading("time", text="Time")
history_frame = ttk.Frame(raw_frame, width=380)
history_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(5, 2), pady=5)
ttk.Label(history_frame, text="History (latest)").pack(anchor=tk.W, padx=4)
list_container = ttk.Frame(history_frame)
list_container.pack(fill=tk.BOTH, expand=True, padx=4, pady=(2, 4))
columns = ("ts", "flow", "tid", "size")
self.history_tree = ttk.Treeview(list_container, columns=columns, show="headings", height=20)
self.history_tree.heading("ts", text="Timestamp")
self.history_tree.heading("flow", text="Flow") self.history_tree.heading("flow", text="Flow")
self.history_tree.heading("tid", text="TID") self.history_tree.heading("tid", text="TID")
self.history_tree.heading("size", text="Size") self.history_tree.heading("size", text="Size")
self.history_tree.column("ts", width=100, anchor="w") self.history_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.history_tree.column("flow", width=50, anchor="w") btn_frame = ttk.Frame(history_frame)
self.history_tree.column("tid", width=40, anchor="center") btn_frame.pack(fill=tk.X, padx=5, pady=5)
self.history_tree.column("size", width=50, anchor="e") ttk.Button(btn_frame, text="Clear", command=self._on_clear_history).pack(side=tk.LEFT)
self.history_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) ttk.Button(btn_frame, text="Settings", command=self._open_history_settings_dialog).pack(side=tk.LEFT, padx=4)
self.history_vscroll = ttk.Scrollbar(list_container, orient=tk.VERTICAL, command=self.history_tree.yview) self.notebook.add(history_frame, text="History")
self.history_vscroll.pack(side=tk.RIGHT, fill=tk.Y)
self.history_tree.config(yscrollcommand=self.history_vscroll.set) # Image tabs (MFD, SAR)
hb_frame = ttk.Frame(history_frame) self.mfd_tab = self._create_image_tab("MFD")
hb_frame.pack(fill=tk.X, padx=4, pady=(4, 4)) self.notebook.add(self.mfd_tab["frame"], text="MFD")
self.history_settings_btn = ttk.Button(hb_frame, text="Settings", command=self._open_history_settings_dialog) self.sar_tab = self._create_image_tab("SAR")
self.history_settings_btn.pack(side=tk.LEFT) self.notebook.add(self.sar_tab["frame"], text="SAR")
self.history_clear_btn = ttk.Button(hb_frame, text="Clear", command=self._on_clear_history)
self.history_clear_btn.pack(side=tk.RIGHT) # Binary and JSON viewers
self.raw_tab_text = scrolledtext.ScrolledText(raw_frame, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 9)) bin_frame = ttk.Frame(self.notebook)
self.raw_tab_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(2, 5), pady=5) self.bin_tab = scrolledtext.ScrolledText(bin_frame, height=12, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 9))
self.notebook.insert(1, raw_frame, text="SFP Raw") self.bin_tab.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.bin_tab = scrolledtext.ScrolledText(self.notebook, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 10)) self.notebook.add(bin_frame, text="BIN")
self.notebook.add(self.bin_tab, text="Binary (Hex)")
self.json_tab = scrolledtext.ScrolledText(self.notebook, state=tk.DISABLED, wrap=tk.WORD, font=("Consolas", 10)) json_frame = ttk.Frame(self.notebook)
self.notebook.add(self.json_tab, text="JSON") self.json_tab = scrolledtext.ScrolledText(json_frame, height=12, state=tk.DISABLED, wrap=tk.NONE, font=("Consolas", 9))
self.json_tab.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.notebook.add(json_frame, text="JSON")
def _create_script_sender_widgets(self, parent):
"""Create a small UI to enter and send ad-hoc script/commands."""
frame = ttk.Frame(parent, padding=4)
frame.pack(fill=tk.X)
ttk.Label(frame, text="Script:").pack(side=tk.LEFT, padx=(0, 4))
entry = ttk.Entry(frame, textvariable=self.script_var, width=72)
entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
send_btn = ttk.Button(frame, text="Send", command=self._on_send_script)
send_btn.pack(side=tk.LEFT, padx=(6, 0))
def _on_send_target(self): def _on_send_target(self):
if not self.shared_communicator or not self.shared_communicator.is_open: if not self.shared_communicator or not self.shared_communicator.is_open:

56
tools/test_ris_remove.py Normal file
View File

@ -0,0 +1,56 @@
# Test removal of targets with flags == 0
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s')
from target_simulator.gui.payload_router import DebugPayloadRouter
from target_simulator.analysis.simulation_state_hub import SimulationStateHub
import target_simulator.gui.payload_router as pr_mod
class FakeRisTarget:
def __init__(self, flags, x, y, z, heading):
self.flags = flags
self.x = x
self.y = y
self.z = z
self.heading = heading
class FakeScenario:
def __init__(self, timetag=123456789):
self.timetag = timetag
class FakeParsed:
def __init__(self, tgt_list):
self.tgt = type('T', (), {'tgt': tgt_list})()
self.scenario = FakeScenario()
@staticmethod
def from_buffer_copy(payload):
raise RuntimeError('Should be monkeypatched')
# first, create initial payload with 2 active targets
rads = [0.0, 1.5707964897155762]
fake_targets = []
for i, h in enumerate(rads):
fake_targets.append(FakeRisTarget(flags=1, x=1000+i, y=2000+i, z=100.0, heading=h))
original_parser = pr_mod.SfpRisStatusPayload
def make_parser(targets):
return type('P', (), {'from_buffer_copy': staticmethod(lambda payload: FakeParsed(targets))})
# inject initial
pr_mod.SfpRisStatusPayload = make_parser(fake_targets)
hub = SimulationStateHub()
router = DebugPayloadRouter(simulation_hub=hub, update_queue=None)
router._handle_ris_status(b'INJ')
print('After first injection, hub ids:', hub.get_all_target_ids())
# now create a payload where target 1 is inactive (flags=0)
fake_targets2 = [FakeRisTarget(flags=1, x=1000, y=2000, z=100.0, heading=0.0), FakeRisTarget(flags=0, x=0, y=0, z=0, heading=0.0)]
pr_mod.SfpRisStatusPayload = make_parser(fake_targets2)
router._handle_ris_status(b'INJ2')
print('After second injection (one inactive), hub ids:', hub.get_all_target_ids())
# restore
pr_mod.SfpRisStatusPayload = original_parser
print('Done')