rimosso moltiplicatore per simulazione.

This commit is contained in:
VALLONGOL 2025-11-04 08:19:17 +01:00
parent d1315c714a
commit b03777ae13
5 changed files with 184 additions and 41 deletions

View File

@ -416,6 +416,38 @@
} }
], ],
"use_spline": false "use_spline": false
},
{
"target_id": 1,
"active": true,
"traceable": true,
"trajectory": [
{
"maneuver_type": "Fly to Point",
"duration_s": 10.0,
"target_range_nm": 40.0,
"target_azimuth_deg": -45.0,
"target_altitude_ft": 10000.0,
"target_velocity_fps": 506.343,
"target_heading_deg": 90.0,
"longitudinal_acceleration_g": 0.0,
"lateral_acceleration_g": 0.0,
"vertical_acceleration_g": 0.0,
"turn_direction": "Right"
},
{
"maneuver_type": "Fly to Point",
"duration_s": 30.0,
"target_range_nm": 40.0,
"target_azimuth_deg": 45.0,
"target_altitude_ft": 10000.0,
"longitudinal_acceleration_g": 0.0,
"lateral_acceleration_g": 0.0,
"vertical_acceleration_g": 0.0,
"turn_direction": "Right"
}
],
"use_spline": false
} }
] ]
} }

View File

@ -3,7 +3,7 @@
"scan_limit": 60, "scan_limit": 60,
"max_range": 100, "max_range": 100,
"geometry": "1305x929+587+179", "geometry": "1305x929+587+179",
"last_selected_scenario": "scenario2", "last_selected_scenario": "corto",
"connection": { "connection": {
"target": { "target": {
"type": "sfp", "type": "sfp",
@ -17,8 +17,8 @@
}, },
"sfp": { "sfp": {
"ip": "127.0.0.1", "ip": "127.0.0.1",
"port": 60003, "port": 60013,
"local_port": 60002, "local_port": 60012,
"use_json_protocol": true "use_json_protocol": true
} }
}, },

View File

