cambiate le dimensioni della finestra, settato porte di debug per essere le stesse del programma, cambiato reset target verso server

This commit is contained in:
VALLONGOL 2025-11-03 13:14:55 +01:00
parent ab22246f54
commit a0652998ab
4 changed files with 205 additions and 132 deletions

View File

@ -2,7 +2,7 @@
"general": { "general": {
"scan_limit": 60, "scan_limit": 60,
"max_range": 100, "max_range": 100,
"geometry": "1599x1089+587+179", "geometry": "1599x1075+587+179",
"last_selected_scenario": "scenario_dritto", "last_selected_scenario": "scenario_dritto",
"connection": { "connection": {
"target": { "target": {

View File

@ -6,10 +6,10 @@
import re import re
# --- Version Data (Generated) --- # --- Version Data (Generated) ---
__version__ = "v.0.0.0.62-0-g00ffcb5-dirty" __version__ = "v.0.0.0.67-0-gc8c1bb0-dirty"
GIT_COMMIT_HASH = "00ffcb54a320df4b682bb77920dbb758e10fbca0" GIT_COMMIT_HASH = "c8c1bb0f372c8217a86fbdb82e4922888594d2ba"
GIT_BRANCH = "master" 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 IS_GIT_REPO = True
# --- Default Values (for comparison or fallback) --- # --- Default Values (for comparison or fallback) ---
@ -17,7 +17,6 @@ DEFAULT_VERSION = "0.0.0+unknown"
DEFAULT_COMMIT = "Unknown" DEFAULT_COMMIT = "Unknown"
DEFAULT_BRANCH = "Unknown" DEFAULT_BRANCH = "Unknown"
# --- Helper Function --- # --- Helper Function ---
def get_version_string(format_string=None): def get_version_string(format_string=None):
""" """
@ -45,38 +44,28 @@ def get_version_string(format_string=None):
replacements = {} replacements = {}
try: try:
replacements["version"] = __version__ if __version__ else DEFAULT_VERSION replacements['version'] = __version__ if __version__ else DEFAULT_VERSION
replacements["commit"] = GIT_COMMIT_HASH if GIT_COMMIT_HASH else DEFAULT_COMMIT replacements['commit'] = GIT_COMMIT_HASH if GIT_COMMIT_HASH else DEFAULT_COMMIT
replacements["commit_short"] = ( replacements['commit_short'] = GIT_COMMIT_HASH[:7] if GIT_COMMIT_HASH and len(GIT_COMMIT_HASH) >= 7 else DEFAULT_COMMIT
GIT_COMMIT_HASH[:7] replacements['branch'] = GIT_BRANCH if GIT_BRANCH else DEFAULT_BRANCH
if GIT_COMMIT_HASH and len(GIT_COMMIT_HASH) >= 7 replacements['timestamp'] = BUILD_TIMESTAMP if BUILD_TIMESTAMP else "Unknown"
else DEFAULT_COMMIT 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["branch"] = GIT_BRANCH if GIT_BRANCH else DEFAULT_BRANCH replacements['dirty'] = "-dirty" if __version__ and __version__.endswith('-dirty') else ""
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 tag = DEFAULT_VERSION
if __version__ and IS_GIT_REPO: 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: if match:
tag = match.group(1) tag = match.group(1)
replacements["tag"] = tag replacements['tag'] = tag
output_string = format_string output_string = format_string
for placeholder, value in replacements.items(): for placeholder, value in replacements.items():
pattern = re.compile(r"{{\s*" + re.escape(placeholder) + r"\s*}}") pattern = re.compile(r'{{\s*' + re.escape(placeholder) + r'\s*}}')
output_string = pattern.sub(str(value), output_string) output_string = pattern.sub(str(value), output_string)
if re.search(r"{\s*\w+\s*}", output_string): if re.search(r'{\s*\w+\s*}', output_string):
pass # Or log a warning: print(f"Warning: Unreplaced placeholders found: {output_string}") pass # Or log a warning: print(f"Warning: Unreplaced placeholders found: {output_string}")
return output_string return output_string

View File

@ -37,6 +37,26 @@ from target_simulator.gui.analysis_window import AnalysisWindow
from target_simulator.core import command_builder from target_simulator.core import command_builder
from target_simulator.analysis.simulation_archive import SimulationArchive 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_QUEUE_POLL_INTERVAL_MS = 100
GUI_REFRESH_RATE_MS = 40 GUI_REFRESH_RATE_MS = 40
@ -85,8 +105,8 @@ class MainView(tk.Tk):
self._slider_is_dragging = False self._slider_is_dragging = False
# --- Window and UI Setup --- # --- Window and UI Setup ---
self.title("Radar Target Simulator") self.title(f"Radar Target Simulator")
self.geometry(settings.get("geometry", "1200x1024")) self.geometry(settings.get("geometry", "1200x900"))
self.minsize(1024, 768) self.minsize(1024, 768)
self._create_menubar() self._create_menubar()
@ -525,6 +545,10 @@ class MainView(tk.Tk):
ttk.Button( ttk.Button(
btn_frame, text="Refresh List", command=self._refresh_analysis_list btn_frame, text="Refresh List", command=self._refresh_analysis_list
).pack(side=tk.LEFT) ).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( ttk.Button(
btn_frame, text="Analyze Selected", command=self._on_analyze_run btn_frame, text="Analyze Selected", command=self._on_analyze_run
).pack(side=tk.RIGHT) ).pack(side=tk.RIGHT)
@ -617,7 +641,7 @@ class MainView(tk.Tk):
def _update_window_title(self): def _update_window_title(self):
"""Updates the window title based on the current scenario.""" """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: if self.current_scenario_name:
self.title(f"{base_title} - {self.current_scenario_name}") self.title(f"{base_title} - {self.current_scenario_name}")
else: else:
@ -830,120 +854,117 @@ class MainView(tk.Tk):
True if the reset commands were sent successfully, False otherwise. True if the reset commands were sent successfully, False otherwise.
""" """
if not self.target_communicator or not self.target_communicator.is_open: if not self.target_communicator or not self.target_communicator.is_open:
self.logger.error( self.logger.error("Cannot reset radar state: communicator is not connected.")
"Cannot reset radar state: communicator is not connected." messagebox.showerror("Connection Error", "Cannot reset radar: Not Connected.")
)
messagebox.showerror(
"Connection Error", "Cannot reset radar: Not Connected."
)
return False 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: try:
reset_command = "tgtset /-s" use_json = bool(getattr(self.target_communicator, "_use_json_protocol", False))
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)
)
except Exception: except Exception:
use_json = False use_json = False
if use_json: # Helper: wait until hub reports no active real targets (bounded)
# Send both the per-target zeroing payloads (if available) followed def _wait_for_clear(timeout_s: float = 3.0, poll_interval: float = 0.2) -> bool:
# 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:
try:
ok = self.target_communicator.send_commands([payload])
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."
)
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 waited = 0.0
while waited < timeout_s: while waited < timeout_s:
# MODIFICATION: Use the new, correct check. try:
if not self.simulation_hub.has_active_real_targets(): if not self.simulation_hub.has_active_real_targets():
self.logger.info("Radar reported zero active real targets after reset.")
return True return True
except Exception:
# 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) time.sleep(poll_interval)
waited += poll_interval waited += poll_interval
return False
# If we reach here, the hub still reports active real targets — treat as failure # 1) Legacy textual command path
self.logger.error( if not use_json:
"Radar did not clear real targets after reset within timeout." 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
# 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
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.") messagebox.showerror("Reset Error", "Radar did not clear targets after reset.")
return False 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): def _on_start_simulation(self):
if self.is_simulation_running.get(): if self.is_simulation_running.get():
self.logger.info("Simulation is already running.") self.logger.info("Simulation is already running.")
@ -1997,6 +2018,41 @@ class MainView(tk.Tk):
iid=run["filepath"], 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): def _on_analyze_run(self):
selected_item = self.analysis_tree.focus() selected_item = self.analysis_tree.focus()
if not selected_item: if not selected_item:

View File

@ -124,6 +124,34 @@ class SfpDebugWindow(tk.Toplevel):
self._create_widgets() 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: if self.shared_communicator:
self._update_toggle_state(self.shared_communicator.is_open) self._update_toggle_state(self.shared_communicator.is_open)
# Schedule the legacy polling loop used by tests and older UI code # Schedule the legacy polling loop used by tests and older UI code