Skip to content

feat(sensors/vehicle_gps_position): per-receiver baseline rotation SENS_GPSn_ROT - #28653

Closed
dakejahl wants to merge 3 commits into
mainfrom
dakejahl/sens-gps-rot
Closed

feat(sensors/vehicle_gps_position): per-receiver baseline rotation SENS_GPSn_ROT#28653
dakejahl wants to merge 3 commits into
mainfrom
dakejahl/sens-gps-rot

Conversation

@dakejahl

Copy link
Copy Markdown
Contributor

Summary

Replaces GPS_YAW_OFFSET, SEP_YAW_OFFS, SEP_PITCH_OFFS and EKF2_GPS_YAW_OFF with a per-receiver baseline rotation SENS_GPSn_ROT (the standard sensor Rotation enum, custom Euler via SENS_GPSn_ROLL/PITCH/YAW) next to the existing SENS_GPSn_ID, SENS_GPSn_OFFX/Y/Z and SENS_GPSn_DELAY. Stacked on #27102; the first two commits are that PR. Bumps PX4-GPSDrivers to PX4/PX4-GPSDrivers#240.

Problem

The antenna baseline orientation lived in three places depending on the transport: in the serial driver (GPS_YAW_OFFSET, subtracted before publish), inside the Septentrio receiver (SEP_YAW_OFFS, SEP_PITCH_OFFS), and in EKF2 for everything that arrived with a NaN offset, in practice CAN receivers (EKF2_GPS_YAW_OFF). A CAN receiver needed the node's GPS_YAW_OFFSET and the autopilot's EKF2_GPS_YAW_OFF to agree, and a second receiver with a different baseline had no parameter at all. u-blox stamped sensor_gnss_relative with a parse-time timestamp_sample and an iTOW time_utc_usec, so the relative position could not be delay-compensated or PPS-aligned.

Solution

Drivers publish the raw baseline heading with heading_offset = NaN; a driver that already reports a body-frame heading keeps a finite heading_offset and is left alone. vehicle_gps_position resolves the receiver's slot by device_id, rotates the baseline into the body frame before blending and before publishing vehicle_gnss_heading, and hands the offset to EKF2 through the message. The baseline yaw is the yaw of the rotated x-axis; a vertical baseline yields no heading. Septentrio receivers get their attitude offsets zeroed. With a real time_utc_usec on sensor_gnss_relative, PPS correction now applies on the relative-position path as well.

Behavior change for CAN nodes running older firmware: they still apply their own GPS_YAW_OFFSET, which must be 0 when SENS_GPSn_ROT is set.

…opic

Dual-antenna heading was only reachable through the blended
vehicle_gps_position, so it carried the position timestamp, followed the
position blending weights and was published at the position rate.
vehicle_gnss_heading carries one selected source at a time, preferring
sensor_gnss_relative (callback driven) and falling back to
sensor_gps.heading for receivers that do not publish it. Per-receiver
delay and the driver heading offset are resolved by device_id; a sample
timestamp within 10 ms of the publish timestamp is treated as unset so
the configured delay applies.
Heading rode on gnssSample and was only fused when a position sample was
popped: faster heading was dropped by the observation rate limit, its
timestamp was the position timestamp and a position outage blocked the
start. gnssYawSample has its own TimestampedRingBuffer and pop. Non-finite
yaw is rejected at the interface and a NaN heading_offset is treated as
zero instead of reaching the fusion Jacobian. The test simulator feeds
heading as an independent sensor.
…NS_GPSn_ROT

The antenna baseline orientation lived in three places depending on the
transport: the serial driver (GPS_YAW_OFFSET), the Septentrio receiver
(SEP_YAW_OFFS, SEP_PITCH_OFFS) and EKF2 for anything arriving with a NaN
offset, in practice CAN receivers (EKF2_GPS_YAW_OFF). A second receiver
with a different baseline had no parameter at all. Drivers now publish
the raw baseline heading and vehicle_gps_position rotates it into the
body frame from the receiver's SENS_GPSn_* slot, the same Rotation enum
the other sensors use, before blending and before publishing
vehicle_gnss_heading. Bumps PX4-GPSDrivers so sensor_gnss_relative
carries a real UTC time and no parse-time sample timestamp, which lets
the relative-position heading be delay-compensated and PPS-aligned.
@github-actions github-actions Bot added kind:feature Request or change that adds new functionality. kind:test Adds or improves tests. scope:drivers Device drivers and hardware interfaces. scope:sensors Sensor pipeline, calibration, voting, or sensor validation. scope:estimation EKF, local position, attitude, wind, bias, or aiding logic. scope:uorb uORB messages, generated interfaces, or message translation. scope:logging ULog, logger, replay, events, or diagnostics. scope:parameters Parameter definitions, metadata, migration, or defaults. scope:testing Unit, integration, fuzzing, or test data. scope:docs labels Sep 10, 2026
@dakejahl
dakejahl marked this pull request as draft September 10, 2026 04:37
@github-actions

