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": {
"scan_limit": 60,
"max_range": 100,
"geometry": "1599x1089+587+179",
"geometry": "1599x1075+587+179",
"last_selected_scenario": "scenario_dritto",
"connection": {
"target": {

View File

@ -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

View File

@ -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:

View File

@ -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