@ -346,21 +346,18 @@ class MainView(tk.Tk):
spacer = ttk.Frame(engine_frame) spacer = ttk.Frame(engine_frame)
spacer.grid(row=0, column=3, sticky="ew") spacer.grid(row=0, column=3, sticky="ew")
# Display fixed speed indicator (1x). The real system runs at
# 1x and the speed should not be changed at runtime, so show a
# read-only label instead of an editable combobox.
ttk.Label(engine_frame, text="Speed:").grid( ttk.Label(engine_frame, text="Speed:").grid(
row=0, column=4, sticky="e", padx=(10, 2), pady=5 row=0, column=4, sticky="e", padx=(10, 2), pady=5
) )
self.time_multiplier_var = tk.StringVar(value="1x") self.time_multiplier_var = tk.StringVar(value="1x")
self.multiplier_combo = ttk.Combobox( # Keep attribute name `multiplier_combo` for backward
engine_frame, # compatibility with other code that references it, but expose
textvariable=self.time_multiplier_var, # a non-interactive label instead of a Combobox.
values=["1x", "2x", "4x", "10x", "20x"], self.multiplier_combo = ttk.Label(engine_frame, text="1x")
state="readonly",
width=4,
)
self.multiplier_combo.grid(row=0, column=5, sticky="w", padx=(0, 5), pady=5) self.multiplier_combo.grid(row=0, column=5, sticky="w", padx=(0, 5), pady=5)
self.multiplier_combo.bind(
"<<ComboboxSelected>>", self._on_time_multiplier_changed
)
ttk.Label(engine_frame, text="Update Time (s):").grid( ttk.Label(engine_frame, text="Update Time (s):").grid(
row=0, column=6, sticky="e", padx=(10, 2), pady=5 row=0, column=6, sticky="e", padx=(10, 2), pady=5
@ -930,20 +927,39 @@ class MainView(tk.Tk):
return False return False
def _on_start_simulation(self): def _on_start_simulation(self):
# Delegate to SimulationController if available # Prevent duplicate start attempts: use a simple re-entrancy guard.
try: if getattr(self, "_start_in_progress_main", False):
if hasattr(self, "simulation_controller") and self.simulation_controller:
return self.simulation_controller.start_simulation(self)
except Exception:
try: try:
self.logger.exception("SimulationController start failed; falling back to inline start.") self.logger.info("Start already in progress; ignoring duplicate request.")
except Exception: except Exception:
pass pass
# If controller is not present or failed, attempt no-op fallback return
self._start_in_progress_main = True
try: try:
messagebox.showerror("Start Error", "Unable to start simulation (controller unavailable).") # Delegate to SimulationController if available
except Exception: try:
pass if hasattr(self, "simulation_controller") and self.simulation_controller:
return self.simulation_controller.start_simulation(self)
except Exception:
try:
self.logger.exception("SimulationController start failed; falling back to inline start.")
except Exception:
pass
# If controller is not present or failed, attempt no-op fallback
try:
messagebox.showerror("Start Error", "Unable to start simulation (controller unavailable).")
except Exception:
pass
finally:
# Clear the guard so future explicit retries are allowed. The
# actual button state will be managed by _update_button_states
# based on `is_simulation_running`.
try:
self._start_in_progress_main = False
except Exception:
pass
def _on_stop_simulation(self): def _on_stop_simulation(self):
try: try:
@ -1102,15 +1118,45 @@ class MainView(tk.Tk):
tk.NORMAL if (not is_running and has_data_to_analyze) else tk.DISABLED tk.NORMAL if (not is_running and has_data_to_analyze) else tk.DISABLED
) )
state = tk.DISABLED if is_running else tk.NORMAL # If a start is currently in progress (either via the controller
# path or via the SimulationControls immediate handler), keep the
# Start button disabled to avoid duplicate starts even though
# `is_simulation_running` may not yet be True.
start_in_progress_flag = False
try:
if getattr(self, "_start_in_progress_main", False):
start_in_progress_flag = True
except Exception:
start_in_progress_flag = start_in_progress_flag
try:
sc = getattr(self, "simulation_controls", None)
if sc and getattr(sc, "_start_in_progress", False):
start_in_progress_flag = True
except Exception:
pass
state = tk.DISABLED if (is_running or start_in_progress_flag) else tk.NORMAL
self.reset_radar_button.config(state=state) self.reset_radar_button.config(state=state)
self.start_button.config(state=tk.DISABLED if is_running else tk.NORMAL) self.start_button.config(state=tk.DISABLED if is_running else tk.NORMAL)
self.stop_button.config(state=tk.NORMAL if is_running else tk.DISABLED) self.stop_button.config(state=tk.NORMAL if is_running else tk.DISABLED)
# Analysis tab has its own controls; nothing to update here. # Analysis tab has its own controls; nothing to update here.
self.multiplier_combo.config( # multiplier_combo may be a non-interactive label after the
state="readonly" if not is_running else tk.DISABLED # speed-combobox removal. Attempt to set state if supported,
) # otherwise ignore errors so UI updates remain robust.
try:
if hasattr(self, "multiplier_combo") and self.multiplier_combo is not None:
try:
self.multiplier_combo.config(
state="readonly" if not is_running else tk.DISABLED
)
except Exception:
# Some widget types (e.g., ttk.Label) don't accept
# a 'state' option; ignore in that case.
pass
except Exception:
pass
self.scenario_controls.new_button.config(state=state) self.scenario_controls.new_button.config(state=state)
self.scenario_controls.save_button.config(state=state) self.scenario_controls.save_button.config(state=state)

View File