Copy link
Copy Markdown
Contributor

🔎 FLASH Analysis

px4_fmu-v5x [Total VM Diff: 2440 byte (0.12 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.1% +2.38Ki  +0.1% +2.38Ki    .text
    [NEW]    +580  [NEW]    +580    sensors::VehicleGPSPosition::UpdateGnssHeading()
    [NEW]    +444  [NEW]    +444    sensors::VehicleGPSPosition::updateBaselineRotation()
    [NEW]    +436  [NEW]    +436    EstimatorInterface::setGnssYawData()
     +24%    +284   +24%    +284    Ekf::controlGpsFusion()
   -99.9%    +196 -99.9%    +196    [102 Others]
    [NEW]    +160  [NEW]    +160    sensors::VehicleGPSPosition::applyBaselineRotation()
     +69%    +150   +69%    +150    sensors::VehicleGPSPosition::ParametersUpdate()
    +0.1%    +136  +0.1%    +136    [section .text]
     +22%    +128   +22%    +128    sensors::VehicleGPSPosition::VehicleGPSPosition()
    [NEW]    +124  [NEW]    +124    EKF2::UpdateGnssYawSample()
    [NEW]    +108  [NEW]    +108    GPSDriverUBX::utcFromItow()
     +60%     +98   +60%     +98    sensors::VehicleGPSPosition::updateParamsImpl()
    [NEW]     +68  [NEW]     +68    sensors::VehicleGPSPosition::resolveSampleTimestamp()
    [NEW]     +62  [NEW]     +62    sensors::VehicleGPSPosition::findGpsInstance()
    [NEW]     +54  [NEW]     +54    sensors::VehicleGPSPosition::findParamSlot()
   -11.8%     -48 -11.8%     -48    Ekf::controlGnssYawFusion()
    -1.5%     -56  -1.5%     -56    Ekf::Ekf()
    -1.2%     -60  -1.2%     -60    Ekf::controlMagFusion()
   -12.0%     -80 -12.0%     -80    EstimatorInterface::setGpsData()
    -5.7%    -108  -5.7%    -108    GPS::run()
   -39.1%    -236 -39.1%    -236    EKF2::UpdateGpsSample()
  +0.1% +1.36Ki  [ = ]       0    .debug_abbrev
  +0.1%    +128  [ = ]       0    .debug_aranges
  +0.1%    +512  [ = ]       0    .debug_frame
  +0.2% +53.7Ki  [ = ]       0    .debug_info
  +0.1% +5.57Ki  [ = ]       0    .debug_line
  +0.2% +7.11Ki  [ = ]       0    .debug_loclists
  +0.1%    +590  [ = ]       0    .debug_rnglists
  +0.3% +10.2Ki  [ = ]       0    .debug_str
    +100%      +1  [ = ]       0    [Unmapped]
    +0.3% +10.2Ki  [ = ]       0    [section .debug_str]
  -0.8%      -2  [ = ]       0    .shstrtab
  +0.1%    +590  [ = ]       0    .strtab
    [DEL]     -11  [ = ]       0    CSWTCH.237
    [NEW]     +11  [ = ]       0    CSWTCH.238
    [NEW]     +32  [ = ]       0    EKF2::UpdateGnssYawSample()
    -6.2%      -4  [ = ]       0    Ekf::controlGnssYawFusion()
    +6.0%      +3  [ = ]       0    Ekf::updateGnssYaw()
    [NEW]     +77  [ = ]       0    EstimatorInterface::setGnssYawData()
    [NEW]     +34  [ = ]       0    GPSDriverUBX::utcFromItow()
    +0.1%     +36  [ = ]       0    [section .strtab]
   -40.0%     -16  [ = ]       0    __hrt_call_enter_veneer
    [NEW]     +27  [ = ]       0    __orb_vehicle_gnss_heading
    +100%     +16  [ = ]       0    __strcmp_veneer
   -33.3%     -11  [ = ]       0    kAutoDetectModes
    [NEW]     +71  [ = ]       0    sensors::VehicleGPSPosition::UpdateGnssHeading()
    [NEW]     +82  [ = ]       0    sensors::VehicleGPSPosition::applyBaselineRotation()
    [NEW]     +51  [ = ]       0    sensors::VehicleGPSPosition::findGpsInstance()
    [NEW]     +51  [ = ]       0    sensors::VehicleGPSPosition::findParamSlot()
    [NEW]     +60  [ = ]       0    sensors::VehicleGPSPosition::resolveSampleTimestamp()
    [NEW]     +81  [ = ]       0    sensors::VehicleGPSPosition::updateBaselineRotation()
  +0.1%    +416  [ = ]       0    .symtab
    [DEL]     -32  [ = ]       0    CSWTCH.237
    [NEW]     +48  [ = ]       0    CSWTCH.238
    [NEW]     +48  [ = ]       0    EKF2::UpdateGnssYawSample()
     +50%     +16  [ = ]       0    EKF2::print_usage()
   -50.0%     -16  [ = ]       0    EKF2::updateParamsImpl()
   -25.0%     -16  [ = ]       0    Ekf::isYawFailure()
    +100%     +16  [ = ]       0    Ekf::stopMagFusion()
     +11%     +16  [ = ]       0    Ekf::updateAidSourceStatus<>()
   -25.0%     -16  [ = ]       0    Ekf::updateHorizontalDeadReckoningstatus()
    +100%     +16  [ = ]       0    Ekf::updateVerticalDeadReckoningStatus()
    [NEW]     +48  [ = ]       0    EstimatorInterface::setGnssYawData()
   -50.0%     -16  [ = ]       0    FieldSensorBiasEstimator::updateEstimate()
     +50%     +16  [ = ]       0    GPSDriverUBX::activateRTCMOutput()
   -14.3%     -16  [ = ]       0    GPSDriverUBX::payloadRxDone()
    [NEW]     +48  [ = ]       0    GPSDriverUBX::utcFromItow()
   -33.3%     -16  [ = ]       0    RTCM_BASE_MSGOUT_I2C
   -85.5%    +336  [ = ]       0    [18 Others]
    -0.1%     -16  [ = ]       0    [section .symtab]
   -20.0%     -48  [ = ]       0    ___ZL19param_get_cplusplustPf.isra.0_veneer
     +50%     +16  [ = ]       0    ____aeabi_l2f_veneer
   -25.0%     -16  [ = ]       0    __file_get2_veneer
   +18% +1.62Ki  [ = ]       0    [Unmapped]
  +0.2% +84.1Ki  +0.1% +2.38Ki    TOTAL

px4_fmu-v6x [Total VM Diff: 2064 byte (0.11 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.1% +2.02Ki  +0.1% +2.02Ki    .text
    [NEW]    +580  [NEW]    +580    sensors::VehicleGPSPosition::UpdateGnssHeading()
    [NEW]    +444  [NEW]    +444    sensors::VehicleGPSPosition::updateBaselineRotation()
    [NEW]    +436  [NEW]    +436    EstimatorInterface::setGnssYawData()
     +24%    +284   +24%    +284    Ekf::controlGpsFusion()
   -99.8%    +194 -99.8%    +194    [128 Others]
    [NEW]    +160  [NEW]    +160    sensors::VehicleGPSPosition::applyBaselineRotation()
     +69%    +150   +69%    +150    sensors::VehicleGPSPosition::ParametersUpdate()
     +22%    +128   +22%    +128    sensors::VehicleGPSPosition::VehicleGPSPosition()
    [NEW]    +124  [NEW]    +124    EKF2::UpdateGnssYawSample()
    [NEW]    +108  [NEW]    +108    GPSDriverUBX::utcFromItow()
     +60%     +98   +60%     +98    sensors::VehicleGPSPosition::updateParamsImpl()
    +0.0%     +68  +0.0%     +68    [section .text]
    [NEW]     +68  [NEW]     +68    sensors::VehicleGPSPosition::resolveSampleTimestamp()
    [NEW]     +62  [NEW]     +62    sensors::VehicleGPSPosition::findGpsInstance()
    -1.5%     -56  -1.5%     -56    Ekf::Ekf()
    -1.2%     -60  -1.2%     -60    Ekf::controlMagFusion()
   -12.0%     -80 -12.0%     -80    EstimatorInterface::setGpsData()
   -66.7%     -80 -66.7%     -80    septentrio::SeptentrioDriver::reset_gps_state_message()
    -5.7%    -108  -5.7%    -108    GPS::run()
    -0.1%    -220  -0.1%    -220    g_cromfs_image
   -39.1%    -236 -39.1%    -236    EKF2::UpdateGpsSample()
  +0.1% +1.37Ki  [ = ]       0    .debug_abbrev
  +0.1%    +128  [ = ]       0    .debug_aranges
  +0.1%    +512  [ = ]       0    .debug_frame
  +0.2% +49.6Ki  [ = ]       0    .debug_info
  +0.1% +5.41Ki  [ = ]       0    .debug_line
    [DEL]      -1  [ = ]       0    [Unmapped]
    +0.1% +5.41Ki  [ = ]       0    [section .debug_line]
  +0.2% +6.93Ki  [ = ]       0    .debug_loclists
  +0.1%    +543  [ = ]       0    .debug_rnglists
  +0.3% +10.1Ki  [ = ]       0    .debug_str
  -0.8%      -2  [ = ]       0    .shstrtab
  +0.1%    +590  [ = ]       0    .strtab
    [DEL]     -11  [ = ]       0    CSWTCH.237
    [NEW]     +11  [ = ]       0    CSWTCH.238
    [NEW]     +11  [ = ]       0    CSWTCH.273
    [NEW]     +11  [ = ]       0    CSWTCH.274
    [DEL]     -11  [ = ]       0    CSWTCH.276
    [DEL]     -11  [ = ]       0    CSWTCH.277
    [NEW]     +32  [ = ]       0    EKF2::UpdateGnssYawSample()
    -6.2%      -4  [ = ]       0    Ekf::controlGnssYawFusion()
    +6.0%      +3  [ = ]       0    Ekf::updateGnssYaw()
    [NEW]     +77  [ = ]       0    EstimatorInterface::setGnssYawData()
    [NEW]     +34  [ = ]       0    GPSDriverUBX::utcFromItow()
    +0.1%     +25  [ = ]       0    [section .strtab]
    [NEW]     +27  [ = ]       0    __orb_vehicle_gnss_heading
    [NEW]     +71  [ = ]       0    sensors::VehicleGPSPosition::UpdateGnssHeading()
    [NEW]     +82  [ = ]       0    sensors::VehicleGPSPosition::applyBaselineRotation()
    [NEW]     +51  [ = ]       0    sensors::VehicleGPSPosition::findGpsInstance()
    [NEW]     +51  [ = ]       0    sensors::VehicleGPSPosition::findParamSlot()
    [NEW]     +60  [ = ]       0    sensors::VehicleGPSPosition::resolveSampleTimestamp()
    [NEW]     +81  [ = ]       0    sensors::VehicleGPSPosition::updateBaselineRotation()
  +0.1%    +448  [ = ]       0    .symtab
    [DEL]     -32  [ = ]       0    CSWTCH.237
    [NEW]     +48  [ = ]       0    CSWTCH.238
    [NEW]     +32  [ = ]       0    CSWTCH.273
    [NEW]     +32  [ = ]       0    CSWTCH.274
    [DEL]     -32  [ = ]       0    CSWTCH.276
    [DEL]     -32  [ = ]       0    CSWTCH.277
   -50.0%     -16  [ = ]       0    ConstLayer::contains()
     +50%     +16  [ = ]       0    ConstLayer::store()
   -20.0%     -16  [ = ]       0    DynamicSparseLayer::DynamicSparseLayer()
    [NEW]     +48  [ = ]       0    EKF2::UpdateGnssYawSample()
     +50%     +16  [ = ]       0    EKF2::print_usage()
   -50.0%     -16  [ = ]       0    EKF2::updateParamsImpl()
   -25.0%     -16  [ = ]       0    Ekf::isYawFailure()
    +100%     +16  [ = ]       0    Ekf::stopMagFusion()
     +11%     +16  [ = ]       0    Ekf::updateAidSourceStatus<>()
   -25.0%     -16  [ = ]       0    Ekf::updateHorizontalDeadReckoningstatus()
    +100%     +16  [ = ]       0    Ekf::updateVerticalDeadReckoningStatus()
    [NEW]     +48  [ = ]       0    EstimatorInterface::setGnssYawData()
   -50.0%     -16  [ = ]       0    FieldSensorBiasEstimator::updateEstimate()
     +50%     +16  [ = ]       0    GPSDriverUBX::activateRTCMOutput()
   -86.5%    +336  [ = ]       0    [21 Others]
   +44% +1.98Ki  [ = ]       0    [Unmapped]
  +0.2% +79.6Ki  +0.1% +2.02Ki    TOTAL

Updated: 2026-09-10T04:39:48

@github-actions

Copy link
Copy Markdown
Contributor

No broken links found in changed files.

@dakejahl

Copy link
Copy Markdown
Contributor Author

Folded into #27102.

@dakejahl dakejahl closed this Sep 10, 2026
@dakejahl
dakejahl deleted the dakejahl/sens-gps-rot branch September 10, 2026 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind:feature Request or change that adds new functionality. kind:test Adds or improves tests. scope:docs scope:drivers Device drivers and hardware interfaces. scope:estimation EKF, local position, attitude, wind, bias, or aiding logic. scope:logging ULog, logger, replay, events, or diagnostics. scope:parameters Parameter definitions, metadata, migration, or defaults. scope:sensors Sensor pipeline, calibration, voting, or sensor validation. scope:testing Unit, integration, fuzzing, or test data. scope:uorb uORB messages, generated interfaces, or message translation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant