@@ -373,7 +373,7 @@ void App::ssrTopicCallback(const String& payload)
373373 hostSynced ? m_timeSync.hostToEspLocalMs (hostEpochMs)
374374 : m_timeSync.localNowMs (); /* Fallback: use local time if host is not synced */
375375
376- LOG_INFO (" SSR pose: ts_host_ms=%llu (hostSync=%s)" , hostEpochMs, hostSynced ? " true" : " false" );
376+ LOG_DEBUG (" SSR pose: ts_host_ms=%llu (hostSync=%s)" , hostEpochMs, hostSynced ? " true" : " false" );
377377
378378 SpaceShipRadarPose ssrPose;
379379 ssrPose.x = static_cast <float >(x_mm_i);
@@ -383,22 +383,22 @@ void App::ssrTopicCallback(const String& payload)
383383 ssrPose.v_y = Y_SIGN * static_cast <float >(vy_mms_i);
384384 ssrPose.timestamp = static_cast <uint32_t >(ssrLocalTsMs);
385385
386- if (false == m_odoOriginInitialized)
386+ if (( false == m_odoOriginInitialized || false == m_odoZeroInitialized) && m_hasVehicleData == true )
387387 {
388388 m_odoOriginX_mm = ssrPose.x ;
389389 m_odoOriginY_mm = ssrPose.y ;
390390 m_odoOriginTheta_mrad = ssrPose.theta ;
391391 m_odoOriginInitialized = true ;
392+ m_odoZeroX_mm = static_cast <float >(m_lastVehicleData.xPos );
393+ m_odoZeroY_mm = static_cast <float >(m_lastVehicleData.yPos );
394+ m_odoZeroTheta_mrad = static_cast <float >(m_lastVehicleData.orientation );
395+ m_odoZeroInitialized = true ;
392396
393- m_odoZeroX_mm = static_cast <float >(m_lastVehicleData.xPos );
394- m_odoZeroY_mm = static_cast <float >(m_lastVehicleData.yPos );
395- m_odoZeroTheta_mrad = static_cast <float >(m_lastVehicleData.orientation );
396- m_odoZeroInitialized = true ;
397-
398- LOG_INFO (" Odometry origin set from SSR: x=%dmm y=%dmm" , x_mm_i, y_mm_i);
397+ LOG_INFO (" Odometry origin set from SSR: x=%fmm y=%fmm, theta=%f mrad" , m_odoOriginX_mm, m_odoOriginY_mm,
398+ m_odoOriginTheta_mrad);
399399 }
400400
401- if (false == m_ekfInitializedFromSSR)
401+ if (false == m_ekfInitializedFromSSR && true == m_odoOriginInitialized && true == m_odoZeroInitialized )
402402 {
403403 StateVector x0;
404404 x0.setZero ();
@@ -524,7 +524,7 @@ void App::filterLocationData(const VehicleData& vehicleData, const SpaceShipRada
524524 zumoLocalMs32 = static_cast <uint32_t >(m_timeSync.mapZumoToLocalMs (zumoTs32));
525525 ssrLocalMs32 = static_cast <uint32_t >(ssrPose.timestamp );
526526
527- LOG_INFO (" Filtering location data: Zumo=%u ms, SSR=%u ms" , zumoLocalMs32, ssrLocalMs32);
527+ LOG_DEBUG (" Filtering location data: Zumo=%u ms, SSR=%u ms" , zumoLocalMs32, ssrLocalMs32);
528528
529529 /* Initialize EKF time on first data. */
530530 hasTimestamp = initializeEkfTimestamp (zumoLocalMs32, ssrLocalMs32);
@@ -576,7 +576,6 @@ void App::filterLocationData(const VehicleData& vehicleData, const SpaceShipRada
576576void App::transformOdometryToGlobal (const VehicleData& vehicleData, float & xGlob_mm, float & yGlob_mm,
577577 float & thetaGlob_mrad) const
578578{
579- /* Fallback if origin or odometry zero is not initialized yet. */
580579 if ((false == m_odoOriginInitialized) || (false == m_odoZeroInitialized))
581580 {
582581 xGlob_mm = static_cast <float >(vehicleData.xPos );
@@ -585,44 +584,32 @@ void App::transformOdometryToGlobal(const VehicleData& vehicleData, float& xGlob
585584 return ;
586585 }
587586
588- /* Odometry delta since SSR origin was captured. */
589- const float xLocal_mm = static_cast <float >(vehicleData.xPos ) - m_odoZeroX_mm;
590- const float yLocal_mm = static_cast <float >(vehicleData.yPos ) - m_odoZeroY_mm;
591- const float thetaLocal_mrad = static_cast <float >(vehicleData.orientation ) - m_odoZeroTheta_mrad;
592-
593- /* ------------------------------------------------------------------
594- * 1) Fixed frame rotation between odometry frame and SSR world frame.
595- * Only a pure +/- 90 deg rotation, no mirroring.
596- * ------------------------------------------------------------------ */
597- constexpr float PHI_MRAD = -M_PI / 2 .0F * 1000 .0F ; // -pi/2 * 1000 -> -90 deg
587+ const float xLocal_raw = static_cast <float >(vehicleData.xPos ) - m_odoZeroX_mm;
588+ const float yLocal_raw = static_cast <float >(vehicleData.yPos ) - m_odoZeroY_mm;
598589
599- const float phi_rad = PHI_MRAD / 1000 . 0F ;
600- const float cP = cosf (phi_rad);
601- const float sP = sinf (phi_rad);
590+ // Map ODO pos to ENU (East/North)
591+ const float xLocal_mm = yLocal_raw; // East
592+ const float yLocal_mm = -xLocal_raw; // North
602593
603- const float xPhi_mm = cP * xLocal_mm - sP * yLocal_mm;
604- const float yPhi_mm = sP * xLocal_mm + cP * yLocal_mm ;
594+ // ODO delta since SSR origin was captured
595+ const float thetaLocal_mrad = static_cast < float >(vehicleData. orientation ) - m_odoZeroTheta_mrad ;
605596
606- /* ------------------------------------------------------------------
607- * 2) Rotate by SSR origin yaw (world orientation at capture time).
608- * ------------------------------------------------------------------ */
597+ // Rotate delta into SSR world frame using SSR yaw at capture time
609598 const float originTh_rad = m_odoOriginTheta_mrad / 1000 .0F ;
610599 const float c0 = cosf (originTh_rad);
611600 const float s0 = sinf (originTh_rad);
612601
613- const float dxW_mm = c0 * xPhi_mm - s0 * yPhi_mm ;
614- const float dyW_mm = s0 * xPhi_mm + c0 * yPhi_mm ;
602+ const float dxW_mm = c0 * xLocal_mm - s0 * yLocal_mm ;
603+ const float dyW_mm = s0 * xLocal_mm + c0 * yLocal_mm ;
615604
616- /* ------------------------------------------------------------------
617- * 3) Translate into SSR world frame.
618- * ------------------------------------------------------------------ */
605+ // Translate into SSR world frame
619606 xGlob_mm = m_odoOriginX_mm + dxW_mm;
620607 yGlob_mm = m_odoOriginY_mm + dyW_mm;
621608
622- /* ------------------------------------------------------------------
623- * 4) Heading: origin yaw + fixed frame offset + odometry delta.
624- * ------------------------------------------------------------------ */
625- thetaGlob_mrad = m_odoOriginTheta_mrad + PHI_MRAD + thetaLocal_mrad ;
609+ // Heading
610+ thetaGlob_mrad = m_odoOriginTheta_mrad + thetaLocal_mrad;
611+
612+ thetaGlob_mrad = wrapAngleMrad (thetaGlob_mrad) ;
626613}
627614
628615void App::publishFusionPose (uint32_t tsMs)
@@ -702,6 +689,7 @@ void App::publishGps(MqttClient& mqttClient, uint32_t tsMs)
702689 }
703690
704691 bool hasOrientation = gps->getOrientation (headingMrad);
692+ headingMrad = wrapAngleMrad (headingMrad);
705693
706694 static bool havePrev = false ;
707695 static int32_t prevX = 0 ;
@@ -752,7 +740,10 @@ void App::publishGps(MqttClient& mqttClient, uint32_t tsMs)
752740
753741void App::onVehicleData (const VehicleData& data)
754742{
755- publishVehicleAndSensorSnapshot (data);
743+ if (m_odoOriginInitialized && m_odoZeroInitialized)
744+ {
745+ publishVehicleAndSensorSnapshot (data);
746+ }
756747
757748 m_lastVehicleData = data;
758749 m_hasVehicleData = true ;
@@ -864,6 +855,20 @@ void App::updateFromSsr(const SpaceShipRadarPose& ssrPose)
864855 m_ekf.updateCamera (z_cam);
865856}
866857
858+ float App::wrapAngleMrad (float angleMrad) const
859+ {
860+ constexpr float PI_MRAD = M_PI * 1000 .0F ;
861+ constexpr float TWO_PI_MRAD = 2 .0F * PI_MRAD;
862+ /* Wrap to [-pi, pi). */
863+ float x = std::fmod (angleMrad + PI_MRAD, TWO_PI_MRAD);
864+ if (x < 0 .0F )
865+ {
866+ x += TWO_PI_MRAD;
867+ }
868+
869+ return x - PI_MRAD;
870+ }
871+
867872/* *****************************************************************************
868873 * External Functions
869874 *****************************************************************************/
0 commit comments