From a0652998ab4be42b2172608a48f03b73d6fd50fd Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 3 Nov 2025 13:14:55 +0100 Subject: [PATCH] cambiate le dimensioni della finestra, settato porte di debug per essere le stesse del programma, cambiato reset target verso server --- settings.json | 2 +- target_simulator/_version.py | 45 ++-- target_simulator/gui/main_view.py | 262 ++++++++++++++--------- target_simulator/gui/sfp_debug_window.py | 28 +++ 4 files changed, 205 insertions(+), 132 deletions(-) diff --git a/settings.json b/settings.json index ace6158..9b98303 100644 --- a/settings.json +++ b/settings.json @@ -2,7 +2,7 @@ "general": { "scan_limit": 60, "max_range": 100, - "geometry": "1599x1089+587+179", + "geometry": "1599x1075+587+179", "last_selected_scenario": "scenario_dritto", "connection": { "target": { diff --git a/target_simulator/_version.py b/target_simulator/_version.py index 3d3ef50..b03ccda 100644 --- a/target_simulator/_version.py +++ b/target_simulator/_version.py @@ -6,10 +6,10 @@ import re # --- Version Data (Generated) --- -__version__ = "v.0.0.0.62-0-g00ffcb5-dirty" -GIT_COMMIT_HASH = "00ffcb54a320df4b682bb77920dbb758e10fbca0" +__version__ = "v.0.0.0.67-0-gc8c1bb0-dirty" +GIT_COMMIT_HASH = "c8c1bb0f372c8217a86fbdb82e4922888594d2ba" GIT_BRANCH = "master" -BUILD_TIMESTAMP = "2025-10-31T11:56:55.656151+00:00" +BUILD_TIMESTAMP = "2025-11-03T11:17:11.985027+00:00" IS_GIT_REPO = True # --- Default Values (for comparison or fallback) --- @@ -17,7 +17,6 @@ DEFAULT_VERSION = "0.0.0+unknown" DEFAULT_COMMIT = "Unknown" DEFAULT_BRANCH = "Unknown" - # --- Helper Function --- def get_version_string(format_string=None): """ @@ -45,39 +44,29 @@ def get_version_string(format_string=None): replacements = {} try: - replacements["version"] = __version__ if __version__ else DEFAULT_VERSION - replacements["commit"] = GIT_COMMIT_HASH if GIT_COMMIT_HASH else DEFAULT_COMMIT - replacements["commit_short"] = ( - GIT_COMMIT_HASH[:7] - if GIT_COMMIT_HASH and len(GIT_COMMIT_HASH) >= 7 - else DEFAULT_COMMIT - ) - replacements["branch"] = GIT_BRANCH if GIT_BRANCH else DEFAULT_BRANCH - replacements["timestamp"] = BUILD_TIMESTAMP if BUILD_TIMESTAMP else "Unknown" - replacements["timestamp_short"] = ( - BUILD_TIMESTAMP.split("T")[0] - if BUILD_TIMESTAMP and "T" in BUILD_TIMESTAMP - else "Unknown" - ) - replacements["is_git"] = "Git" if IS_GIT_REPO else "Unknown" - replacements["dirty"] = ( - "-dirty" if __version__ and __version__.endswith("-dirty") else "" - ) + replacements['version'] = __version__ if __version__ else DEFAULT_VERSION + replacements['commit'] = GIT_COMMIT_HASH if GIT_COMMIT_HASH else DEFAULT_COMMIT + replacements['commit_short'] = GIT_COMMIT_HASH[:7] if GIT_COMMIT_HASH and len(GIT_COMMIT_HASH) >= 7 else DEFAULT_COMMIT + replacements['branch'] = GIT_BRANCH if GIT_BRANCH else DEFAULT_BRANCH + replacements['timestamp'] = BUILD_TIMESTAMP if BUILD_TIMESTAMP else "Unknown" + replacements['timestamp_short'] = BUILD_TIMESTAMP.split('T')[0] if BUILD_TIMESTAMP and 'T' in BUILD_TIMESTAMP else "Unknown" + replacements['is_git'] = "Git" if IS_GIT_REPO else "Unknown" + replacements['dirty'] = "-dirty" if __version__ and __version__.endswith('-dirty') else "" tag = DEFAULT_VERSION if __version__ and IS_GIT_REPO: - match = re.match(r"^(v?([0-9]+(?:\.[0-9]+)*))", __version__) + match = re.match(r'^(v?([0-9]+(?:\.[0-9]+)*))', __version__) if match: tag = match.group(1) - replacements["tag"] = tag + replacements['tag'] = tag output_string = format_string for placeholder, value in replacements.items(): - pattern = re.compile(r"{{\s*" + re.escape(placeholder) + r"\s*}}") - output_string = pattern.sub(str(value), output_string) + pattern = re.compile(r'{{\s*' + re.escape(placeholder) + r'\s*}}') + output_string = pattern.sub(str(value), output_string) - if re.search(r"{\s*\w+\s*}", output_string): - pass # Or log a warning: print(f"Warning: Unreplaced placeholders found: {output_string}") + if re.search(r'{\s*\w+\s*}', output_string): + pass # Or log a warning: print(f"Warning: Unreplaced placeholders found: {output_string}") return output_string diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index 032f1db..5a086d2 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -37,6 +37,26 @@ from target_simulator.gui.analysis_window import AnalysisWindow from target_simulator.core import command_builder from target_simulator.analysis.simulation_archive import SimulationArchive +# --- Import Version Info FOR THE WRAPPER ITSELF --- +try: + # Use absolute import based on package name + from target_simulator import _version as wrapper_version + + WRAPPER_APP_VERSION_STRING = f"{wrapper_version.__version__} ({wrapper_version.GIT_BRANCH}/{wrapper_version.GIT_COMMIT_HASH[:7]})" + WRAPPER_BUILD_INFO = f"Wrapper Built: {wrapper_version.BUILD_TIMESTAMP}" +except ImportError: + # This might happen if you run the wrapper directly from source + # without generating its _version.py first (if you use that approach for the wrapper itself) + WRAPPER_APP_VERSION_STRING = "(Dev Wrapper)" + WRAPPER_BUILD_INFO = "Wrapper build time unknown" +# --- End Import Version Info --- + +# --- Constants for Version Generation --- +DEFAULT_VERSION = "0.0.0+unknown" +DEFAULT_COMMIT = "Unknown" +DEFAULT_BRANCH = "Unknown" +# --- End Constants --- + GUI_QUEUE_POLL_INTERVAL_MS = 100 GUI_REFRESH_RATE_MS = 40 @@ -85,8 +105,8 @@ class MainView(tk.Tk): self._slider_is_dragging = False # --- Window and UI Setup --- - self.title("Radar Target Simulator") - self.geometry(settings.get("geometry", "1200x1024")) + self.title(f"Radar Target Simulator") + self.geometry(settings.get("geometry", "1200x900")) self.minsize(1024, 768) self._create_menubar() @@ -525,6 +545,10 @@ class MainView(tk.Tk): ttk.Button( btn_frame, text="Refresh List", command=self._refresh_analysis_list ).pack(side=tk.LEFT) + # Button to open the archive folder in the system file explorer + ttk.Button( + btn_frame, text="Open Archive Folder", command=self._open_archive_folder + ).pack(side=tk.LEFT, padx=(6, 0)) ttk.Button( btn_frame, text="Analyze Selected", command=self._on_analyze_run ).pack(side=tk.RIGHT) @@ -617,7 +641,7 @@ class MainView(tk.Tk): def _update_window_title(self): """Updates the window title based on the current scenario.""" - base_title = "Radar Target Simulator" + base_title = (f"Radar Target Simulator- {WRAPPER_APP_VERSION_STRING}") if self.current_scenario_name: self.title(f"{base_title} - {self.current_scenario_name}") else: @@ -830,119 +854,116 @@ class MainView(tk.Tk): True if the reset commands were sent successfully, False otherwise. """ if not self.target_communicator or not self.target_communicator.is_open: - self.logger.error( - "Cannot reset radar state: communicator is not connected." - ) - messagebox.showerror( - "Connection Error", "Cannot reset radar: Not Connected." - ) + self.logger.error("Cannot reset radar state: communicator is not connected.") + messagebox.showerror("Connection Error", "Cannot reset radar: Not Connected.") return False - self.logger.info("Sending reset commands to deactivate all radar targets...") + self.logger.info("Attempting to reset radar state (legacy vs JSON-aware logic)") - # Build the atomic reset command. try: - reset_command = "tgtset /-s" - except Exception: - self.logger.exception( - "Error while building atomic reset command; falling back to raw string." - ) - reset_command = "tgtset /-s" - - # Some radar servers require adjusting internal parameters for legacy mode. - # Use the non-$ textual commands and ensure newline termination per the - # user's request: send exactly "mex.t_rows=80\n" and "tgtset /-s\n". - prep_command = "mex.t_rows=80\n" - - # If the communicator was configured to use the JSON protocol, send - # the JSON reset command expected by the server. Otherwise, fall back - # to the legacy prep + atomic reset command sequence. - try: - use_json = bool( - getattr(self.target_communicator, "_use_json_protocol", False) - ) + use_json = bool(getattr(self.target_communicator, "_use_json_protocol", False)) except Exception: use_json = False - if use_json: - # Send both the per-target zeroing payloads (if available) followed - # by a minimal {'CMD':'reset'} line. The per-target payloads clear - # active flags on individual IDs; the simple reset is a noop on - # servers that don't implement it but is harmless when supported. - try: - json_payloads = command_builder.build_json_reset_ids() - # Ensure all payloads are newline-terminated and then append - # the simple reset as a final step so servers that process - # the reset command can react accordingly. - final_reset = '{"CMD":"reset"}\n' - commands_to_send = [ - p if p.endswith("\n") else p + "\n" for p in json_payloads - ] + [final_reset] - self.logger.info( - "Using JSON Reset IDs payloads for radar reset (parts=%d + reset).", - len(json_payloads), - ) - except Exception: - self.logger.exception( - "Failed to build Reset IDs JSON payloads; falling back to simple JSON reset." - ) - commands_to_send = ['{"CMD":"reset"}\n'] - else: - # Legacy textual reset sequence (no leading $; newline-terminated strings) - commands_to_send = [prep_command, reset_command + "\n"] - - # When using JSON protocol, send each payload part individually. - if use_json: - all_ok = True - for payload in commands_to_send: + # Helper: wait until hub reports no active real targets (bounded) + def _wait_for_clear(timeout_s: float = 3.0, poll_interval: float = 0.2) -> bool: + waited = 0.0 + while waited < timeout_s: try: - ok = self.target_communicator.send_commands([payload]) + if not self.simulation_hub.has_active_real_targets(): + return True except Exception: - self.logger.exception("Exception while sending JSON reset payload") - ok = False - if not ok: - all_ok = False - self.logger.error("Failed to send JSON reset payload part.") - break - if not all_ok: - messagebox.showerror( - "Reset Error", "Failed to send reset payload(s) to the radar." - ) - return False - else: - if not self.target_communicator.send_commands(commands_to_send): - self.logger.error( - "Failed to send preparatory/reset commands to the radar." - ) - messagebox.showerror( - "Reset Error", "Failed to send reset command to the radar." - ) + # If hub query fails, treat as not cleared and keep waiting + self.logger.debug("Error while querying simulation_hub during reset wait", exc_info=True) + time.sleep(poll_interval) + waited += poll_interval + return False + + # 1) Legacy textual command path + if not use_json: + cmd = "tgtset /-s\n" + self.logger.info("Sending legacy reset command: %s", cmd.strip()) + try: + ok = self.target_communicator.send_commands([cmd]) + except Exception: + self.logger.exception("Failed to send legacy reset command") + ok = False + + if not ok: + messagebox.showerror("Reset Error", "Failed to send reset command to the radar.") return False - self.logger.info( - "Successfully sent preparatory and atomic reset commands: %s", - commands_to_send, - ) - - # Poll the simulation hub to confirm the server processed the reset. - # The success condition is that there are no more active REAL targets being reported. - timeout_s = 3.0 - poll_interval = 0.2 - waited = 0.0 - while waited < timeout_s: - # MODIFICATION: Use the new, correct check. - if not self.simulation_hub.has_active_real_targets(): - self.logger.info("Radar reported zero active real targets after reset.") + # Wait briefly for the server to apply the reset and for hub to reflect it + cleared = _wait_for_clear() + if cleared: + self.logger.info("Legacy reset acknowledged: no active real targets remain.") return True - time.sleep(poll_interval) - waited += poll_interval + else: + self.logger.error("Legacy reset sent but server did not clear active targets in time.") + messagebox.showerror("Reset Error", "Radar did not clear targets after reset.") + return False - # If we reach here, the hub still reports active real targets — treat as failure - self.logger.error( - "Radar did not clear real targets after reset within timeout." - ) - messagebox.showerror("Reset Error", "Radar did not clear targets after reset.") - return False + # 2) JSON-capable communicator path + # First attempt: send a single simple JSON reset command + try: + json_reset = '{"CMD":"reset"}\n' + self.logger.info("Sending JSON reset command: %s", json_reset.strip()) + try: + ok = self.target_communicator.send_commands([json_reset]) + except Exception: + self.logger.exception("Failed to send JSON reset command") + ok = False + + # Wait to see if server cleared active flags + if ok: + cleared = _wait_for_clear() + if cleared: + self.logger.info("JSON reset acknowledged: server cleared active flags.") + return True + else: + self.logger.info("JSON reset sent but server did not clear active flags; will attempt per-target JSON zeroing.") + else: + self.logger.warning("JSON reset command failed to send; will attempt per-target JSON zeroing.") + except Exception: + self.logger.exception("Unexpected error while attempting JSON reset") + + # 3) Build and send per-target JSON payloads that explicitly clear active flag + try: + self.logger.info("Building per-target JSON reset payloads to clear active flags on all targets.") + json_payloads = command_builder.build_json_reset_ids() + except Exception: + self.logger.exception("Failed to build per-target JSON reset payloads") + messagebox.showerror("Reset Error", "Failed to build JSON reset payloads.") + return False + + # Send each payload and check hub state after sending + all_sent_ok = True + for i, payload in enumerate(json_payloads): + p = payload if payload.endswith("\n") else payload + "\n" + self.logger.info("Sending JSON reset payload part %d/%d", i + 1, len(json_payloads)) + try: + ok = self.target_communicator.send_commands([p]) + except Exception: + self.logger.exception("Exception while sending JSON reset payload part %d", i + 1) + ok = False + if not ok: + all_sent_ok = False + self.logger.error("Failed to send JSON reset payload part %d", i + 1) + break + + if not all_sent_ok: + messagebox.showerror("Reset Error", "Failed to send one or more JSON reset payloads to the radar.") + return False + + # After sending all per-target payloads, wait for hub to show targets cleared + cleared = _wait_for_clear() + if cleared: + self.logger.info("Per-target JSON reset succeeded: all active flags cleared.") + return True + else: + self.logger.error("Per-target JSON reset did not clear active flags within timeout.") + messagebox.showerror("Reset Error", "Radar did not clear targets after per-target reset.") + return False def _on_start_simulation(self): if self.is_simulation_running.get(): @@ -1997,6 +2018,41 @@ class MainView(tk.Tk): iid=run["filepath"], ) + def _open_archive_folder(self): + """Open the simulation archive folder in the system file explorer.""" + archive_folder = SimulationArchive.ARCHIVE_FOLDER + try: + # Ensure the folder exists + os.makedirs(archive_folder, exist_ok=True) + # On Windows, os.startfile opens the folder in Explorer + try: + os.startfile(archive_folder) + return + except Exception: + # Fallback to using explorer.exe via subprocess + import subprocess + + try: + subprocess.run(["explorer", os.path.abspath(archive_folder)]) + return + except Exception: + pass + except Exception as e: + self.logger.exception("Failed to open archive folder: %s", e) + + # If we get here, show an error to the user + try: + messagebox.showerror( + "Error", + f"Could not open archive folder: {archive_folder}\nSee logs for details.", + ) + except Exception: + # If even showing a messagebox fails, log and continue + try: + self.logger.error("Could not open archive folder: %s", archive_folder) + except Exception: + pass + def _on_analyze_run(self): selected_item = self.analysis_tree.focus() if not selected_item: diff --git a/target_simulator/gui/sfp_debug_window.py b/target_simulator/gui/sfp_debug_window.py index 6cd887f..cc74123 100644 --- a/target_simulator/gui/sfp_debug_window.py +++ b/target_simulator/gui/sfp_debug_window.py @@ -124,6 +124,34 @@ class SfpDebugWindow(tk.Toplevel): self._create_widgets() + # Initialize connection fields from the central connection settings + try: + cfg = None + # Prefer the master's runtime connection_config if available + if hasattr(self.master, "connection_config") and self.master.connection_config: + cfg = self.master.connection_config.get("target", {}) + else: + # Fallback to ConfigManager if present on master + gm = getattr(self.master, "config_manager", None) + if gm: + cfg = gm.get_connection_settings().get("target", {}) + + if cfg is not None: + sfp_cfg = cfg.get("sfp", {}) + if sfp_cfg: + ip = sfp_cfg.get("ip") or sfp_cfg.get("host") + if ip: + self.ip_var.set(str(ip)) + port = sfp_cfg.get("port") + if port is not None: + self.server_port_var.set(str(port)) + local = sfp_cfg.get("local_port") + if local is not None: + self.local_port_var.set(str(local)) + except Exception: + # Do not prevent window from opening on any config read error + self.logger.debug("Could not initialise SFP debug connection fields from central config", exc_info=True) + if self.shared_communicator: self._update_toggle_state(self.shared_communicator.is_open) # Schedule the legacy polling loop used by tests and older UI code