diff --git a/target_simulator/core/sfp_transport.py b/target_simulator/core/sfp_transport.py index 3e0e111..ba097af 100644 --- a/target_simulator/core/sfp_transport.py +++ b/target_simulator/core/sfp_transport.py @@ -287,9 +287,9 @@ class SfpTransport: if frag == 0: self._cleanup_lingering_transactions(flow, tid) - logger.debug( - f"New transaction started for key={key}. Total size: {total_size} bytes." - ) + #logger.debug( + # f"New transaction started for key={key}. Total size: {total_size} bytes." + #) self._fragments[key] = {} try: self._buffers[key] = bytearray(total_size) @@ -321,9 +321,9 @@ class SfpTransport: ] if len(self._fragments[key]) == total_frags: - logger.debug( - f"Transaction complete for key={key}. Handing off to application layer." - ) + #logger.debug( + # f"Transaction complete for key={key}. Handing off to application layer." + #) completed_payload = self._buffers.pop(key) self._fragments.pop(key) diff --git a/target_simulator/gui/sfp_debug_window.py b/target_simulator/gui/sfp_debug_window.py index b6bec54..5d963c2 100644 --- a/target_simulator/gui/sfp_debug_window.py +++ b/target_simulator/gui/sfp_debug_window.py @@ -1,3 +1,4 @@ +from target_simulator.gui.ppi_display import PPIDisplay # target_simulator/gui/sfp_debug_window.py """ Provides a Toplevel window for debugging the SFP transport layer. @@ -14,6 +15,7 @@ import datetime import os import ctypes import time +import math from typing import Dict, Callable, Optional, Any import socket @@ -69,6 +71,7 @@ class SfpDebugWindow(tk.Toplevel): self.payload_router = DebugPayloadRouter() self.sfp_transport: Optional[SfpTransport] = None self.image_area_size = 150 + self._ppi_visible = False # --- TK Variables --- self.ip_var = tk.StringVar(value="127.0.0.1") @@ -146,7 +149,7 @@ class SfpDebugWindow(tk.Toplevel): self._create_target_sender_widgets(target_sender_frame) # --- Script Sender Frame (optional, can be removed if not needed) --- - script_frame = ttk.Frame(top_controls_frame) + script_frame = ttk.LabelFrame(top_controls_frame, text="Script to send") script_frame.pack(side=tk.TOP, fill=tk.X, pady=(0, 5)) self._create_script_sender_widgets(script_frame) @@ -154,6 +157,54 @@ class SfpDebugWindow(tk.Toplevel): self.notebook = ttk.Notebook(self) self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self._create_notebook_tabs() + + # (PPI widget is created inside the RIS tab right pane; see _create_notebook_tabs) + + def _toggle_ppi(self): + # Swap RIS table and RIS PPI container + try: + if self._ppi_visible: + # show table, hide ppi + 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: + # hide table, show ppi + 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: + # Fallback: if containers are missing, do nothing + self.logger.exception("Toggle PPI failed") + + def update_ppi_targets(self, targets): + if self._ppi_visible: + try: + self.ris_ppi_widget.update_targets(targets) + except Exception: + self.logger.exception("Failed to update RIS PPI targets") + + def on_ris_status_update(self, ris_status_payload): + # Convert RIS targets to Target objects (minimal, for display) + targets = [] + for i in range(getattr(ris_status_payload.tgt, 'tgt', []).__len__()): + ris_tgt = ris_status_payload.tgt.tgt[i] + # Only show if valid/active (customize as needed) + if getattr(ris_tgt, 'flags', 0) & 1: + t = Target( + target_id=i, + trajectory=[], + active=True, + traceable=True, + ) + t.current_range_nm = (ris_tgt.x ** 2 + ris_tgt.y ** 2) ** 0.5 / 6076.12 + t.current_azimuth_deg = math.degrees(math.atan2(ris_tgt.x, ris_tgt.y)) + t.current_altitude_ft = ris_tgt.z + t.current_heading_deg = getattr(ris_tgt, 'heading', 0.0) + targets.append(t) + self.update_ppi_targets(targets) def _create_connection_widgets(self, parent): ttk.Label(parent, text="IP:").pack(side=tk.LEFT, padx=(4, 2)) @@ -181,11 +232,8 @@ class SfpDebugWindow(tk.Toplevel): # Quick commands frame quick_frame = ttk.Frame(parent) quick_frame.pack(side=tk.RIGHT) - # Always prefix commands with '$' to satisfy server's mex parser - ttk.Button(quick_frame, text="tgtreset", command=lambda: self._on_send_simple_command(command_builder.build_tgtreset())).pack(side=tk.LEFT, padx=2) - ttk.Button(quick_frame, text="pause", command=lambda: self._on_send_simple_command(command_builder.build_pause())).pack(side=tk.LEFT, padx=2) - ttk.Button(quick_frame, text="continue", command=lambda: self._on_send_simple_command(command_builder.build_continue())).pack(side=tk.LEFT, padx=2) - ttk.Button(quick_frame, text="tgtset (cur)", command=lambda: self._on_send_tgtset()).pack(side=tk.LEFT, padx=6) + # Always prefix commands with '$' to satisfy server's mex parser + # Quick command buttons moved to the Simple Target Sender area def _create_target_sender_widgets(self, parent): grid = ttk.Frame(parent, padding=5) @@ -249,6 +297,17 @@ class SfpDebugWindow(tk.Toplevel): ) send_button.pack(side=tk.LEFT, padx=(10, 0)) + # --- Quick command buttons (moved here from connection frame) --- + quick_cmd_frame = ttk.Frame(parent) + quick_cmd_frame.pack(fill=tk.X, pady=(6, 0)) + ttk.Button(quick_cmd_frame, text="tgtreset", command=lambda: self._on_send_simple_command(command_builder.build_tgtreset())).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="tgtset (cur)", command=lambda: self._on_send_tgtset()).pack(side=tk.LEFT, padx=8) + # PPI toggle button (moved here) + self.ppi_toggle_btn = ttk.Button(quick_cmd_frame, text="Show PPI Map", command=self._toggle_ppi) + 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( @@ -285,8 +344,12 @@ class SfpDebugWindow(tk.Toplevel): self.scenario_tree.pack(fill=tk.BOTH, expand=True) paned.add(left, weight=1) right = ttk.Frame(paned) + # Containers: one for the numeric table, one for the PPI (swapable) + self.ris_table_container = ttk.Frame(right) + self.ris_ppi_container = ttk.Frame(right) + cols = ("idx", "flags", "heading", "x", "y", "z") - self.ris_tree = ttk.Treeview(right, columns=cols, show="headings", height=12) + self.ris_tree = ttk.Treeview(self.ris_table_container, columns=cols, show="headings", height=12) for c, txt in zip(cols, ("#", "flags", "heading", "x", "y", "z")): self.ris_tree.heading(c, text=txt) self.ris_tree.column(c, width=70, anchor="center") @@ -299,7 +362,15 @@ class SfpDebugWindow(tk.Toplevel): except Exception: pass self.ris_tree.pack(fill=tk.BOTH, expand=True) + # Initially show table container, keep ppi container ready (hidden) + self.ris_table_container.pack(fill=tk.BOTH, expand=True) paned.add(right, weight=2) + # Create PPI widget inside ris_ppi_container but keep it hidden + try: + self.ris_ppi_widget = PPIDisplay(self.ris_ppi_container, max_range_nm=100) + self.ris_ppi_widget.pack(fill=tk.BOTH, expand=True) + except Exception: + self.ris_ppi_widget = None btn_frame = ttk.Frame(ris_frame) btn_frame.pack(fill=tk.X, padx=5, pady=(0, 5)) self.scenario_view_mode = tk.StringVar(value="simplified") @@ -788,7 +859,7 @@ class SfpDebugWindow(tk.Toplevel): minutes_full = (ad - degrees) * 60 minutes = int(minutes_full) seconds = (minutes_full - minutes) * 60 - return f"{degrees}°{minutes}'{seconds:.2f}\" {direction}" + return f"{degrees}°{minutes}'{seconds:.2f} {direction}" scenario_rows = [] for label, key, unit in order: if key in scenario: @@ -1184,7 +1255,7 @@ class SfpDebugWindow(tk.Toplevel): import json text = json.dumps(json.loads(payload.decode("utf-8")), indent=2) except Exception as e: - text = f"--- FAILED TO PARSE JSON ---\n{e}\n\n--- RAW HEX DUMP ---\n" + text = f"--- FAILED TO PARSE JSON ---\n{e}\n\n--- RAW HEX DUMP ---" text += self._format_hex_dump(payload) widget.config(state=tk.NORMAL) widget.delete("1.0", tk.END) @@ -1230,4 +1301,4 @@ class SfpDebugWindow(tk.Toplevel): hex_part = " ".join(f"{b:02X}" for b in chunk) ascii_part = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk) lines.append(f"{i:08X} {hex_part:<{length*3}} |{ascii_part}|") - return "\n".join(lines) \ No newline at end of file + return "\n".join(lines)