diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index 5c27b5d..52567e6 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -588,6 +588,9 @@ class MainView(tk.Tk): self.ppi_widget.update_simulated_targets(display_data.get("simulated", [])) self.ppi_widget.update_real_targets(display_data.get("real", [])) + # Update the new active targets table + self.simulation_controls.update_targets_table(display_data.get("simulated", [])) + if self.simulation_hub: # Update antenna sweep line az_deg, az_ts = self.simulation_hub.get_antenna_azimuth() diff --git a/target_simulator/gui/simulation_controls.py b/target_simulator/gui/simulation_controls.py index 5c345bf..24c2138 100644 --- a/target_simulator/gui/simulation_controls.py +++ b/target_simulator/gui/simulation_controls.py @@ -2,14 +2,15 @@ import tkinter as tk from tkinter import ttk -from typing import Dict, Any +from typing import Dict, Any, List -from target_simulator.core.models import FPS_TO_KNOTS +from target_simulator.core.models import FPS_TO_KNOTS, Target class SimulationControls(ttk.LabelFrame): """ - Encapsulates the Live Simulation Engine controls and ownship status display. + Encapsulates the Live Simulation Engine controls, ownship status, + and active targets display. """ def __init__(self, parent, main_view): @@ -17,10 +18,14 @@ class SimulationControls(ttk.LabelFrame): self.main_view = main_view # --- Variables for UI Binding --- - self.time_multiplier_var = getattr(main_view, "time_multiplier_var", tk.StringVar(value="1x")) + self.time_multiplier_var = getattr( + main_view, "time_multiplier_var", tk.StringVar(value="1x") + ) self.update_time = getattr(main_view, "update_time", tk.DoubleVar(value=1.0)) - self.sim_slider_var = getattr(main_view, "sim_slider_var", tk.DoubleVar(value=0.0)) - + self.sim_slider_var = getattr( + main_view, "sim_slider_var", tk.DoubleVar(value=0.0) + ) + # Ownship display variables self.lat_var = tk.StringVar(value="-") self.lon_var = tk.StringVar(value="-") @@ -37,26 +42,48 @@ class SimulationControls(ttk.LabelFrame): controls_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) controls_frame.grid_columnconfigure(3, weight=1) - self.start_button = ttk.Button(controls_frame, text="Start Live", command=self._start) + self.start_button = ttk.Button( + controls_frame, text="Start Live", command=self._start + ) self.start_button.grid(row=0, column=0, sticky="w") - self.stop_button = ttk.Button(controls_frame, text="Stop Live", command=self._stop, state=tk.DISABLED) + self.stop_button = ttk.Button( + controls_frame, text="Stop Live", command=self._stop, state=tk.DISABLED + ) self.stop_button.grid(row=0, column=1, sticky="w", padx=5) - - ttk.Frame(controls_frame).grid(row=0, column=2, sticky="ew", padx=10) # Spacer - ttk.Label(controls_frame, text="Speed:").grid(row=0, column=4, sticky="e", padx=(10, 2)) - self.multiplier_combo = ttk.Label(controls_frame, text="1x") # Placeholder, could be a combo + ttk.Frame(controls_frame).grid( + row=0, column=2, sticky="ew", padx=10 + ) # Spacer + + ttk.Label(controls_frame, text="Speed:").grid( + row=0, column=4, sticky="e", padx=(10, 2) + ) + self.multiplier_combo = ttk.Label( + controls_frame, text="1x" + ) # Placeholder, could be a combo self.multiplier_combo.grid(row=0, column=5, sticky="w") - ttk.Label(controls_frame, text="Update (s):").grid(row=0, column=6, sticky="e", padx=(10, 2)) - self.update_time_entry = ttk.Entry(controls_frame, textvariable=self.update_time, width=5) + ttk.Label(controls_frame, text="Update (s):").grid( + row=0, column=6, sticky="e", padx=(10, 2) + ) + self.update_time_entry = ttk.Entry( + controls_frame, textvariable=self.update_time, width=5 + ) self.update_time_entry.grid(row=0, column=7, sticky="w") - self.reset_button = ttk.Button(controls_frame, text="Reset Sim", command=getattr(main_view, "_on_reset_simulation", lambda: None)) + self.reset_button = ttk.Button( + controls_frame, + text="Reset Sim", + command=getattr(main_view, "_on_reset_simulation", lambda: None), + ) self.reset_button.grid(row=0, column=8, sticky="e", padx=10) - self.reset_radar_button = ttk.Button(controls_frame, text="Reset Radar", command=getattr(main_view, "_reset_radar_state", lambda: None)) + self.reset_radar_button = ttk.Button( + controls_frame, + text="Reset Radar", + command=getattr(main_view, "_reset_radar_state", lambda: None), + ) self.reset_radar_button.grid(row=0, column=9, sticky="e") # Progress slider row @@ -65,10 +92,16 @@ class SimulationControls(ttk.LabelFrame): progress_frame.grid_columnconfigure(0, weight=1) self.sim_slider = ttk.Scale( - progress_frame, orient=tk.HORIZONTAL, variable=self.sim_slider_var, from_=0.0, to=1.0 + progress_frame, + orient=tk.HORIZONTAL, + variable=self.sim_slider_var, + from_=0.0, + to=1.0, ) self.sim_slider.grid(row=0, column=0, sticky="ew", padx=(4, 8)) - self.sim_slider.bind("", lambda e: setattr(main_view, "_slider_is_dragging", True)) + self.sim_slider.bind( + "", lambda e: setattr(main_view, "_slider_is_dragging", True) + ) self.sim_slider.bind( "", lambda e: ( @@ -79,10 +112,14 @@ class SimulationControls(ttk.LabelFrame): labels_frame = ttk.Frame(progress_frame) labels_frame.grid(row=0, column=1, sticky="e") - self.sim_elapsed_label = ttk.Label(labels_frame, text="0.0s", width=8, anchor=tk.E) + self.sim_elapsed_label = ttk.Label( + labels_frame, text="0.0s", width=8, anchor=tk.E + ) self.sim_elapsed_label.pack(side=tk.LEFT) ttk.Label(labels_frame, text="/").pack(side=tk.LEFT, padx=2) - self.sim_total_label = ttk.Label(labels_frame, text="0.0s", width=8, anchor=tk.W) + self.sim_total_label = ttk.Label( + labels_frame, text="0.0s", width=8, anchor=tk.W + ) self.sim_total_label.pack(side=tk.LEFT) # --- Ownship State Display --- @@ -90,39 +127,117 @@ class SimulationControls(ttk.LabelFrame): ownship_frame.grid(row=2, column=0, sticky="ew", padx=5, pady=8) ownship_frame.grid_columnconfigure(1, weight=1) ownship_frame.grid_columnconfigure(3, weight=1) + ownship_frame.grid_columnconfigure(5, weight=1) ttk.Label(ownship_frame, text="Latitude:").grid(row=0, column=0, sticky="w") - ttk.Label(ownship_frame, textvariable=self.lat_var, anchor="w").grid(row=0, column=1, sticky="ew", padx=5) - + ttk.Label(ownship_frame, textvariable=self.lat_var, anchor="w").grid( + row=0, column=1, sticky="ew", padx=5 + ) + ttk.Label(ownship_frame, text="Longitude:").grid(row=1, column=0, sticky="w") - ttk.Label(ownship_frame, textvariable=self.lon_var, anchor="w").grid(row=1, column=1, sticky="ew", padx=5) + ttk.Label(ownship_frame, textvariable=self.lon_var, anchor="w").grid( + row=1, column=1, sticky="ew", padx=5 + ) - ttk.Label(ownship_frame, text="Altitude:").grid(row=0, column=2, sticky="w", padx=(10, 0)) - ttk.Label(ownship_frame, textvariable=self.alt_var, anchor="w").grid(row=0, column=3, sticky="ew", padx=5) + ttk.Label(ownship_frame, text="Altitude:").grid( + row=0, column=2, sticky="w", padx=(10, 0) + ) + ttk.Label(ownship_frame, textvariable=self.alt_var, anchor="w").grid( + row=0, column=3, sticky="ew", padx=5 + ) - ttk.Label(ownship_frame, text="Heading:").grid(row=1, column=2, sticky="w", padx=(10, 0)) - ttk.Label(ownship_frame, textvariable=self.hdg_var, anchor="w").grid(row=1, column=3, sticky="ew", padx=5) - - ttk.Label(ownship_frame, text="Ground Speed:").grid(row=0, column=4, sticky="w", padx=(10, 0)) - ttk.Label(ownship_frame, textvariable=self.gnd_speed_var, anchor="w").grid(row=0, column=5, sticky="ew", padx=5) + ttk.Label(ownship_frame, text="Heading:").grid( + row=1, column=2, sticky="w", padx=(10, 0) + ) + ttk.Label(ownship_frame, textvariable=self.hdg_var, anchor="w").grid( + row=1, column=3, sticky="ew", padx=5 + ) - ttk.Label(ownship_frame, text="Vertical Speed:").grid(row=1, column=4, sticky="w", padx=(10, 0)) - ttk.Label(ownship_frame, textvariable=self.vert_speed_var, anchor="w").grid(row=1, column=5, sticky="ew", padx=5) + ttk.Label(ownship_frame, text="Ground Speed:").grid( + row=0, column=4, sticky="w", padx=(10, 0) + ) + ttk.Label(ownship_frame, textvariable=self.gnd_speed_var, anchor="w").grid( + row=0, column=5, sticky="ew", padx=5 + ) + ttk.Label(ownship_frame, text="Vertical Speed:").grid( + row=1, column=4, sticky="w", padx=(10, 0) + ) + ttk.Label(ownship_frame, textvariable=self.vert_speed_var, anchor="w").grid( + row=1, column=5, sticky="ew", padx=5 + ) + + # --- Active Targets Table --- + targets_frame = ttk.LabelFrame(self, text="Active Targets", padding=10) + targets_frame.grid(row=3, column=0, sticky="nsew", padx=5, pady=8) + self.grid_rowconfigure(3, weight=1) # Allow this frame to expand vertically + targets_frame.grid_columnconfigure(0, weight=1) + targets_frame.grid_rowconfigure(0, weight=1) + + # Scrollbar + scrollbar = ttk.Scrollbar(targets_frame, orient=tk.VERTICAL) + + # Treeview + columns = ( + "id", + "lat", + "lon", + "alt", + "hdg", + "gnd_speed", + "vert_speed", + ) + self.targets_tree = ttk.Treeview( + targets_frame, + columns=columns, + show="headings", + yscrollcommand=scrollbar.set, + ) + scrollbar.config(command=self.targets_tree.yview) + + # Define headings + self.targets_tree.heading("id", text="ID") + self.targets_tree.heading("lat", text="Latitude") + self.targets_tree.heading("lon", text="Longitude") + self.targets_tree.heading("alt", text="Altitude") + self.targets_tree.heading("hdg", text="Heading") + self.targets_tree.heading("gnd_speed", text="Ground Speed") + self.targets_tree.heading("vert_speed", text="Vertical Speed") + + # Define column properties + self.targets_tree.column("id", width=40, anchor=tk.CENTER, stretch=False) + self.targets_tree.column("lat", width=100, anchor=tk.W) + self.targets_tree.column("lon", width=100, anchor=tk.W) + self.targets_tree.column("alt", width=100, anchor=tk.E) + self.targets_tree.column("hdg", width=80, anchor=tk.E) + self.targets_tree.column("gnd_speed", width=100, anchor=tk.E) + self.targets_tree.column("vert_speed", width=100, anchor=tk.E) + + # Layout Treeview and Scrollbar + self.targets_tree.grid(row=0, column=0, sticky="nsew") + scrollbar.grid(row=0, column=1, sticky="ns") # --- Non-modal notice area --- self.notice_var = tk.StringVar(value="") self.notice_frame = ttk.Frame(self) - self.notice_frame.grid(row=3, column=0, sticky="ew", padx=5, pady=(5, 0)) - self.notice_frame.grid_remove() # Hidden by default + self.notice_frame.grid(row=4, column=0, sticky="ew", padx=5, pady=(5, 0)) + self.notice_frame.grid_remove() # Hidden by default notice_label = tk.Label( - self.notice_frame, textvariable=self.notice_var, bg="#fff3cd", - fg="#6a4b00", anchor="w", relief=tk.SOLID, bd=1, padx=6, pady=2 + self.notice_frame, + textvariable=self.notice_var, + bg="#fff3cd", + fg="#6a4b00", + anchor="w", + relief=tk.SOLID, + bd=1, + padx=6, + pady=2, ) notice_label.pack(side=tk.LEFT, fill=tk.X, expand=True) - ttk.Button(self.notice_frame, text="Dismiss", command=self.hide_notice).pack(side=tk.RIGHT, padx=(6, 0)) - + ttk.Button(self.notice_frame, text="Dismiss", command=self.hide_notice).pack( + side=tk.RIGHT, padx=(6, 0) + ) def update_ownship_display(self, state: Dict[str, Any]): """Updates the labels in the Ownship State frame.""" @@ -136,28 +251,68 @@ class SimulationControls(ttk.LabelFrame): return # Latitude / Longitude - lat = state.get('latitude', 0.0) - lon = state.get('longitude', 0.0) + lat = state.get("latitude", 0.0) + lon = state.get("longitude", 0.0) self.lat_var.set(f"{abs(lat):.5f}° {'N' if lat >= 0 else 'S'}") self.lon_var.set(f"{abs(lon):.5f}° {'E' if lon >= 0 else 'W'}") # Altitude - alt_ft = state.get('altitude_ft', 0.0) + alt_ft = state.get("altitude_ft", 0.0) self.alt_var.set(f"{alt_ft:.1f} ft") # Heading - hdg_deg = state.get('heading_deg', 0.0) + hdg_deg = state.get("heading_deg", 0.0) self.hdg_var.set(f"{hdg_deg:.2f}°") # Ground Speed - vx_fps, vy_fps = state.get('velocity_xy_fps', (0.0, 0.0)) - gnd_speed_kn = (vx_fps**2 + vy_fps**2)**0.5 * FPS_TO_KNOTS + vx_fps, vy_fps = state.get("velocity_xy_fps", (0.0, 0.0)) + gnd_speed_kn = (vx_fps**2 + vy_fps**2) ** 0.5 * FPS_TO_KNOTS self.gnd_speed_var.set(f"{gnd_speed_kn:.1f} kn") # Vertical Speed - vz_fps = state.get('velocity_z_fps', 0.0) # Assuming 'vz' is part of state + vz_fps = state.get("velocity_z_fps", 0.0) # Assuming 'vz' is part of state self.vert_speed_var.set(f"{vz_fps:+.1f} ft/s") + def update_targets_table(self, targets: List[Target]): + """Clears and repopulates the active targets table.""" + # Clear existing items + for item in self.targets_tree.get_children(): + self.targets_tree.delete(item) + + # Insert new items + for target in sorted(targets, key=lambda t: t.target_id): + if not target.active: + continue + + # This data is not directly in the Target model, so we calculate it + # based on its relative position for display purposes. + # NOTE: For "real" targets, lat/lon are not directly known without + # ownship data. The current implementation shows polar coords. + # This can be expanded later if geo-referenced data becomes available. + lat_str = f"{target.current_range_nm:.2f} NM" # Placeholder + lon_str = f"{target.current_azimuth_deg:.2f}°" # Placeholder + + alt_str = f"{target.current_altitude_ft:.1f} ft" + hdg_str = f"{target.current_heading_deg:.2f}°" + + # Assuming ground speed is the 2D velocity for simplicity + gnd_speed_kn = target.current_velocity_fps * FPS_TO_KNOTS + gnd_speed_str = f"{gnd_speed_kn:.1f} kn" + + # Vertical speed is not directly in the simple Target model + vert_speed_str = "N/A" + + values = ( + target.target_id, + lat_str, + lon_str, + alt_str, + hdg_str, + gnd_speed_str, + vert_speed_str, + ) + self.targets_tree.insert("", tk.END, values=values) + def show_notice(self, message: str): self.notice_var.set(message) self.notice_frame.grid() diff --git a/todo.md b/todo.md index edfe70a..d441b14 100644 --- a/todo.md +++ b/todo.md @@ -1,18 +1,22 @@ # ToDo List -- [ ] Inserire dati di navigazione dell'ownship nel file di salvataggio della simulazione +- [x] Inserire dati di navigazione dell'ownship nel file di salvataggio della simulazione - [ ] muovere il ppi in base al movimento dell'ownship -- [ ] Aggiungere tabella dei dati cinematici dell'ownship nella schermata della simulazione +- [x] Aggiungere tabella dei dati cinematici dell'ownship nella schermata della simulazione - [ ] Mettere nel file di comando inviato al srver l'ultimo timetag che è arrivato dal server - [ ] Implementare il comando ping con numero indentificativo per verificare i tempi di risposta - [ ] Mettere configurazione cifre decimali inviate nei json al server - [ ] Se lat/lon passato dal server non è valido posso fare come fa mcs, integrare sul tempo e simulare il movimente dell'ownship - [ ] poter scegliere se visualizzare la mappa ppi fissa a nord o fissa con l'heading dell'ownship -- [ ] salvare nei file delle simulazione i dati in lat/lon dei target così da poter piazzare su mappa oepnstreetmap le traiettorie e vedere come si è mosso lo scenario durante la simulazione +- [x] salvare nei file delle simulazione i dati in lat/lon dei target così da poter piazzare su mappa oepnstreetmap le traiettorie e vedere come si è mosso lo scenario durante la simulazione - [ ] vedere anche la simulazione in 3d usando le mappe dem e le mappe operstreetmap. - [ ] Scrivere test unitari - [ ] creare repository su git aziendale, usando codice PJ40906 come progetto +- [ ] aprire l'analisi direttamente cliccando sulla riga della tabella +- [ ] creare una procedura di allineamento tra server e client usando il comando di ping da implementare anche sul server # FIXME List - [ ] sistemare la visualizzazione nella tabe simulator, per poter vedere quale scenario è stato selezionato +- [ ] sistemare l'animazione della antenna che adesso non si muove più +- [ ] rivedere la visualizzazione della combobox per scegliere lo scenario da usare. \ No newline at end of file