sistemate alcuni problemi di aggiornamento.

This commit is contained in:
VALLONGOL 2025-10-27 15:45:04 +01:00
parent 747ec3b40f
commit 23bd5ee6dc
6 changed files with 160 additions and 74 deletions

View File

@ -162,6 +162,107 @@
"use_spline": false "use_spline": false
} }
] ]
},
"scenario3": {
"name": "scenario3",
"targets": [
{
"target_id": 0,
"active": true,
"traceable": true,
"trajectory": [
{
"maneuver_type": "Fly to Point",
"duration_s": 10.0,
"target_range_nm": 10.0,
"target_azimuth_deg": 0.0,
"target_altitude_ft": 10000.0,
"target_velocity_fps": 506.343,
"target_heading_deg": 180.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": 40.0,
"target_range_nm": 20.0,
"target_azimuth_deg": 0.0,
"target_altitude_ft": 10000.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": 40.0,
"target_range_nm": 20.0,
"target_azimuth_deg": -90.0,
"target_altitude_ft": 10000.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": 40.0,
"target_range_nm": 30.0,
"target_azimuth_deg": -30.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": true
},
{
"target_id": 1,
"active": true,
"traceable": true,
"trajectory": [
{
"maneuver_type": "Fly to Point",
"duration_s": 10.0,
"target_range_nm": 10.0,
"target_azimuth_deg": 25.0,
"target_altitude_ft": 10000.0,
"target_velocity_fps": 506.343,
"target_heading_deg": 180.0,
"longitudinal_acceleration_g": 0.0,
"lateral_acceleration_g": 0.0,
"vertical_acceleration_g": 0.0,
"turn_direction": "Right"
},
{
"maneuver_type": "Fly for Duration",
"duration_s": 50.0,
"target_altitude_ft": 10000.0,
"target_velocity_fps": 1519.029,
"target_heading_deg": 45.0,
"longitudinal_acceleration_g": 0.0,
"lateral_acceleration_g": 0.0,
"vertical_acceleration_g": 0.0,
"turn_direction": "Right"
},
{
"maneuver_type": "Fly for Duration",
"duration_s": 80.0,
"target_altitude_ft": 10000.0,
"target_velocity_fps": 1181.467,
"target_heading_deg": -30.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": "1599x1024+501+84", "geometry": "1599x1024+501+84",
"last_selected_scenario": "scenario1", "last_selected_scenario": "scenario3",
"connection": { "connection": {
"target": { "target": {
"type": "sfp", "type": "sfp",

View File

@ -165,6 +165,20 @@ class SimulationStateHub:
with self._lock: with self._lock:
return list(self._target_data.keys()) return list(self._target_data.keys())
def has_active_real_targets(self) -> bool:
"""
Checks if there is any real target data currently stored in the hub.
Returns:
True if at least one target has a non-empty 'real' data history,
False otherwise.
"""
with self._lock:
for target_info in self._target_data.values():
if target_info.get("real"): # Check if the 'real' deque is not empty
return True
return False
def reset(self): def reset(self):
"""Clears all stored data for all targets.""" """Clears all stored data for all targets."""
with self._lock: with self._lock:

View File

@ -139,18 +139,29 @@ class SFPCommunicator(CommunicatorInterface):
self.logger.info(f"Sending scenario '{scenario.name}' via SFP...") self.logger.info(f"Sending scenario '{scenario.name}' via SFP...")
if not self._send_single_command(command_builder.build_pause()): # Build a list of commands to be sent atomically
return False commands = [
command_builder.build_pause(),
command_builder.build_aclatch() # Add aclatch for atomic update
]
for target in scenario.get_all_targets(): for target in scenario.get_all_targets():
cmd = command_builder.build_tgtinit(target) commands.append(command_builder.build_tgtinit(target))
if not self._send_single_command(cmd):
self.logger.error(f"Failed to send init for target {target.target_id}")
return False
time.sleep(0.01)
if not self._send_single_command(command_builder.build_continue()): commands.extend([
command_builder.build_acunlatch(), # Add acunlatch to apply changes
command_builder.build_continue()
])
# Send commands with a small delay between each to prevent packet loss
for cmd in commands:
if not self._send_single_command(cmd):
self.logger.error(f"Failed to send command '{cmd}'. Aborting scenario send.")
# Attempt to leave the server in a good state
self._send_single_command(command_builder.build_acunlatch())
self._send_single_command(command_builder.build_continue())
return False return False
time.sleep(0.05) # Use a safer 50ms delay between commands
self.logger.info("Finished sending scenario via SFP.") self.logger.info("Finished sending scenario via SFP.")
return True return True

View File

@ -589,54 +589,40 @@ class MainView(tk.Tk):
self.logger.info("Sending reset commands to deactivate all radar targets...") self.logger.info("Sending reset commands to deactivate all radar targets...")
# Prefer an atomic reset command to deactivate all targets on the server # Build the atomic reset command.
# instead of sending many individual tgtinit commands which is slow and
# can cause dropped messages. The server understands 'tgtset /-s' which
# clears all targets atomically.
# Build the atomic reset command. Use command_builder.tgtset if available,
# otherwise build the raw command string.
try: try:
# Some command_builder implementations may provide build_tgtset; use it if present
if hasattr(command_builder, "build_tgtset"):
reset_command = command_builder.build_tgtset("/-s")
else:
# Fallback: raw command string
reset_command = "tgtset /-s" reset_command = "tgtset /-s"
except Exception: except Exception:
# In case command_builder raises for unexpected inputs, fallback to raw
self.logger.exception("Error while building atomic reset command; falling back to raw string.") self.logger.exception("Error while building atomic reset command; falling back to raw string.")
reset_command = "tgtset /-s" reset_command = "tgtset /-s"
# Some radar servers require adjusting internal parameters to accept # Some radar servers require adjusting internal parameters.
# large multi-target operations. Send a preparatory command to set
# the server t_rows parameter before issuing the atomic tgtset reset.
prep_command = "$mex.t_rows=80" prep_command = "$mex.t_rows=80"
commands_to_send = [prep_command, reset_command] commands_to_send = [prep_command, reset_command]
# Send the preparatory command followed by the atomic reset using the
# communicator's send_commands API which accepts a list of commands.
if not self.target_communicator.send_commands(commands_to_send): if not self.target_communicator.send_commands(commands_to_send):
self.logger.error("Failed to send preparatory/reset commands to the radar.") self.logger.error("Failed to send preparatory/reset commands to the radar.")
messagebox.showerror("Reset Error", "Failed to send reset command to the radar.") messagebox.showerror("Reset Error", "Failed to send reset command to the radar.")
return False return False
self.logger.info("Successfully sent preparatory and atomic reset commands: %s", commands_to_send) self.logger.info("Successfully sent preparatory and atomic reset commands: %s", commands_to_send)
# Poll the simulation hub for up to a short timeout to ensure the server
# processed the reset and returned no active targets. # 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 timeout_s = 3.0
poll_interval = 0.2 poll_interval = 0.2
waited = 0.0 waited = 0.0
while waited < timeout_s: while waited < timeout_s:
# If there are no real target entries in the hub, assume reset succeeded # MODIFICATION: Use the new, correct check.
if not self.simulation_hub.get_all_target_ids(): if not self.simulation_hub.has_active_real_targets():
self.logger.info("Radar reported zero active targets after reset.") self.logger.info("Radar reported zero active real targets after reset.")
return True return True
time.sleep(poll_interval) time.sleep(poll_interval)
waited += poll_interval waited += poll_interval
# If we reach here, the hub still reports targets — treat as failure # If we reach here, the hub still reports active real targets — treat as failure
self.logger.error("Radar did not clear targets after reset within timeout.") self.logger.error("Radar did not clear real targets after reset within timeout.")
messagebox.showerror("Reset Error", "Radar did not clear targets after reset.") messagebox.showerror("Reset Error", "Radar did not clear targets after reset.")
return False return False
@ -692,27 +678,15 @@ class MainView(tk.Tk):
self.logger.info("Resetting simulation data hub and PPI trails.") self.logger.info("Resetting simulation data hub and PPI trails.")
self.simulation_hub.reset() self.simulation_hub.reset()
self.ppi_widget.clear_trails() self.ppi_widget.clear_trails()
# If SFP reception was deferred, establish SFP connection now so we
# can receive server state updates during simulation.
if hasattr(self.target_communicator, "connect") and not self.target_communicator.is_open:
try:
# Try to connect with stored config; if connect fails, we abort
sfp_cfg = self.connection_config.get("target", {}).get("sfp", {})
if sfp_cfg:
self.logger.info("Deferred SFP connect: attempting to connect for simulation start.")
if not self.target_communicator.connect(sfp_cfg):
self.logger.error("Failed to connect SFP communicator at simulation start.")
messagebox.showerror("Connection Error", "Failed to establish SFP receive connection.")
return
except Exception:
self.logger.exception("Exception while attempting deferred SFP connect.")
messagebox.showerror("Connection Error", "Exception while establishing SFP receive connection.")
return
if not self._reset_radar_state(): if not self._reset_radar_state():
self.logger.error("Aborting simulation start due to radar reset failure.") self.logger.error("Aborting simulation start due to radar reset failure.")
return return
# MODIFICATION: Add a short delay to allow the server to process the reset
# before it receives the new scenario initialization commands.
time.sleep(1) # 1 second delay
self.logger.info( self.logger.info(
"Sending initial scenario state before starting live updates..." "Sending initial scenario state before starting live updates..."
) )
@ -733,13 +707,11 @@ class MainView(tk.Tk):
self.scenario.reset_simulation() self.scenario.reset_simulation()
# --- MODIFICATION HERE ---
self.simulation_engine = SimulationEngine( self.simulation_engine = SimulationEngine(
communicator=self.target_communicator, communicator=self.target_communicator,
update_queue=self.gui_update_queue, update_queue=self.gui_update_queue,
simulation_hub=self.simulation_hub, # Pass the hub to the engine simulation_hub=self.simulation_hub,
) )
# --- END MODIFICATION ---
self.simulation_engine.set_time_multiplier(self.time_multiplier) self.simulation_engine.set_time_multiplier(self.time_multiplier)
self.simulation_engine.set_update_interval(update_interval) self.simulation_engine.set_update_interval(update_interval)

View File

@ -14,6 +14,7 @@ import logging
import math import math
import json import json
import ctypes import ctypes
import time
from queue import Queue, Full from queue import Queue, Full
from typing import Dict, Optional, Any, List, Callable from typing import Dict, Optional, Any, List, Callable
@ -135,19 +136,9 @@ class DebugPayloadRouter:
if self._hub: if self._hub:
try: try:
parsed_for_hub = SfpRisStatusPayload.from_buffer_copy(payload) # Use the client's monotonic clock as the single source of truth for timestamps
ts_s = parsed_for_hub.scenario.timetag / 1000.0 # to ensure simulated and real data can be correlated.
reception_timestamp = time.monotonic()
# First: clear real data for any targets that the server marked as inactive
try:
for i, ris_t in enumerate(parsed_for_hub.tgt.tgt):
try:
if ris_t.flags == 0 and self._hub and hasattr(self._hub, 'clear_real_target_data'):
self._hub.clear_real_target_data(i)
except Exception:
pass
except Exception:
pass
# Add real states for active targets # Add real states for active targets
for target in real_targets: for target in real_targets:
@ -157,19 +148,16 @@ class DebugPayloadRouter:
getattr(target, '_pos_z_ft', 0.0), getattr(target, '_pos_z_ft', 0.0),
) )
self._hub.add_real_state( self._hub.add_real_state(
target_id=target.target_id, timestamp=ts_s, state=state_tuple target_id=target.target_id, timestamp=reception_timestamp, state=state_tuple
) )
# Propagate heading information (if available) into the hub so # Propagate heading information (if available) into the hub
# GUI builders that reconstruct lightweight Target objects
# from the hub can also pick up the last known heading.
try: try:
for target in real_targets: for target in real_targets:
if hasattr(self._hub, 'set_real_heading'): if hasattr(self._hub, 'set_real_heading'):
raw_val = getattr(target, '_raw_heading', None) raw_val = getattr(target, '_raw_heading', None)
self._hub.set_real_heading(target.target_id, getattr(target, 'current_heading_deg', 0.0), raw_value=raw_val) self._hub.set_real_heading(target.target_id, getattr(target, 'current_heading_deg', 0.0), raw_value=raw_val)
except Exception: except Exception:
# Never allow heading propagation to break payload handling
self._logger.debug("Failed to propagate heading to hub", exc_info=True) self._logger.debug("Failed to propagate heading to hub", exc_info=True)
if self._update_queue: if self._update_queue: