diff --git a/logger_prefs.json b/logger_prefs.json index 5fa87f2..0df2b41 100644 --- a/logger_prefs.json +++ b/logger_prefs.json @@ -1,9 +1,10 @@ { "saved_levels": { - "target_simulator.gui.payload_router": "INFO", + "target_simulator.gui.payload_router": "DEBUG", "target_simulator.analysis.simulation_state_hub": "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" } \ No newline at end of file diff --git a/target_simulator/analysis/simulation_state_hub.py b/target_simulator/analysis/simulation_state_hub.py index 7c1621c..1754485 100644 --- a/target_simulator/analysis/simulation_state_hub.py +++ b/target_simulator/analysis/simulation_state_hub.py @@ -270,6 +270,16 @@ class SimulationStateHub: with self._lock: self._antenna_azimuth_deg = az 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]]: """ @@ -278,6 +288,15 @@ class SimulationStateHub: If not available, returns (None, None). """ 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) # Backwards-compatible aliases for existing callers. These delegate to the diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index ae39bef..142141a 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -642,6 +642,15 @@ class MainView(tk.Tk): # Update antenna sweep line if self.ppi_widget.animate_antenna_var.get(): 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: self.ppi_widget.render_antenna_line(az_deg) else: @@ -679,6 +688,9 @@ class MainView(tk.Tk): 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) def _refresh_analysis_list(self): @@ -776,4 +788,4 @@ class MainView(tk.Tk): self.logger.debug(f"Error updating latency status: {e}") finally: # Schedule the next update - self.after(1000, self._update_latency_status) \ No newline at end of file + self.after(1000, self._update_latency_status) diff --git a/target_simulator/gui/payload_router.py b/target_simulator/gui/payload_router.py index f330012..7b02eb3 100644 --- a/target_simulator/gui/payload_router.py +++ b/target_simulator/gui/payload_router.py @@ -336,7 +336,9 @@ class DebugPayloadRouter: # --- Propagate antenna azimuth to hub --- if self._hub: # 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") total_az_rad = None @@ -372,61 +374,65 @@ class DebugPayloadRouter: except Exception: return - # The JSON may follow the same structure as the RIS debug JSON - # we generate elsewhere: {"scenario": {...}, "targets": [...]} - def _find_scenario_field(dct, key): + # Helper to find fields in varied JSON structures + def _find_val(key_candidates, dct): if not isinstance(dct, dict): return None - # Direct scenario container - sc = dct.get("scenario") or dct.get("sc") or dct - if isinstance(sc, dict) and key in sc: - return sc.get(key) - # Top-level key - if key in dct: - return dct.get(key) - # Nested search - for v in dct.values(): - if isinstance(v, dict) and key in v: - return v.get(key) + # Check top-level + for key in key_candidates: + if key in dct: + return dct[key] + # Check scenario sub-dict if present + sc = dct.get("scenario") or dct.get("sc") + if isinstance(sc, dict): + for key in key_candidates: + if key in sc: + return sc[key] return None - # Find platform heading and relative antenna sweep - heading_val = _find_scenario_field(obj, "true_heading") or _find_scenario_field( - obj, "platform_azimuth" + # Find platform heading and relative antenna sweep using multiple potential keys + heading_val = _find_val( + ["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 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: - # Values may be in radians or degrees; if small absolute - # value (< 2*pi) assume radians and convert. - val = float(total_az_val) - if abs(val) <= (2 * math.pi + 0.01): - az_deg = math.degrees(val) - else: - az_deg = val + h_rad = float(heading_val) + # If heading is in degrees (> 2*pi), convert to radians + if abs(h_rad) > (2 * math.pi + 0.01): + h_rad = math.radians(h_rad) + + total_az_rad = h_rad + 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()) - except Exception: + except Exception as e: + self._logger.debug(f"Error processing azimuth values: {e}") pass + else: + pass + # self._logger.debug("No heading info found in JSON payload") # Optionally capture elevation for future UI use - plat_el = _find_scenario_field(obj, "ant_nav_el") or _find_scenario_field( - obj, "platform_elevation" - ) - 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 + # plat_el = _find_val(["ant_nav_el", "platform_elevation"], obj) + # if plat_el is not None: + # pass - except Exception: + except Exception as e: self._logger.exception("Error handling JSON payload") def get_and_clear_latest_payloads(self) -> Dict[str, Any]: @@ -545,4 +551,4 @@ class DebugPayloadRouter: def set_persist(self, enabled: bool): with self._lock: - self._persist = bool(enabled) \ No newline at end of file + self._persist = bool(enabled) diff --git a/target_simulator/gui/ppi_adapter.py b/target_simulator/gui/ppi_adapter.py index 09b08ee..2c37a55 100644 --- a/target_simulator/gui/ppi_adapter.py +++ b/target_simulator/gui/ppi_adapter.py @@ -40,10 +40,10 @@ def build_display_data( # --- Process Simulated Data (transforming from absolute to relative) --- if history.get("simulated"): last_sim_state = history["simulated"][-1] - + if len(last_sim_state) >= 6: _ts, x_ft, y_ft, z_ft, vel_fps, vert_vel_fps = last_sim_state[:6] - else: + else: _ts, x_ft, y_ft, z_ft = last_sim_state vel_fps, vert_vel_fps = 0.0, 0.0 @@ -52,11 +52,13 @@ def build_display_data( if ownship_pos_xy_ft: rel_x_ft = x_ft - ownship_pos_xy_ft[0] rel_y_ft = y_ft - ownship_pos_xy_ft[1] - + sim_target = Target(target_id=tid, trajectory=[]) setattr(sim_target, "_pos_x_ft", rel_x_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_vertical_velocity_fps = vert_vel_fps sim_target._update_current_polar_coords() @@ -117,4 +119,4 @@ def build_display_data( len(real_targets_for_ppi), ) - return {"simulated": simulated_targets_for_ppi, "real": real_targets_for_ppi} \ No newline at end of file + return {"simulated": simulated_targets_for_ppi, "real": real_targets_for_ppi} diff --git a/target_simulator/gui/ppi_display.py b/target_simulator/gui/ppi_display.py index abe7f81..16981d8 100644 --- a/target_simulator/gui/ppi_display.py +++ b/target_simulator/gui/ppi_display.py @@ -236,7 +236,9 @@ class PPIDisplay(ttk.Frame): (self._path_plot,) = self.ax.plot([], [], "g--", linewidth=1.5, 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] limit_rad = np.deg2rad(self.scan_limit_deg) @@ -413,7 +415,13 @@ class PPIDisplay(ttk.Frame): r_nm = target.current_range_nm theta_rad_plot = np.deg2rad(target.current_azimuth_deg) (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) (x_mark,) = self.ax.plot( @@ -424,7 +432,7 @@ class PPIDisplay(ttk.Frame): markersize=8, markeredgewidth=0.9, linestyle="", - zorder=6 + zorder=6, ) label_artist_list.append(x_mark) except Exception: @@ -489,7 +497,13 @@ class PPIDisplay(ttk.Frame): if len(trail) > 1: thetas, rs = zip(*trail) (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) @@ -620,21 +634,53 @@ class PPIDisplay(ttk.Frame): def render_antenna_line(self, az_deg: Optional[float]): """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: + # 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: - 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(): self._antenna_line_artist.set_visible(False) 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] self._antenna_line_artist.set_data([theta, theta], [0, max_r]) self._antenna_line_artist.set_visible(True) - - # This method is called frequently, so we rely on the main loop's - # final draw call to update the canvas, avoiding redundant draws. - # self.canvas.draw_idle() + # logger.debug(f"Antenna line drawn at theta={theta}") + # If we created the artist now, or we made it visible, schedule a + # canvas redraw. Use draw_idle to avoid immediate blocking draws. + 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: # Silently fail to prevent logging floods - pass \ No newline at end of file + pass diff --git a/target_simulator/utils/logger.py b/target_simulator/utils/logger.py index 9663d5c..f4d6771 100644 --- a/target_simulator/utils/logger.py +++ b/target_simulator/utils/logger.py @@ -144,11 +144,24 @@ def setup_basic_logging( _actual_console_handler = logging.StreamHandler() _actual_console_handler.setFormatter(_base_formatter) _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.setLevel(logging.DEBUG) 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 _log_processor_after_id = _tk_root_instance_for_processing.after( GLOBAL_LOG_QUEUE_POLL_INTERVAL_MS, _process_global_log_queue