diff --git a/scenarios.json b/scenarios.json index e448f12..127b15b 100644 --- a/scenarios.json +++ b/scenarios.json @@ -389,7 +389,7 @@ "targets": [ { "target_id": 0, - "active": false, + "active": true, "traceable": true, "trajectory": [ { diff --git a/target_simulator/core/models.py b/target_simulator/core/models.py index 276179c..012a2e9 100644 --- a/target_simulator/core/models.py +++ b/target_simulator/core/models.py @@ -70,6 +70,7 @@ class Target: current_azimuth_deg: float = field(init=False, default=0.0) current_altitude_ft: float = field(init=False, default=0.0) current_velocity_fps: float = field(init=False, default=0.0) + current_vertical_velocity_fps: float = field(init=False, default=0.0) current_heading_deg: float = field(init=False, default=0.0) current_pitch_deg: float = field(init=False, default=0.0) _pos_x_ft: float = field(init=False, repr=False, default=0.0) @@ -123,19 +124,22 @@ class Target: math.degrees(math.atan2(dz, dist_2d)) if dist_2d > 0.1 else 0.0 ) self.current_velocity_fps = dist_3d / dt if dt > 0 else 0.0 + self.current_vertical_velocity_fps = dz / dt if dt > 0 else 0.0 else: ( self.current_heading_deg, self.current_pitch_deg, self.current_velocity_fps, - ) = (0.0, 0.0, 0.0) + self.current_vertical_velocity_fps, + ) = (0.0, 0.0, 0.0, 0.0) else: self._pos_x_ft, self._pos_y_ft, self._pos_z_ft = 0.0, 0.0, 0.0 ( self.current_velocity_fps, + self.current_vertical_velocity_fps, self.current_heading_deg, self.current_pitch_deg, - ) = (0.0, 0.0, 0.0) + ) = (0.0, 0.0, 0.0, 0.0) self._update_current_polar_coords() self.active = bool(self.trajectory) @@ -341,7 +345,8 @@ class Target: final_pos[2], final_pos[3], ) - self.current_velocity_fps = 0.0 + # Do not reset velocity to 0, keep the last computed value. + self.current_vertical_velocity_fps = 0.0 if self._sim_time_s >= self._total_duration_s: self.active = False else: @@ -370,6 +375,7 @@ class Target: ) if delta_time_s > 0: self.current_velocity_fps = dist_3d / delta_time_s + self.current_vertical_velocity_fps = dz / delta_time_s if dist_2d > 0.1: # Restore original update_state heading computation using atan2(dx, dy). try: diff --git a/target_simulator/core/simulation_engine.py b/target_simulator/core/simulation_engine.py index 8a365fe..cde13a7 100644 --- a/target_simulator/core/simulation_engine.py +++ b/target_simulator/core/simulation_engine.py @@ -155,10 +155,13 @@ class SimulationEngine(threading.Thread): if self.simulation_hub: for target in active_targets: + # Include velocities in the state tuple for the hub state_tuple = ( getattr(target, "_pos_x_ft", 0.0), getattr(target, "_pos_y_ft", 0.0), getattr(target, "_pos_z_ft", 0.0), + getattr(target, "current_velocity_fps", 0.0), + getattr(target, "current_vertical_velocity_fps", 0.0), ) self.simulation_hub.add_simulated_state( target.target_id, tick_timestamp, state_tuple diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index 52567e6..fdd67a5 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -588,9 +588,6 @@ 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() @@ -605,8 +602,11 @@ class MainView(tk.Tk): self.simulation_controls.update_ownship_display(ownship_state) else: # Ensure display is cleared if no ownship data is present + ownship_state = {} self.simulation_controls.update_ownship_display({}) - + + # Update the new active targets table, providing ownship state for context + self.simulation_controls.update_targets_table(display_data.get("simulated", []), ownship_state) if sim_is_running_now: if self.simulation_engine and self.simulation_engine.scenario: diff --git a/target_simulator/gui/ppi_adapter.py b/target_simulator/gui/ppi_adapter.py index c51e932..b748219 100644 --- a/target_simulator/gui/ppi_adapter.py +++ b/target_simulator/gui/ppi_adapter.py @@ -8,10 +8,10 @@ from target_simulator.analysis.simulation_state_hub import SimulationStateHub def build_display_data( simulation_hub: SimulationStateHub, - scenario: Optional['Scenario'] = None, - engine: Optional['SimulationEngine'] = None, - ppi_widget: Optional['PPIDisplay'] = None, - logger: Optional['Logger'] = None, + scenario: Optional["Scenario"] = None, + engine: Optional["SimulationEngine"] = None, + ppi_widget: Optional["PPIDisplay"] = None, + logger: Optional["Logger"] = None, ) -> Dict[str, List[Target]]: """ Builds PPI display data from the simulation hub, converting absolute @@ -41,12 +41,20 @@ def build_display_data( if history.get("simulated"): # Simulated data is generated relative to (0,0), so no transformation is needed. last_sim_state = history["simulated"][-1] - _ts, x_ft, y_ft, z_ft = last_sim_state - + + # Unpack the state tuple, now expecting velocities + if len(last_sim_state) >= 6: + _ts, x_ft, y_ft, z_ft, vel_fps, vert_vel_fps = last_sim_state[:6] + else: # Fallback for old data format + _ts, x_ft, y_ft, z_ft = last_sim_state + vel_fps, vert_vel_fps = 0.0, 0.0 + sim_target = Target(target_id=tid, trajectory=[]) setattr(sim_target, "_pos_x_ft", x_ft) setattr(sim_target, "_pos_y_ft", y_ft) setattr(sim_target, "_pos_z_ft", z_ft) + sim_target.current_velocity_fps = vel_fps + sim_target.current_vertical_velocity_fps = vert_vel_fps sim_target._update_current_polar_coords() # Preserve heading information from the engine/scenario if available @@ -64,7 +72,7 @@ def build_display_data( sim_target.current_heading_deg = float(heading) except Exception: pass - + # Preserve active flag sim_target.active = True if engine and getattr(engine, "scenario", None): @@ -77,14 +85,14 @@ def build_display_data( # --- Process Real Data (transforming from absolute to relative) --- if history.get("real"): last_real_state = history["real"][-1] - _ts, abs_x_ft, abs_y_ft, abs_z_ft = last_real_state - + _ts, abs_x_ft, abs_y_ft, abs_z_ft = last_real_state[:4] + rel_x_ft, rel_y_ft = abs_x_ft, abs_y_ft if ownship_pos_xy_ft: # Calculate position relative to the ownship rel_x_ft = abs_x_ft - ownship_pos_xy_ft[0] rel_y_ft = abs_y_ft - ownship_pos_xy_ft[1] - + # The z-coordinate (altitude) is typically absolute, but for display # we can treat it as relative to the ownship's altitude. ownship_alt_ft = ownship_state.get("altitude_ft", 0.0) @@ -100,7 +108,7 @@ def build_display_data( hdg = simulation_hub.get_real_heading(tid) if hdg is not None: real_target.current_heading_deg = float(hdg) % 360 - + real_target.active = True real_targets_for_ppi.append(real_target) diff --git a/target_simulator/gui/simulation_controls.py b/target_simulator/gui/simulation_controls.py index 24c2138..13db663 100644 --- a/target_simulator/gui/simulation_controls.py +++ b/target_simulator/gui/simulation_controls.py @@ -3,6 +3,7 @@ import tkinter as tk from tkinter import ttk from typing import Dict, Any, List +import math from target_simulator.core.models import FPS_TO_KNOTS, Target @@ -199,18 +200,18 @@ class SimulationControls(ttk.LabelFrame): 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") + self.targets_tree.heading("alt", text="Altitude (ft)") + self.targets_tree.heading("hdg", text="Heading (°)") + self.targets_tree.heading("gnd_speed", text="Speed (kn)") + self.targets_tree.heading("vert_speed", text="Vert. Speed (ft/s)") # Define column properties - self.targets_tree.column("id", width=40, anchor=tk.CENTER, stretch=False) + self.targets_tree.column("id", width=30, 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("alt", width=80, 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("gnd_speed", width=80, anchor=tk.E) self.targets_tree.column("vert_speed", width=100, anchor=tk.E) # Layout Treeview and Scrollbar @@ -270,37 +271,57 @@ class SimulationControls(ttk.LabelFrame): 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) 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 + def update_targets_table(self, targets: List[Target], ownship_state: Dict[str, Any]): + """Clears and repopulates the active targets table with geographic data.""" for item in self.targets_tree.get_children(): self.targets_tree.delete(item) - # Insert new items + # Get ownship data needed for conversion + own_lat = ownship_state.get("latitude") + own_lon = ownship_state.get("longitude") + own_pos_xy_ft = ownship_state.get("position_xy_ft") + 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 + lat_str = "N/A" + lon_str = "N/A" - alt_str = f"{target.current_altitude_ft:.1f} ft" - hdg_str = f"{target.current_heading_deg:.2f}°" + # Calculate geographic position if possible + if own_lat is not None and own_lon is not None and own_pos_xy_ft: + target_x_ft = getattr(target, "_pos_x_ft", 0.0) + target_y_ft = getattr(target, "_pos_y_ft", 0.0) + own_x_ft, own_y_ft = own_pos_xy_ft - # Assuming ground speed is the 2D velocity for simplicity + # Delta from ownship's current position in meters + delta_east_m = (target_x_ft - own_x_ft) * 0.3048 + delta_north_m = (target_y_ft - own_y_ft) * 0.3048 + + # Equirectangular approximation for lat/lon calculation + earth_radius_m = 6378137.0 + dlat = (delta_north_m / earth_radius_m) * (180.0 / math.pi) + dlon = (delta_east_m / (earth_radius_m * math.cos(math.radians(own_lat)))) * (180.0 / math.pi) + + target_lat = own_lat + dlat + target_lon = own_lon + dlon + + lat_str = f"{abs(target_lat):.5f}° {'N' if target_lat >= 0 else 'S'}" + lon_str = f"{abs(target_lon):.5f}° {'E' if target_lon >= 0 else 'W'}" + + alt_str = f"{target.current_altitude_ft:.1f}" + hdg_str = f"{target.current_heading_deg:.2f}" + + # Use the now-correct velocity values from the Target object gnd_speed_kn = target.current_velocity_fps * FPS_TO_KNOTS - gnd_speed_str = f"{gnd_speed_kn:.1f} kn" + gnd_speed_str = f"{gnd_speed_kn:.1f}" + + vert_speed_fps = target.current_vertical_velocity_fps + vert_speed_str = f"{vert_speed_fps:+.1f}" - # Vertical speed is not directly in the simple Target model - vert_speed_str = "N/A" values = ( target.target_id, diff --git a/todo.md b/todo.md index d441b14..2477422 100644 --- a/todo.md +++ b/todo.md @@ -15,8 +15,12 @@ - [ ] 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 +- [ ] funzione di sincronizzazione: è stato aggiunto al server la possibilità di gestire dei messaggi che sono di tipo SY (tag) che sono fatti per gestire il sincronismo tra client e server. In questa nuova tipologia di messaggi io invio un mio timetag che poi il server mi restituirà subito appena lo riceve, facendo così sappiamo in quanto tempo il messaggio che spedisco è arrivato al server, viene letto, e viene risposto il mio numero con anche il timetag del server. Facendo così misurando i delta posso scroprire esattamente il tempo che intercorre tra inviare un messaggio al server e ricevere una risposta. Per come è fatto il server il tempo di applicazione dei nuovi valori per i target sarà al massimo di 1 batch, che può essere variabile, ma a quel punto lo potremmo calibrare in altro modo. Con l'analisi sui sync possiamo sapere come allineare gli orologi. + # 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 +- [ ] rivedere la visualizzazione della combobox per scegliere lo scenario da usare. +- [ ] quando è finita la simulazione i target nella tabella si devono fermare all'ultima posizione scambiata. +- [ ] quando la traiettoria si ferma deve comparire la x gialla e non deve sparire a fine simulazione \ No newline at end of file