siistemata la visualizzazione della tabella dei target durante la simulazione

This commit is contained in:
VALLONGOL 2025-11-05 13:59:00 +01:00
parent e08466c77f
commit 0c347be5c8
7 changed files with 87 additions and 45 deletions

View File

@ -389,7 +389,7 @@
"targets": [ "targets": [
{ {
"target_id": 0, "target_id": 0,
"active": false, "active": true,
"traceable": true, "traceable": true,
"trajectory": [ "trajectory": [
{ {

View File

@ -70,6 +70,7 @@ class Target:
current_azimuth_deg: float = field(init=False, default=0.0) current_azimuth_deg: float = field(init=False, default=0.0)
current_altitude_ft: 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_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_heading_deg: float = field(init=False, default=0.0)
current_pitch_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) _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 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_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: else:
( (
self.current_heading_deg, self.current_heading_deg,
self.current_pitch_deg, self.current_pitch_deg,
self.current_velocity_fps, self.current_velocity_fps,
) = (0.0, 0.0, 0.0) self.current_vertical_velocity_fps,
) = (0.0, 0.0, 0.0, 0.0)
else: else:
self._pos_x_ft, self._pos_y_ft, self._pos_z_ft = 0.0, 0.0, 0.0 self._pos_x_ft, self._pos_y_ft, self._pos_z_ft = 0.0, 0.0, 0.0
( (
self.current_velocity_fps, self.current_velocity_fps,
self.current_vertical_velocity_fps,
self.current_heading_deg, self.current_heading_deg,
self.current_pitch_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._update_current_polar_coords()
self.active = bool(self.trajectory) self.active = bool(self.trajectory)
@ -341,7 +345,8 @@ class Target:
final_pos[2], final_pos[2],
final_pos[3], 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: if self._sim_time_s >= self._total_duration_s:
self.active = False self.active = False
else: else:
@ -370,6 +375,7 @@ class Target:
) )
if delta_time_s > 0: if delta_time_s > 0:
self.current_velocity_fps = dist_3d / delta_time_s self.current_velocity_fps = dist_3d / delta_time_s
self.current_vertical_velocity_fps = dz / delta_time_s
if dist_2d > 0.1: if dist_2d > 0.1:
# Restore original update_state heading computation using atan2(dx, dy). # Restore original update_state heading computation using atan2(dx, dy).
try: try:

View File

@ -155,10 +155,13 @@ class SimulationEngine(threading.Thread):
if self.simulation_hub: if self.simulation_hub:
for target in active_targets: for target in active_targets:
# Include velocities in the state tuple for the hub
state_tuple = ( state_tuple = (
getattr(target, "_pos_x_ft", 0.0), getattr(target, "_pos_x_ft", 0.0),
getattr(target, "_pos_y_ft", 0.0), getattr(target, "_pos_y_ft", 0.0),
getattr(target, "_pos_z_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( self.simulation_hub.add_simulated_state(
target.target_id, tick_timestamp, state_tuple target.target_id, tick_timestamp, state_tuple

View File

@ -588,9 +588,6 @@ class MainView(tk.Tk):
self.ppi_widget.update_simulated_targets(display_data.get("simulated", [])) self.ppi_widget.update_simulated_targets(display_data.get("simulated", []))
self.ppi_widget.update_real_targets(display_data.get("real", [])) 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: if self.simulation_hub:
# Update antenna sweep line # Update antenna sweep line
az_deg, az_ts = self.simulation_hub.get_antenna_azimuth() 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) self.simulation_controls.update_ownship_display(ownship_state)
else: else:
# Ensure display is cleared if no ownship data is present # Ensure display is cleared if no ownship data is present
ownship_state = {}
self.simulation_controls.update_ownship_display({}) 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 sim_is_running_now:
if self.simulation_engine and self.simulation_engine.scenario: if self.simulation_engine and self.simulation_engine.scenario:

View File

@ -8,10 +8,10 @@ from target_simulator.analysis.simulation_state_hub import SimulationStateHub
def build_display_data( def build_display_data(
simulation_hub: SimulationStateHub, simulation_hub: SimulationStateHub,
scenario: Optional['Scenario'] = None, scenario: Optional["Scenario"] = None,
engine: Optional['SimulationEngine'] = None, engine: Optional["SimulationEngine"] = None,
ppi_widget: Optional['PPIDisplay'] = None, ppi_widget: Optional["PPIDisplay"] = None,
logger: Optional['Logger'] = None, logger: Optional["Logger"] = None,
) -> Dict[str, List[Target]]: ) -> Dict[str, List[Target]]:
""" """
Builds PPI display data from the simulation hub, converting absolute Builds PPI display data from the simulation hub, converting absolute
@ -41,12 +41,20 @@ def build_display_data(
if history.get("simulated"): if history.get("simulated"):
# Simulated data is generated relative to (0,0), so no transformation is needed. # Simulated data is generated relative to (0,0), so no transformation is needed.
last_sim_state = history["simulated"][-1] last_sim_state = history["simulated"][-1]
# 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 _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=[]) sim_target = Target(target_id=tid, trajectory=[])
setattr(sim_target, "_pos_x_ft", x_ft) setattr(sim_target, "_pos_x_ft", x_ft)
setattr(sim_target, "_pos_y_ft", y_ft) setattr(sim_target, "_pos_y_ft", y_ft)
setattr(sim_target, "_pos_z_ft", z_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() sim_target._update_current_polar_coords()
# Preserve heading information from the engine/scenario if available # Preserve heading information from the engine/scenario if available
@ -77,7 +85,7 @@ def build_display_data(
# --- Process Real Data (transforming from absolute to relative) --- # --- Process Real Data (transforming from absolute to relative) ---
if history.get("real"): if history.get("real"):
last_real_state = history["real"][-1] 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 rel_x_ft, rel_y_ft = abs_x_ft, abs_y_ft
if ownship_pos_xy_ft: if ownship_pos_xy_ft:

View File

@ -3,6 +3,7 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import Dict, Any, List from typing import Dict, Any, List
import math
from target_simulator.core.models import FPS_TO_KNOTS, Target 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("id", text="ID")
self.targets_tree.heading("lat", text="Latitude") self.targets_tree.heading("lat", text="Latitude")
self.targets_tree.heading("lon", text="Longitude") self.targets_tree.heading("lon", text="Longitude")
self.targets_tree.heading("alt", text="Altitude") self.targets_tree.heading("alt", text="Altitude (ft)")
self.targets_tree.heading("hdg", text="Heading") self.targets_tree.heading("hdg", text="Heading (°)")
self.targets_tree.heading("gnd_speed", text="Ground Speed") self.targets_tree.heading("gnd_speed", text="Speed (kn)")
self.targets_tree.heading("vert_speed", text="Vertical Speed") self.targets_tree.heading("vert_speed", text="Vert. Speed (ft/s)")
# Define column properties # 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("lat", width=100, anchor=tk.W)
self.targets_tree.column("lon", 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("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) self.targets_tree.column("vert_speed", width=100, anchor=tk.E)
# Layout Treeview and Scrollbar # Layout Treeview and Scrollbar
@ -270,37 +271,57 @@ class SimulationControls(ttk.LabelFrame):
self.gnd_speed_var.set(f"{gnd_speed_kn:.1f} kn") self.gnd_speed_var.set(f"{gnd_speed_kn:.1f} kn")
# Vertical Speed # 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") self.vert_speed_var.set(f"{vz_fps:+.1f} ft/s")
def update_targets_table(self, targets: List[Target]): def update_targets_table(self, targets: List[Target], ownship_state: Dict[str, Any]):
"""Clears and repopulates the active targets table.""" """Clears and repopulates the active targets table with geographic data."""
# Clear existing items
for item in self.targets_tree.get_children(): for item in self.targets_tree.get_children():
self.targets_tree.delete(item) 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): for target in sorted(targets, key=lambda t: t.target_id):
if not target.active: if not target.active:
continue continue
# This data is not directly in the Target model, so we calculate it lat_str = "N/A"
# based on its relative position for display purposes. lon_str = "N/A"
# 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" # Calculate geographic position if possible
hdg_str = f"{target.current_heading_deg:.2f}°" 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_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 = ( values = (
target.target_id, target.target_id,

View File

@ -15,8 +15,12 @@
- [ ] aprire l'analisi direttamente cliccando sulla riga della tabella - [ ] 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 - [ ] 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 # FIXME List
- [ ] sistemare la visualizzazione nella tabe simulator, per poter vedere quale scenario è stato selezionato - [ ] sistemare la visualizzazione nella tabe simulator, per poter vedere quale scenario è stato selezionato
- [ ] sistemare l'animazione della antenna che adesso non si muove più - [ ] sistemare l'animazione della antenna che adesso non si muove più
- [ ] rivedere la visualizzazione della combobox per scegliere lo scenario da usare. - [ ] 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