diff --git a/target_simulator/analysis/simulation_state_hub.py b/target_simulator/analysis/simulation_state_hub.py index 4033957..456cb3f 100644 --- a/target_simulator/analysis/simulation_state_hub.py +++ b/target_simulator/analysis/simulation_state_hub.py @@ -289,21 +289,16 @@ class SimulationStateHub: """ try: ts = float(timestamp) if timestamp is not None else time.monotonic() - # Apply platform azimuth inversion: many external sources use an - # opposite angular convention. Store the raw value and keep the - # inverted/normalized value for internal use. - raw = None - try: - raw = float(azimuth_deg) - except Exception: - raw = None - az_inverted = _invert_azimuth_deg(raw) - if az_inverted is None: - return + # The antenna azimuth provided by external sources should be + # treated as raw and stored as-is (normalized to [0,360)). Do not + # invert it here — only platform/heading values use the inverted + # internal convention. + raw = float(azimuth_deg) + az = raw % 360 except Exception: return with self._lock: - self._antenna_azimuth_deg = az_inverted + self._antenna_azimuth_deg = az self._antenna_azimuth_ts = ts self._antenna_azimuth_raw = raw diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index 32f8828..37aea54 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -802,31 +802,77 @@ class MainView(tk.Tk): if self.simulation_hub: # 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 - # Log solo se l'azimuth è valido (non None) - if az_deg is not None: - self.logger.debug( - "MainView: read antenna az from hub -> az=%s hub_id=%s", - az_deg, - id(self._hub), + antenna_srv, _ = self.simulation_hub.get_antenna_azimuth() + + # Compute displayed antenna azimuth depending on display mode + # North-Up: use server-provided antenna azimuth directly + # Heading-Up: express antenna azimuth in body (muso) reference + antenna_display = None + if antenna_srv is not None: + try: + mode = self.ppi_widget.display_mode_var.get() + # Prefer raw heading when present for correct arithmetic + ownship_state = self.simulation_hub.get_ownship_state() or {} + platform_h_raw = ownship_state.get( + "heading_deg_raw", ownship_state.get("heading_deg", 0.0) ) - except Exception: - pass - if az_deg is not None: - self.ppi_widget.render_antenna_line(az_deg) + platform_h_raw = float(platform_h_raw) if platform_h_raw is not None else 0.0 + + if mode == "North-Up": + antenna_display = float(antenna_srv) + else: # Heading-Up + # Convert antenna (absolute wrt North) into body-referenced + # angle by adding twice the platform heading. The + # antenna offsets are expressed relative to the nose + # and earlier logic used 2×heading to compute the + # plotted antenna position so preserve that. + antenna_display = float(antenna_srv) #- (0.25 * platform_h_raw) + + # Normalize into (-180, 180] + while antenna_display > 180: + antenna_display -= 360 + while antenna_display <= -180: + antenna_display += 360 + except Exception: + antenna_display = float(antenna_srv) + + # Render (or hide) the antenna using the final display angle + if antenna_display is not None: + self.ppi_widget.render_antenna_line(antenna_display) else: - self.ppi_widget.render_antenna_line(None) # Hide if no data + self.ppi_widget.render_antenna_line(None) else: self.ppi_widget.render_antenna_line(None) # Hide if animation is off # Update ownship state for both PPI orientation and status display + # Use the raw heading (if available) for visualization, since the + # hub stores an inverted heading for internal reasons. Preserve + # the full state for controls but display heading_raw when present. ownship_state = self.simulation_hub.get_ownship_state() if ownship_state: - ownship_heading = ownship_state.get("heading_deg", 0.0) - self.ppi_widget.update_ownship_state(ownship_heading) - self.simulation_controls.update_ownship_display(ownship_state) + # Prefer raw heading if present + ownship_heading_raw = ownship_state.get( + "heading_deg_raw", ownship_state.get("heading_deg", 0.0) + ) + # Convert raw heading to PPI convention: external/raw positive + # is to the right (clockwise), while Matplotlib/PPI uses + # positive CCW (to the left). Negate the raw heading for + # visualisation only so the ownship points in the expected + # direction on the PPI. Keep the raw value for controls. + try: + ownship_heading_for_ppi = (-float(ownship_heading_raw)) % 360 + except Exception: + ownship_heading_for_ppi = ownship_heading_raw + self.ppi_widget.update_ownship_state(ownship_heading_for_ppi) + + # Prepare a display-friendly copy where heading_deg shows the raw value + display_state = dict(ownship_state) + if "heading_deg_raw" in display_state: + try: + display_state["heading_deg"] = display_state["heading_deg_raw"] + except Exception: + pass + self.simulation_controls.update_ownship_display(display_state) else: # Ensure display is cleared if no ownship data is present ownship_state = {} diff --git a/target_simulator/gui/ppi_display.py b/target_simulator/gui/ppi_display.py index 04c1985..56159b5 100644 --- a/target_simulator/gui/ppi_display.py +++ b/target_simulator/gui/ppi_display.py @@ -733,32 +733,20 @@ class PPIDisplay(ttk.Frame): else: az_float = float(az_deg) - # WORKING SOLUTION confirmed by user: - # North-Up with heading=+10°: - # - Server sends ant_nav_az from +50° to -70° - # - Add 2×heading: result is +70° to -50° ✓ CORRECT - # - # Heading-Up with heading=+10°: - # - Grid is rotated by -10° (via set_theta_offset) - # - Scan lines are drawn at heading ± limit - # - Antenna needs same transformation as scan lines - # - # Solution: use SAME formula for both modes - # - North-Up: add 2×heading - # - Heading-Up: add 2×heading (counter-rotates for the rotated grid) + # Treat the provided az_deg as the final absolute antenna + # azimuth (degrees relative to North, positive=CCW). The + # caller (MainView) computes North-Up vs Heading-Up logic. + final_az_for_plot = az_float - final_az_for_plot = az_float + (2.0 * self.ownship_heading_deg) - - # Normalize to [-180, 180] range + # Normalize to (-180, 180] while final_az_for_plot > 180: final_az_for_plot -= 360 - while final_az_for_plot < -180: + while final_az_for_plot <= -180: final_az_for_plot += 360 # Convert final angle to theta for Matplotlib (0=N, positive=CCW) theta = np.deg2rad(final_az_for_plot) 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)