sistemate alcuni problemi di aggiornamento.
This commit is contained in:
parent
747ec3b40f
commit
23bd5ee6dc
103
scenarios.json
103
scenarios.json
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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([
|
||||||
return False
|
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.")
|
self.logger.info("Finished sending scenario via SFP.")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -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
|
reset_command = "tgtset /-s"
|
||||||
if hasattr(command_builder, "build_tgtset"):
|
|
||||||
reset_command = command_builder.build_tgtset("/-s")
|
|
||||||
else:
|
|
||||||
# Fallback: raw command string
|
|
||||||
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)
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user