@ -1,5 +1,5 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk, messagebox
from typing import Optional from typing import Optional
@ -35,21 +35,23 @@ class SimulationControls(ttk.LabelFrame):
self.stop_button = ttk.Button(self, text="Stop Live", command=self._stop, state=tk.DISABLED) self.stop_button = ttk.Button(self, text="Stop Live", command=self._stop, state=tk.DISABLED)
self.stop_button.grid(row=0, column=1, sticky="w", padx=5, pady=5) self.stop_button.grid(row=0, column=1, sticky="w", padx=5, pady=5)
# Guard to prevent re-entrant start attempts while a start is in progress
self._start_in_progress = False
# spacer # spacer
spacer = ttk.Frame(self) spacer = ttk.Frame(self)
spacer.grid(row=0, column=3, sticky="ew") spacer.grid(row=0, column=3, sticky="ew")
# The simulation runs at real-time (1x) for live mode; show a
# non-editable indicator instead of an interactive combobox so the
# UI cannot change the runtime multiplier.
ttk.Label(self, text="Speed:").grid(row=0, column=4, sticky="e", padx=(10, 2), pady=5) ttk.Label(self, text="Speed:").grid(row=0, column=4, sticky="e", padx=(10, 2), pady=5)
self.multiplier_combo = ttk.Combobox( # Use a simple (non-interactive) label to indicate fixed 1x speed.
self, # Keep the attribute name `multiplier_combo` for backward
textvariable=self.time_multiplier_var, # compatibility with code that expects this attribute, but expose a
values=["1x", "2x", "4x", "10x", "20x"], # read-only display instead of a Combobox.
state="readonly", self.multiplier_combo = ttk.Label(self, text="1x")
width=4,
)
self.multiplier_combo.grid(row=0, column=5, sticky="w", padx=(0, 5), pady=5) self.multiplier_combo.grid(row=0, column=5, sticky="w", padx=(0, 5), pady=5)
self.multiplier_combo.bind("<<ComboboxSelected>>", getattr(main_view, "_on_time_multiplier_changed", lambda e=None: None))
ttk.Label(self, text="Update Time (s):").grid(row=0, column=6, sticky="e", padx=(10, 2), pady=5) ttk.Label(self, text="Update Time (s):").grid(row=0, column=6, sticky="e", padx=(10, 2), pady=5)
self.update_time_entry = ttk.Entry(self, textvariable=self.update_time, width=5) self.update_time_entry = ttk.Entry(self, textvariable=self.update_time, width=5)
@ -104,9 +106,59 @@ class SimulationControls(ttk.LabelFrame):
# Button handlers that delegate to main_view methods # Button handlers that delegate to main_view methods
def _start(self): def _start(self):
# 1) Verify connection before attempting to start
try: try:
connected = False
try:
if hasattr(self.main_view, "target_communicator") and self.main_view.target_communicator:
connected = bool(getattr(self.main_view.target_communicator, "is_open", False))
except Exception:
connected = False
if not connected:
# Inform the user and do NOT disable the Start button
try:
messagebox.showinfo(
"Not connected",
"Target communicator is not connected. Please connect before starting the simulation.",
parent=self.main_view,
)
except Exception:
# As a fallback, log to stdout (rare in GUI) and return
print("Target communicator is not connected. Please connect before starting the simulation.")
return
# 3) If connected, disable Start immediately to prevent duplicates
try:
self.start_button.config(state=tk.DISABLED)
# Force the UI to process this change immediately so the
# button appears disabled before any potentially blocking
# start/reset work begins in MainView.
try:
if hasattr(self.main_view, "update_idletasks"):
self.main_view.update_idletasks()
if hasattr(self.main_view, "update"):
# update() processes pending events and redraws UI
# immediately. Use cautiously but helpful here to
# avoid race where user can click again before the
# widget state is visually updated.
self.main_view.update()
except Exception:
pass
except Exception:
pass
# Delegate to MainView to start the simulation. If it fails,
# re-enable the Start button so the user can retry.
if hasattr(self.main_view, "_on_start_simulation"): if hasattr(self.main_view, "_on_start_simulation"):
self.main_view._on_start_simulation() try:
self.main_view._on_start_simulation()
except Exception:
try:
self.start_button.config(state=tk.NORMAL)
except Exception:
pass
raise
except Exception: except Exception:
raise raise

View File

@ -427,7 +427,8 @@ class SimulationController:
except Exception: except Exception:
pass pass
try: try:
main_view.after(0, lambda: main_view._update_button_states()) # Clear start-in-progress flag and update UI on main thread
main_view.after(0, lambda: (setattr(main_view, "_start_in_progress_main", False), main_view._update_button_states()))
except Exception: except Exception:
pass pass
return return
@ -454,7 +455,7 @@ class SimulationController:
except Exception: except Exception:
pass pass
try: try:
main_view.after(0, lambda: main_view._update_button_states()) main_view.after(0, lambda: (setattr(main_view, "_start_in_progress_main", False), main_view._update_button_states()))
except Exception: except Exception:
pass pass
return return
@ -476,7 +477,7 @@ class SimulationController:
except Exception: except Exception:
pass pass
try: try:
main_view.after(0, lambda: main_view._update_button_states()) main_view.after(0, lambda: (setattr(main_view, "_start_in_progress_main", False), main_view._update_button_states()))
except Exception: except Exception:
pass pass
return return
@ -510,7 +511,7 @@ class SimulationController:
except Exception: except Exception:
pass pass
try: try:
main_view.after(0, lambda: main_view._update_button_states()) main_view.after(0, lambda: (setattr(main_view, "_start_in_progress_main", False), main_view._update_button_states()))
except Exception: except Exception:
pass pass
return return
@ -537,6 +538,11 @@ class SimulationController:
except Exception: except Exception:
pass pass
main_view.is_simulation_running.set(True) main_view.is_simulation_running.set(True)
# Clear the start-in-progress flag and update UI
try:
main_view._start_in_progress_main = False
except Exception:
pass
try: try:
main_view._update_button_states() main_view._update_button_states()
except Exception: except Exception:
@ -549,6 +555,13 @@ class SimulationController:
except Exception: except Exception:
pass pass
# Mark that a start is in progress so the UI keeps Start disabled
try:
# Set synchronously so immediate callers see the flag
main_view._start_in_progress_main = True
except Exception:
pass
# UI: show status and disable controls before background work # UI: show status and disable controls before background work
try: try:
main_view.after(0, lambda: main_view.status_bar.show_status_message("Starting simulation...", timeout_ms=0)) main_view.after(0, lambda: main_view.status_bar.show_status_message("Starting simulation...", timeout_ms=0))