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
}
]
},
"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,
"max_range": 100,
"geometry": "1599x1024+501+84",
"last_selected_scenario": "scenario1",
"last_selected_scenario": "scenario3",
"connection": {
"target": {
"type": "sfp",

View File

@ -165,6 +165,20 @@ class SimulationStateHub:
with self._lock:
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):
"""Clears all stored data for all targets."""
with self._lock:

View File

@ -139,18 +139,29 @@ class SFPCommunicator(CommunicatorInterface):
self.logger.info(f"Sending scenario '{scenario.name}' via SFP...")
if not self._send_single_command(command_builder.build_pause()):
return False
# Build a list of commands to be sent atomically
commands = [
command_builder.build_pause(),
command_builder.build_aclatch() # Add aclatch for atomic update
]
for target in scenario.get_all_targets():
cmd = 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)
commands.append(command_builder.build_tgtinit(target))
if not self._send_single_command(command_builder.build_continue()):
return False
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
time.sleep(0.05) # Use a safer 50ms delay between commands
self.logger.info("Finished sending scenario via SFP.")
return True

View File

@ -589,54 +589,40 @@ class MainView(tk.Tk):
self.logger.info("Sending reset commands to deactivate all radar targets...")
# Prefer an atomic reset command to deactivate all targets on the server
# 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.
# Build the atomic reset command.
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:
# 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.")
reset_command = "tgtset /-s"
# Some radar servers require adjusting internal parameters to accept
# large multi-target operations. Send a preparatory command to set
# the server t_rows parameter before issuing the atomic tgtset reset.
# Some radar servers require adjusting internal parameters.
prep_command = "$mex.t_rows=80"
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):
self.logger.error("Failed to send preparatory/reset commands to the radar.")
messagebox.showerror("Reset Error", "Failed to send reset command to the radar.")
return False
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
poll_interval = 0.2
waited = 0.0
while waited < timeout_s:
# If there are no real target entries in the hub, assume reset succeeded
if not self.simulation_hub.get_all_target_ids():
self.logger.info("Radar reported zero active targets after reset.")
# MODIFICATION: Use the new, correct check.
if not self.simulation_hub.has_active_real_targets():
self.logger.info("Radar reported zero active real targets after reset.")
return True
time.sleep(poll_interval)
waited += poll_interval
# If we reach here, the hub still reports targets — treat as failure
self.logger.error("Radar did not clear targets after reset within timeout.")
# If we reach here, the hub still reports active real targets — treat as failure
self.logger.error("Radar did not clear real targets after reset within timeout.")
messagebox.showerror("Reset Error", "Radar did not clear targets after reset.")
return False
@ -692,27 +678,15 @@ class MainView(tk.Tk):
self.logger.info("Resetting simulation data hub and PPI trails.")
self.simulation_hub.reset()
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():
self.logger.error("Aborting simulation start due to radar reset failure.")
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(
"Sending initial scenario state before starting live updates..."
)
@ -733,13 +707,11 @@ class MainView(tk.Tk):
self.scenario.reset_simulation()
# --- MODIFICATION HERE ---
self.simulation_engine = SimulationEngine(
communicator=self.target_communicator,
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_update_interval(update_interval)

View File

@ -14,6 +14,7 @@ import logging
import math
import json
import ctypes
import time
from queue import Queue, Full
from typing import Dict, Optional, Any, List, Callable
@ -135,19 +136,9 @@ class DebugPayloadRouter:
if self._hub:
try:
parsed_for_hub = SfpRisStatusPayload.from_buffer_copy(payload)
ts_s = parsed_for_hub.scenario.timetag / 1000.0
# 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
# Use the client's monotonic clock as the single source of truth for timestamps
# to ensure simulated and real data can be correlated.
reception_timestamp = time.monotonic()
# Add real states for active targets
for target in real_targets:
@ -157,19 +148,16 @@ class DebugPayloadRouter:
getattr(target, '_pos_z_ft', 0.0),
)
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
# GUI builders that reconstruct lightweight Target objects
# from the hub can also pick up the last known heading.
# Propagate heading information (if available) into the hub
try:
for target in real_targets:
if hasattr(self._hub, 'set_real_heading'):
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)
except Exception:
# Never allow heading propagation to break payload handling
self._logger.debug("Failed to propagate heading to hub", exc_info=True)
if self._update_queue: