From 23bd5ee6dcd616753edf435ed4343a53483d363c Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 27 Oct 2025 15:45:04 +0100 Subject: [PATCH] sistemate alcuni problemi di aggiornamento. --- scenarios.json | 105 +++++++++++++++++- settings.json | 2 +- .../analysis/simulation_state_hub.py | 14 +++ target_simulator/core/sfp_communicator.py | 29 +++-- target_simulator/gui/main_view.py | 60 +++------- target_simulator/gui/payload_router.py | 24 +--- 6 files changed, 160 insertions(+), 74 deletions(-) diff --git a/scenarios.json b/scenarios.json index 0f04c34..b17e27f 100644 --- a/scenarios.json +++ b/scenarios.json @@ -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 + } + ] } -} - +} \ No newline at end of file diff --git a/settings.json b/settings.json index 759cbdb..d261138 100644 --- a/settings.json +++ b/settings.json @@ -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", diff --git a/target_simulator/analysis/simulation_state_hub.py b/target_simulator/analysis/simulation_state_hub.py index 7be38e6..f1eecab 100644 --- a/target_simulator/analysis/simulation_state_hub.py +++ b/target_simulator/analysis/simulation_state_hub.py @@ -164,6 +164,20 @@ class SimulationStateHub: """Returns a list of all target IDs currently being tracked.""" 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.""" diff --git a/target_simulator/core/sfp_communicator.py b/target_simulator/core/sfp_communicator.py index 8bbbe50..a26b07c 100644 --- a/target_simulator/core/sfp_communicator.py +++ b/target_simulator/core/sfp_communicator.py @@ -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)) + + commands.extend([ + command_builder.build_acunlatch(), # Add acunlatch to apply changes + command_builder.build_continue() + ]) - if not self._send_single_command(command_builder.build_continue()): - return False + # 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 diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index a36d88b..6694e09 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -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) diff --git a/target_simulator/gui/payload_router.py b/target_simulator/gui/payload_router.py index 62d8733..9554bc1 100644 --- a/target_simulator/gui/payload_router.py +++ b/target_simulator/gui/payload_router.py @@ -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: