sistemata la visualizzazione dell'antenna durante la connessione al server

This commit is contained in:
VALLONGOL 2025-11-07 13:16:00 +01:00
parent 7c7bbe57ba
commit 05ae71420b
7 changed files with 162 additions and 63 deletions

View File

@ -1,9 +1,10 @@
{ {
"saved_levels": { "saved_levels": {
"target_simulator.gui.payload_router": "INFO", "target_simulator.gui.payload_router": "DEBUG",
"target_simulator.analysis.simulation_state_hub": "INFO", "target_simulator.analysis.simulation_state_hub": "INFO",
"target_simulator.gui.main_view": "INFO", "target_simulator.gui.main_view": "INFO",
"target_simulator.core.sfp_transport": "INFO" "target_simulator.core.sfp_transport": "INFO",
"target_simulator.gui.ppi_display": "DEBUG"
}, },
"last_selected": "target_simulator.core.sfp_transport" "last_selected": "target_simulator.gui.payload_router"
} }

View File

@ -270,6 +270,16 @@ class SimulationStateHub:
with self._lock: with self._lock:
self._antenna_azimuth_deg = az self._antenna_azimuth_deg = az
self._antenna_azimuth_ts = ts self._antenna_azimuth_ts = ts
try:
# Debug log to aid diagnosis when antenna updates arrive
logger.debug(
"[SimulationStateHub] set_antenna_azimuth: az=%.3f ts=%s hub_id=%s",
az,
ts,
id(self),
)
except Exception:
pass
def get_antenna_azimuth(self) -> Tuple[Optional[float], Optional[float]]: def get_antenna_azimuth(self) -> Tuple[Optional[float], Optional[float]]:
""" """
@ -278,6 +288,15 @@ class SimulationStateHub:
If not available, returns (None, None). If not available, returns (None, None).
""" """
with self._lock: with self._lock:
try:
logger.debug(
"[SimulationStateHub] get_antenna_azimuth -> az=%s ts=%s hub_id=%s",
self._antenna_azimuth_deg,
self._antenna_azimuth_ts,
id(self),
)
except Exception:
pass
return (self._antenna_azimuth_deg, self._antenna_azimuth_ts) return (self._antenna_azimuth_deg, self._antenna_azimuth_ts)
# Backwards-compatible aliases for existing callers. These delegate to the # Backwards-compatible aliases for existing callers. These delegate to the

View File

@ -642,6 +642,15 @@ class MainView(tk.Tk):
# Update antenna sweep line # Update antenna sweep line
if self.ppi_widget.animate_antenna_var.get(): if self.ppi_widget.animate_antenna_var.get():
az_deg, _ = self.simulation_hub.get_antenna_azimuth() az_deg, _ = self.simulation_hub.get_antenna_azimuth()
try:
# Debug: record the value read from the hub and hub identity
self.logger.debug(
"MainView: read antenna az from hub -> az=%s hub_id=%s",
az_deg,
id(self.simulation_hub),
)
except Exception:
pass
if az_deg is not None: if az_deg is not None:
self.ppi_widget.render_antenna_line(az_deg) self.ppi_widget.render_antenna_line(az_deg)
else: else:
@ -679,6 +688,9 @@ class MainView(tk.Tk):
self._update_simulation_progress_display() self._update_simulation_progress_display()
if hasattr(self, "ppi_widget") and self.ppi_widget.canvas:
self.ppi_widget.canvas.draw_idle()
self.after(GUI_REFRESH_RATE_MS, self._gui_refresh_loop) self.after(GUI_REFRESH_RATE_MS, self._gui_refresh_loop)
def _refresh_analysis_list(self): def _refresh_analysis_list(self):

View File

@ -336,7 +336,9 @@ class DebugPayloadRouter:
# --- Propagate antenna azimuth to hub --- # --- Propagate antenna azimuth to hub ---
if self._hub: if self._hub:
# Get platform heading and relative antenna sweep # Get platform heading and relative antenna sweep
heading_rad = scenario_dict.get("true_heading", scenario_dict.get("platform_azimuth")) heading_rad = scenario_dict.get(
"true_heading", scenario_dict.get("platform_azimuth")
)
sweep_rad = scenario_dict.get("ant_nav_az") sweep_rad = scenario_dict.get("ant_nav_az")
total_az_rad = None total_az_rad = None
@ -372,61 +374,65 @@ class DebugPayloadRouter:
except Exception: except Exception:
return return
# The JSON may follow the same structure as the RIS debug JSON # Helper to find fields in varied JSON structures
# we generate elsewhere: {"scenario": {...}, "targets": [...]} def _find_val(key_candidates, dct):
def _find_scenario_field(dct, key):
if not isinstance(dct, dict): if not isinstance(dct, dict):
return None return None
# Direct scenario container # Check top-level
sc = dct.get("scenario") or dct.get("sc") or dct for key in key_candidates:
if isinstance(sc, dict) and key in sc: if key in dct:
return sc.get(key) return dct[key]
# Top-level key # Check scenario sub-dict if present
if key in dct: sc = dct.get("scenario") or dct.get("sc")
return dct.get(key) if isinstance(sc, dict):
# Nested search for key in key_candidates:
for v in dct.values(): if key in sc:
if isinstance(v, dict) and key in v: return sc[key]
return v.get(key)
return None return None
# Find platform heading and relative antenna sweep # Find platform heading and relative antenna sweep using multiple potential keys
heading_val = _find_scenario_field(obj, "true_heading") or _find_scenario_field( heading_val = _find_val(
obj, "platform_azimuth" ["true_heading", "platform_azimuth", "heading"], obj
)
sweep_val = _find_val(
["ant_nav_az", "antenna_azimuth", "sweep_azimuth"], obj
) )
sweep_val = _find_scenario_field(obj, "ant_nav_az")
total_az_val = None total_az_val = None
if heading_val is not None: if heading_val is not None:
total_az_val = float(heading_val)
if sweep_val is not None:
total_az_val += float(sweep_val)
if total_az_val is not None:
try: try:
# Values may be in radians or degrees; if small absolute h_rad = float(heading_val)
# value (< 2*pi) assume radians and convert. # If heading is in degrees (> 2*pi), convert to radians
val = float(total_az_val) if abs(h_rad) > (2 * math.pi + 0.01):
if abs(val) <= (2 * math.pi + 0.01): h_rad = math.radians(h_rad)
az_deg = math.degrees(val)
else: total_az_rad = h_rad
az_deg = val if sweep_val is not None:
s_rad = float(sweep_val)
# If sweep is in degrees, convert
if abs(s_rad) > (2 * math.pi + 0.01):
s_rad = math.radians(s_rad)
total_az_rad += s_rad
az_deg = math.degrees(total_az_rad)
# LOGGARE IL SUCCESSO PER DEBUG
self._logger.debug(
f"Found azimuth info in JSON: heading={heading_val}, sweep={sweep_val} -> total_deg={az_deg}"
)
self._hub.set_antenna_azimuth(az_deg, timestamp=time.monotonic()) self._hub.set_antenna_azimuth(az_deg, timestamp=time.monotonic())
except Exception: except Exception as e:
self._logger.debug(f"Error processing azimuth values: {e}")
pass pass
else:
pass
# self._logger.debug("No heading info found in JSON payload")
# Optionally capture elevation for future UI use # Optionally capture elevation for future UI use
plat_el = _find_scenario_field(obj, "ant_nav_el") or _find_scenario_field( # plat_el = _find_val(["ant_nav_el", "platform_elevation"], obj)
obj, "platform_elevation" # if plat_el is not None:
) # pass
if plat_el is not None:
try:
_ = float(plat_el)
# For now we don't store elevation in the hub; leave as TODO.
except Exception:
pass
except Exception: except Exception as e:
self._logger.exception("Error handling JSON payload") self._logger.exception("Error handling JSON payload")
def get_and_clear_latest_payloads(self) -> Dict[str, Any]: def get_and_clear_latest_payloads(self) -> Dict[str, Any]:

View File

@ -56,7 +56,9 @@ def build_display_data(
sim_target = Target(target_id=tid, trajectory=[]) sim_target = Target(target_id=tid, trajectory=[])
setattr(sim_target, "_pos_x_ft", rel_x_ft) setattr(sim_target, "_pos_x_ft", rel_x_ft)
setattr(sim_target, "_pos_y_ft", rel_y_ft) setattr(sim_target, "_pos_y_ft", rel_y_ft)
setattr(sim_target, "_pos_z_ft", z_ft) # Altitude is handled relative to ground setattr(
sim_target, "_pos_z_ft", z_ft
) # Altitude is handled relative to ground
sim_target.current_velocity_fps = vel_fps sim_target.current_velocity_fps = vel_fps
sim_target.current_vertical_velocity_fps = vert_vel_fps sim_target.current_vertical_velocity_fps = vert_vel_fps
sim_target._update_current_polar_coords() sim_target._update_current_polar_coords()

View File

@ -236,7 +236,9 @@ class PPIDisplay(ttk.Frame):
(self._path_plot,) = self.ax.plot([], [], "g--", linewidth=1.5, zorder=3) (self._path_plot,) = self.ax.plot([], [], "g--", linewidth=1.5, zorder=3)
(self._start_plot,) = self.ax.plot([], [], "go", markersize=8, zorder=3) (self._start_plot,) = self.ax.plot([], [], "go", markersize=8, zorder=3)
(self._waypoints_plot,) = self.ax.plot([], [], "y+", markersize=10, mew=2, zorder=3) (self._waypoints_plot,) = self.ax.plot(
[], [], "y+", markersize=10, mew=2, zorder=3
)
self.preview_artists = [self._path_plot, self._start_plot, self._waypoints_plot] self.preview_artists = [self._path_plot, self._start_plot, self._waypoints_plot]
limit_rad = np.deg2rad(self.scan_limit_deg) limit_rad = np.deg2rad(self.scan_limit_deg)
@ -413,7 +415,13 @@ class PPIDisplay(ttk.Frame):
r_nm = target.current_range_nm r_nm = target.current_range_nm
theta_rad_plot = np.deg2rad(target.current_azimuth_deg) theta_rad_plot = np.deg2rad(target.current_azimuth_deg)
(dot,) = self.ax.plot( (dot,) = self.ax.plot(
theta_rad_plot, r_nm, "o", markersize=6, color=color, alpha=0.6, zorder=5 theta_rad_plot,
r_nm,
"o",
markersize=6,
color=color,
alpha=0.6,
zorder=5,
) )
artist_list.append(dot) artist_list.append(dot)
(x_mark,) = self.ax.plot( (x_mark,) = self.ax.plot(
@ -424,7 +432,7 @@ class PPIDisplay(ttk.Frame):
markersize=8, markersize=8,
markeredgewidth=0.9, markeredgewidth=0.9,
linestyle="", linestyle="",
zorder=6 zorder=6,
) )
label_artist_list.append(x_mark) label_artist_list.append(x_mark)
except Exception: except Exception:
@ -489,7 +497,13 @@ class PPIDisplay(ttk.Frame):
if len(trail) > 1: if len(trail) > 1:
thetas, rs = zip(*trail) thetas, rs = zip(*trail)
(line,) = self.ax.plot( (line,) = self.ax.plot(
thetas, rs, color=color, linestyle="-", linewidth=0.8, alpha=0.7, zorder=3 thetas,
rs,
color=color,
linestyle="-",
linewidth=0.8,
alpha=0.7,
zorder=3,
) )
artist_list.append(line) artist_list.append(line)
@ -620,21 +634,53 @@ class PPIDisplay(ttk.Frame):
def render_antenna_line(self, az_deg: Optional[float]): def render_antenna_line(self, az_deg: Optional[float]):
"""Directly renders the antenna line at a given absolute azimuth.""" """Directly renders the antenna line at a given absolute azimuth."""
# Aggiunto log di debug per tracciare la chiamata
# logger.debug(f"PPIDisplay.render_antenna_line called with az_deg={az_deg}")
try: try:
# If the artist was not created (unexpected), create it lazily so
# the antenna can still be rendered. This avoids silent failures
# when the initial plot setup didn't complete for some reason.
created_artist = False
if self._antenna_line_artist is None: if self._antenna_line_artist is None:
return try:
(self._antenna_line_artist,) = self.ax.plot(
[],
[],
color="lightgray",
linestyle="--",
linewidth=1.2,
alpha=0.85,
zorder=2,
)
created_artist = True
logger.debug(
"PPIDisplay: lazily created _antenna_line_artist for widget id=%s",
id(self),
)
except Exception:
# If creation fails, give up gracefully
return
if az_deg is None or not self.animate_antenna_var.get(): if az_deg is None or not self.animate_antenna_var.get():
self._antenna_line_artist.set_visible(False) self._antenna_line_artist.set_visible(False)
else: else:
theta = np.deg2rad(float(az_deg)) # Assicuriamoci che az_deg sia un float valido
az_float = float(az_deg)
theta = np.deg2rad(az_float % 360)
max_r = self.ax.get_ylim()[1] max_r = self.ax.get_ylim()[1]
self._antenna_line_artist.set_data([theta, theta], [0, max_r]) self._antenna_line_artist.set_data([theta, theta], [0, max_r])
self._antenna_line_artist.set_visible(True) self._antenna_line_artist.set_visible(True)
# logger.debug(f"Antenna line drawn at theta={theta}")
# This method is called frequently, so we rely on the main loop's # If we created the artist now, or we made it visible, schedule a
# final draw call to update the canvas, avoiding redundant draws. # canvas redraw. Use draw_idle to avoid immediate blocking draws.
# self.canvas.draw_idle() try:
if (
created_artist
or (az_deg is not None and self.animate_antenna_var.get())
) and self.canvas:
self.canvas.draw_idle()
except Exception:
pass
except Exception: except Exception:
# Silently fail to prevent logging floods # Silently fail to prevent logging floods
pass pass

View File

@ -144,11 +144,24 @@ def setup_basic_logging(
_actual_console_handler = logging.StreamHandler() _actual_console_handler = logging.StreamHandler()
_actual_console_handler.setFormatter(_base_formatter) _actual_console_handler.setFormatter(_base_formatter)
_actual_console_handler.setLevel(logging.DEBUG) _actual_console_handler.setLevel(logging.DEBUG)
try:
# Also attach console handler directly to the root logger so
# console output appears immediately (helps during development
# and when the Tk polling loop hasn't started yet).
root_logger.addHandler(_actual_console_handler)
except Exception:
pass
queue_putter = QueuePuttingHandler(handler_queue=_global_log_queue) queue_putter = QueuePuttingHandler(handler_queue=_global_log_queue)
queue_putter.setLevel(logging.DEBUG) queue_putter.setLevel(logging.DEBUG)
root_logger.addHandler(queue_putter) root_logger.addHandler(queue_putter)
# Emit a small startup message so users running from console see logging is active
try:
root_logger.debug("Logging system initialized (queue-based).")
except Exception:
pass
_logging_system_active = True _logging_system_active = True
_log_processor_after_id = _tk_root_instance_for_processing.after( _log_processor_after_id = _tk_root_instance_for_processing.after(
GLOBAL_LOG_QUEUE_POLL_INTERVAL_MS, _process_global_log_queue GLOBAL_LOG_QUEUE_POLL_INTERVAL_MS, _process_global_log_queue