feat(fusion): fuse Doppler velocity in ESKF
This commit is contained in:
@@ -19,14 +19,17 @@ gps_system.c
|
||||
-> timestamped IMU queue -> navigation_fusion prediction
|
||||
-> 20 ms tracking mailbox -> C/N0 + Doppler/range-rate
|
||||
-> GPS events -> coherent position/velocity WLS -> gps_nav_solution_t
|
||||
-> valid GPS position -> fusion_gps_measurement_t -> navigation_fusion update
|
||||
-> valid GPS position + Doppler velocity -> fusion_gps_measurement_t
|
||||
-> same-epoch navigation_fusion position/velocity update
|
||||
-> navigation_fusion_output_t -> corrected lat/lon publish
|
||||
```
|
||||
|
||||
Doppler WLS'nin ECEF velocity, speed ve residual sonucu
|
||||
`fusion_gps_measurement_t` icine aktarilir ve UART'ta yayinlanir. Mevcut
|
||||
`navigation_fusion` update'i henuz bu velocity alanlarini state correction
|
||||
olarak kullanmaz; ESKF'ye giren aktif GPS olcumu position'dir.
|
||||
Doppler WLS'nin ECEF velocity, covariance, speed ve residual sonucu
|
||||
`fusion_gps_measurement_t` icine aktarilir. GPS adapter velocity ile
|
||||
covariance'i ECEF'ten ENU'ya dondurur; position ve velocity ayni capture
|
||||
epoch'unda ESKF'ye uygulanir, sonra daha yeni IMU history replay edilir.
|
||||
Position ve velocity'nin ayri NIS kapilari/sayaclari vardir; bir velocity
|
||||
outlier'i ayni epoch'taki gecerli position update'ini geri almaz.
|
||||
|
||||
Kaynak ve public header agaci ayni modul sinirlarini izler:
|
||||
|
||||
@@ -791,9 +794,21 @@ weight = 1 / sigma_rate^2
|
||||
|
||||
En az dort uydu gerekir. Dort uyduda velocity residual serbestlik derecesi
|
||||
yoktur; bes uydu ve dusuk PDOP'ta bile zayif geometri velocity gurultusunu
|
||||
buyutebilir. Sonuc `velocity_valid`, ECEF velocity, `speed_mps` ve
|
||||
`velocity_residual_rms_mps` alanlarinda yayinlanir, fakat henuz ESKF update'ine
|
||||
fusion edilmez.
|
||||
buyutebilir. WLS, `inverse(H'WH)` matrisinden ECEF velocity covariance uretir.
|
||||
`N>4` ise weighted post-fit reduced chi-square yalniz covariance'i buyutebilir;
|
||||
tek bir sessiz epoch formal belirsizligi kucultemez. Sonuc `velocity_valid`,
|
||||
ECEF velocity/covariance, `speed_mps` ve `velocity_residual_rms_mps`
|
||||
alanlarinda yayinlanir.
|
||||
|
||||
Fusion adapter covariance'i `R_ecef_to_enu * P * R^T` ile ENU'ya dondurur.
|
||||
2026-07-25 sabit kart verisinde zayif geometriyle gorulen
|
||||
`p95=6.020 m/s` speed hatasi nedeniyle default alt sinirlar yatay eksenlerde
|
||||
`2.0 m/s`, dikey eksende `3.0 m/s`'dir. Bunlar IIR ile korelasyonlu ard arda
|
||||
epoch'larin ESKF'yi asiri guvenle cekmesini onler; formal covariance daha
|
||||
buyukse aynen korunur. Velocity innovation 3-DOF `%99.9`
|
||||
`NIS=16.266` kapisindan gecer. Kabul/red sayilari ve son velocity NIS'i
|
||||
`[FUSION]` UART status satirinda `velocity=accepted/rejected` ve `vel_nis`
|
||||
olarak yayinlanir.
|
||||
|
||||
- Konum cozumu URA/elevation/CN0 agirlikli WLS kullanir; raporlanan DOP degerleri ise bu normalize relative weight'lerden uretilmez. GDOP/PDOP/TDOP agirliksiz geometri matrisinden, HDOP/VDOP de ayni covariance ECEF'ten lokal ENU eksenlerine donduruldukten sonra hesaplanir. Boylece loglanan degerler klasik geometrik DOP anlamini korur.
|
||||
- Troposfer correction her satellite icin bagimsiz hesaplanir ve dort satellite ile de aktiftir. Ilk konumsuz iteration sabit baslangic correction'i kullanir; receiver konumu olustuktan sonra elevation ve metre cinsinden receiver height modele verilir. Model kilometre bekledigi icin height donusumu yapilir ve dusuk elevation'da gecerli olabilen 5 m uzeri delay degerleri artik keyfi olarak sifirlanmaz.
|
||||
@@ -1071,8 +1086,11 @@ make -C hardware/external/gps/rtl/trck_src/tb test
|
||||
```
|
||||
|
||||
`run` ve `sanitize`, C/N0 estimator'i, Q7 Doppler/range-rate donusumu,
|
||||
coherent mailbox read'i, weighted velocity WLS ve satellite velocity/clock
|
||||
drift hesaplarini kapsar. RTL Questa/vlog-vsim testi prompt akumulasyonu,
|
||||
coherent mailbox read'i, weighted velocity WLS/covariance ve satellite
|
||||
velocity/clock drift hesaplarini kapsar. Fusion regression'i ayrica
|
||||
ECEF->ENU velocity/covariance rotasyonunu, velocity NIS kabul/redini ve ayni
|
||||
epoch position+velocity delayed replay esdegerligini kapsar. RTL
|
||||
Questa/vlog-vsim testi prompt akumulasyonu,
|
||||
20 ms hold, synchronizer settle, atomik APB capture ve interrupt yolunu
|
||||
kapsar.
|
||||
|
||||
@@ -1110,8 +1128,12 @@ Kart ve GPS anteni sabitken canli tracking mailbox olcumlerinde:
|
||||
C/N0 ve Doppler verisi fiziksel olarak makul, IIR velocity gurultusunu
|
||||
azaltiyor ve C/N0 weighting ornek epoch'ta sonucu iyilestiriyor. Buna ragmen
|
||||
position WLS'deki pseudorange/code-phase hata ve zayif geometriyi tek basina
|
||||
duzeltmiyor. Doppler velocity de henuz ESKF state update'inde kullanilmadigi
|
||||
icin `imu_corrected` cikisina dogrudan faydasi yoktur.
|
||||
duzeltmiyor. Doppler velocity artik ESKF state update'ine baglidir; formal
|
||||
covariance, kart verisine dayali floor ve ayri NIS gate ile asiri guven
|
||||
sinirlanir. Bu yeni software'in gercek kartta `vel_nis` ve
|
||||
`velocity=accepted/rejected` sayaclariyla saha A/B sonucu henuz olculmedi;
|
||||
yukaridaki board istatistikleri ESKF entegrasyonundan onceki ham velocity
|
||||
dogrulamasidir.
|
||||
|
||||
### 2026-07-08 ACQ Debug Notu
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ typedef struct {
|
||||
double gravity_mps2;
|
||||
double max_propagation_step_s;
|
||||
double position_nis_gate;
|
||||
double velocity_nis_gate;
|
||||
double covariance_diagonal_floor;
|
||||
} eskf_config_t;
|
||||
|
||||
@@ -40,9 +41,12 @@ typedef struct {
|
||||
double last_linear_accel_nav_mps2[ESKF_VECTOR_DIM];
|
||||
double last_unbiased_gyro_rps[ESKF_VECTOR_DIM];
|
||||
double last_position_nis;
|
||||
double last_velocity_nis;
|
||||
uint64_t propagation_count;
|
||||
uint64_t position_update_accepted_count;
|
||||
uint64_t position_update_rejected_count;
|
||||
uint64_t velocity_update_accepted_count;
|
||||
uint64_t velocity_update_rejected_count;
|
||||
bool initialized;
|
||||
} eskf_t;
|
||||
|
||||
@@ -84,6 +88,13 @@ eskf_update_result_t eskf_position_update(
|
||||
const double covariance_m2[ESKF_VECTOR_DIM * ESKF_VECTOR_DIM],
|
||||
double* nis_out);
|
||||
|
||||
eskf_update_result_t eskf_velocity_update(
|
||||
eskf_t* filter,
|
||||
const eskf_config_t* config,
|
||||
const double velocity_mps[ESKF_VECTOR_DIM],
|
||||
const double covariance_mps2[ESKF_VECTOR_DIM * ESKF_VECTOR_DIM],
|
||||
double* nis_out);
|
||||
|
||||
/* Re-anchor position after a higher-level fusion timeout has established that
|
||||
* repeated, valid position observations are mutually coherent. Velocity,
|
||||
* attitude and IMU biases are preserved; position cross-covariances are
|
||||
|
||||
@@ -80,6 +80,9 @@ typedef struct {
|
||||
uint64_t position_pending_overflow;
|
||||
uint64_t position_accepted;
|
||||
uint64_t position_recovery_count;
|
||||
uint64_t velocity_received;
|
||||
uint64_t velocity_nis_rejected;
|
||||
uint64_t velocity_accepted;
|
||||
uint64_t replay_count;
|
||||
uint64_t replayed_imu_count;
|
||||
uint64_t zero_velocity_update_count;
|
||||
@@ -104,7 +107,7 @@ typedef struct {
|
||||
fusion_runtime_t checkpoint;
|
||||
fusion_calibration_accumulator_t calibration;
|
||||
fusion_imu_measurement_t imu_history[FUSION_IMU_HISTORY_CAPACITY];
|
||||
fusion_position_measurement_t pending_positions[FUSION_POSITION_PENDING_CAPACITY];
|
||||
fusion_navigation_measurement_t pending_positions[FUSION_POSITION_PENDING_CAPACITY];
|
||||
fusion_position_measurement_t initial_position;
|
||||
fusion_position_measurement_t position_recovery_candidate;
|
||||
fusion_engine_diagnostics_t diagnostics;
|
||||
@@ -133,6 +136,13 @@ bool fusion_engine_push_position(
|
||||
const fusion_position_measurement_t* measurement,
|
||||
eskf_update_result_t* update_result_out,
|
||||
double* nis_out);
|
||||
bool fusion_engine_push_navigation(
|
||||
fusion_engine_t* engine,
|
||||
const fusion_navigation_measurement_t* measurement,
|
||||
eskf_update_result_t* position_update_result_out,
|
||||
double* position_nis_out,
|
||||
eskf_update_result_t* velocity_update_result_out,
|
||||
double* velocity_nis_out);
|
||||
|
||||
bool fusion_engine_get_runtime(const fusion_engine_t* engine,
|
||||
fusion_runtime_t* runtime_out);
|
||||
|
||||
@@ -9,6 +9,9 @@ typedef struct {
|
||||
double minimum_horizontal_std_m;
|
||||
double minimum_vertical_std_m;
|
||||
double maximum_position_std_m;
|
||||
double minimum_horizontal_velocity_std_mps;
|
||||
double minimum_vertical_velocity_std_mps;
|
||||
double maximum_velocity_std_mps;
|
||||
} fusion_gps_adapter_config_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -27,6 +30,10 @@ bool fusion_gps_adapter_init(fusion_gps_adapter_t* adapter,
|
||||
bool fusion_gps_adapter_convert(fusion_gps_adapter_t* adapter,
|
||||
const fusion_gps_measurement_t* gps,
|
||||
fusion_position_measurement_t* position_out);
|
||||
bool fusion_gps_adapter_convert_navigation(
|
||||
fusion_gps_adapter_t* adapter,
|
||||
const fusion_gps_measurement_t* gps,
|
||||
fusion_navigation_measurement_t* navigation_out);
|
||||
bool fusion_gps_adapter_local_to_geodetic(
|
||||
const fusion_gps_adapter_t* adapter,
|
||||
const double position_enu_m[3],
|
||||
|
||||
@@ -22,6 +22,7 @@ typedef struct {
|
||||
double residual_rms_m;
|
||||
double chi_square_ratio;
|
||||
double velocity_ecef_mps[3];
|
||||
double velocity_covariance_ecef_mps2[3][3];
|
||||
double speed_mps;
|
||||
double velocity_residual_rms_mps;
|
||||
unsigned int used_satellites;
|
||||
@@ -49,4 +50,16 @@ typedef struct {
|
||||
bool source_valid;
|
||||
} fusion_position_measurement_t;
|
||||
|
||||
/*
|
||||
* Position and Doppler velocity from one GNSS solve. Keeping them in one
|
||||
* message makes the fixed-lag coordinator apply both observations at exactly
|
||||
* the captured GPS epoch before replaying newer IMU samples.
|
||||
*/
|
||||
typedef struct {
|
||||
fusion_position_measurement_t position;
|
||||
double velocity_enu_mps[3];
|
||||
double velocity_covariance_enu_mps2[9];
|
||||
bool velocity_valid;
|
||||
} fusion_navigation_measurement_t;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,10 +20,13 @@ typedef struct {
|
||||
double position_std_m[3];
|
||||
double speed_mps;
|
||||
double last_position_nis;
|
||||
double last_velocity_nis;
|
||||
fusion_motion_state_t motion_state;
|
||||
uint64_t gps_accepted;
|
||||
uint64_t gps_nis_rejected;
|
||||
uint64_t gps_position_recoveries;
|
||||
uint64_t gps_velocity_accepted;
|
||||
uint64_t gps_velocity_nis_rejected;
|
||||
uint64_t zero_update_count;
|
||||
uint64_t replay_count;
|
||||
uint64_t stale_gps_count;
|
||||
@@ -54,8 +57,9 @@ void navigation_fusion_set_platform_landed(
|
||||
* ESKF observation and is never rate-selected in this layer. */
|
||||
bool navigation_fusion_push_imu(const imu_sample_t* measurement);
|
||||
|
||||
/* Observation input: convert a valid LLH fix to local ENU, apply it at the
|
||||
* captured GPS epoch, then replay newer IMU history to current time. */
|
||||
/* Observation input: convert a valid LLH fix and optional Doppler velocity to
|
||||
* local ENU, apply both at the captured GPS epoch, then replay newer IMU
|
||||
* history to current time. */
|
||||
bool navigation_fusion_push_gps(const fusion_gps_measurement_t* measurement);
|
||||
|
||||
/* local_valid can exist without GPS. global_valid/valid require a GPS origin. */
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
double ecef_m[3];
|
||||
double clock_bias_m;
|
||||
double velocity_ecef_mps[3];
|
||||
double velocity_covariance_ecef_mps2[3][3];
|
||||
double clock_drift_mps;
|
||||
double speed_mps;
|
||||
double velocity_residual_rms_mps;
|
||||
|
||||
@@ -55,6 +55,7 @@ bool gps_tracking_velocity_wls(
|
||||
const gps_tracking_measurement_t measurements[],
|
||||
unsigned int count,
|
||||
double receiver_velocity_ecef_mps_out[3],
|
||||
double receiver_velocity_covariance_ecef_mps2_out[3][3],
|
||||
double* receiver_clock_drift_mps_out,
|
||||
double* residual_rms_mps_out);
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ bool gps_fusion_build_measurement(
|
||||
memcpy(measurement_out->velocity_ecef_mps,
|
||||
solution->velocity_ecef_mps,
|
||||
sizeof(measurement_out->velocity_ecef_mps));
|
||||
memcpy(measurement_out->velocity_covariance_ecef_mps2,
|
||||
solution->velocity_covariance_ecef_mps2,
|
||||
sizeof(measurement_out->velocity_covariance_ecef_mps2));
|
||||
measurement_out->speed_mps = solution->speed_mps;
|
||||
measurement_out->velocity_residual_rms_mps =
|
||||
solution->velocity_residual_rms_mps;
|
||||
|
||||
@@ -116,15 +116,19 @@ void gps_system_uart_publish_periodic_fusion_status(
|
||||
{
|
||||
GPS_LOG(DEBUG_EVENTS, GPS_LOG_CAT_FUSION,
|
||||
"state=%s lat=%.10lf lon=%.10lf height=%.3lf "
|
||||
"speed=%.3lf nis=%.3lf gps=%lu/%lu recoveries=%lu "
|
||||
"speed=%.3lf pos_nis=%.3lf vel_nis=%.3lf "
|
||||
"gps=%lu/%lu velocity=%lu/%lu recoveries=%lu "
|
||||
"zupt=%lu replay=%lu "
|
||||
"sigma=%.2lf,%.2lf,%.2lf imu=%lu fused_imu=%lu "
|
||||
"overflow=%lu missed=%lu\r\n",
|
||||
fusion_motion_state_name(output->motion_state),
|
||||
output->latitude_deg, output->longitude_deg, output->height_m,
|
||||
output->speed_mps, output->last_position_nis,
|
||||
output->last_velocity_nis,
|
||||
(unsigned long)output->gps_accepted,
|
||||
(unsigned long)output->gps_nis_rejected,
|
||||
(unsigned long)output->gps_velocity_accepted,
|
||||
(unsigned long)output->gps_velocity_nis_rejected,
|
||||
(unsigned long)output->gps_position_recoveries,
|
||||
(unsigned long)output->zero_update_count,
|
||||
(unsigned long)output->replay_count,
|
||||
|
||||
@@ -224,6 +224,7 @@ void eskf_default_config(eskf_config_t* config)
|
||||
config->gravity_mps2 = 9.80665;
|
||||
config->max_propagation_step_s = 0.02;
|
||||
config->position_nis_gate = 16.26623619623813; /* chi-square 3 DOF, 99.9%. */
|
||||
config->velocity_nis_gate = 16.26623619623813; /* chi-square 3 DOF, 99.9%. */
|
||||
config->covariance_diagonal_floor = 1.0e-12;
|
||||
}
|
||||
|
||||
@@ -319,6 +320,7 @@ bool eskf_initialize(eskf_t* filter,
|
||||
}
|
||||
covariance_symmetrize_and_floor(filter, config->covariance_diagonal_floor);
|
||||
filter->last_position_nis = NAN;
|
||||
filter->last_velocity_nis = NAN;
|
||||
filter->initialized = true;
|
||||
return true;
|
||||
}
|
||||
@@ -806,6 +808,54 @@ eskf_update_result_t eskf_position_update(
|
||||
return ESKF_UPDATE_ACCEPTED;
|
||||
}
|
||||
|
||||
eskf_update_result_t eskf_velocity_update(
|
||||
eskf_t* filter,
|
||||
const eskf_config_t* config,
|
||||
const double velocity_mps[3],
|
||||
const double covariance_mps2[9],
|
||||
double* nis_out)
|
||||
{
|
||||
if(nis_out != NULL)
|
||||
{
|
||||
*nis_out = NAN;
|
||||
}
|
||||
if((filter == NULL) || !filter->initialized || (config == NULL) ||
|
||||
!vector_is_finite(velocity_mps, 3u) ||
|
||||
!vector_is_finite(covariance_mps2, 9u))
|
||||
{
|
||||
return ESKF_UPDATE_INVALID;
|
||||
}
|
||||
double residual[3];
|
||||
for(unsigned int i = 0u; i < 3u; i++)
|
||||
{
|
||||
residual[i] = velocity_mps[i] - filter->velocity_mps[i];
|
||||
}
|
||||
double nis = NAN;
|
||||
if(!eskf_selected_update(
|
||||
filter, ESKF_VELOCITY_INDEX, residual, covariance_mps2,
|
||||
true, config->velocity_nis_gate, &nis))
|
||||
{
|
||||
filter->last_velocity_nis = nis;
|
||||
if(isfinite(nis) && (nis > config->velocity_nis_gate))
|
||||
{
|
||||
filter->velocity_update_rejected_count++;
|
||||
if(nis_out != NULL)
|
||||
{
|
||||
*nis_out = nis;
|
||||
}
|
||||
return ESKF_UPDATE_REJECTED_NIS;
|
||||
}
|
||||
return ESKF_UPDATE_INVALID;
|
||||
}
|
||||
filter->last_velocity_nis = nis;
|
||||
filter->velocity_update_accepted_count++;
|
||||
if(nis_out != NULL)
|
||||
{
|
||||
*nis_out = nis;
|
||||
}
|
||||
return ESKF_UPDATE_ACCEPTED;
|
||||
}
|
||||
|
||||
bool eskf_reset_position(
|
||||
eskf_t* filter,
|
||||
const double position_m[3],
|
||||
|
||||
@@ -176,6 +176,10 @@ bool fusion_engine_apply_imu_quality(
|
||||
static bool config_valid(const fusion_engine_config_t* config)
|
||||
{
|
||||
return (config != NULL) && (config->timebase_hz != 0u) &&
|
||||
isfinite(config->eskf.position_nis_gate) &&
|
||||
(config->eskf.position_nis_gate > 0.0) &&
|
||||
isfinite(config->eskf.velocity_nis_gate) &&
|
||||
(config->eskf.velocity_nis_gate > 0.0) &&
|
||||
isfinite(config->calibration_duration_s) &&
|
||||
(config->calibration_duration_s > 0.0) &&
|
||||
(config->calibration_min_samples >= 2u) &&
|
||||
@@ -230,6 +234,18 @@ static bool position_valid(const fusion_position_measurement_t* measurement)
|
||||
isfinite(measurement->source_quality);
|
||||
}
|
||||
|
||||
static bool navigation_valid(
|
||||
const fusion_navigation_measurement_t* measurement)
|
||||
{
|
||||
if((measurement == NULL) || !position_valid(&measurement->position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return !measurement->velocity_valid ||
|
||||
(finite_values(measurement->velocity_enu_mps, 3u) &&
|
||||
finite_values(measurement->velocity_covariance_enu_mps2, 9u));
|
||||
}
|
||||
|
||||
static void calibration_reset(fusion_engine_t* engine)
|
||||
{
|
||||
memset(&engine->calibration, 0, sizeof(engine->calibration));
|
||||
@@ -865,13 +881,17 @@ static eskf_update_result_t runtime_apply_position(
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool process_position_now(fusion_engine_t* engine,
|
||||
const fusion_position_measurement_t* measurement,
|
||||
eskf_update_result_t* result_out,
|
||||
double* nis_out)
|
||||
static bool process_navigation_now(
|
||||
fusion_engine_t* engine,
|
||||
const fusion_navigation_measurement_t* measurement,
|
||||
eskf_update_result_t* position_result_out,
|
||||
double* position_nis_out,
|
||||
eskf_update_result_t* velocity_result_out,
|
||||
double* velocity_nis_out)
|
||||
{
|
||||
const fusion_position_measurement_t* position = &measurement->position;
|
||||
if(!engine->checkpoint_valid ||
|
||||
(measurement->timestamp_ticks < engine->checkpoint.filter_timestamp_ticks))
|
||||
(position->timestamp_ticks < engine->checkpoint.filter_timestamp_ticks))
|
||||
{
|
||||
engine->diagnostics.position_stale_rejected++;
|
||||
return false;
|
||||
@@ -882,7 +902,7 @@ static bool process_position_now(fusion_engine_t* engine,
|
||||
for(unsigned int i = 0 ; i < engine->imu_history_count ; i++)
|
||||
{
|
||||
const fusion_imu_measurement_t* imu = history_at(engine, i);
|
||||
if(imu->timestamp_ticks > measurement->timestamp_ticks)
|
||||
if(imu->timestamp_ticks > position->timestamp_ticks)
|
||||
{
|
||||
next_imu = imu;
|
||||
break;
|
||||
@@ -894,7 +914,7 @@ static bool process_position_now(fusion_engine_t* engine,
|
||||
}
|
||||
replayed++;
|
||||
}
|
||||
if(revised.filter_timestamp_ticks < measurement->timestamp_ticks)
|
||||
if(revised.filter_timestamp_ticks < position->timestamp_ticks)
|
||||
{
|
||||
if(!revised.have_held_imu)
|
||||
{
|
||||
@@ -902,12 +922,12 @@ static bool process_position_now(fusion_engine_t* engine,
|
||||
return false;
|
||||
}
|
||||
fusion_imu_measurement_t endpoint = revised.held_imu;
|
||||
endpoint.timestamp_ticks = measurement->timestamp_ticks;
|
||||
endpoint.timestamp_ticks = position->timestamp_ticks;
|
||||
if((next_imu != NULL) &&
|
||||
(next_imu->timestamp_ticks > revised.held_imu.timestamp_ticks))
|
||||
{
|
||||
endpoint = interpolate_imu(&revised.held_imu, next_imu,
|
||||
measurement->timestamp_ticks);
|
||||
position->timestamp_ticks);
|
||||
}
|
||||
if(!runtime_propagate_to(engine, &revised, &endpoint))
|
||||
{
|
||||
@@ -915,31 +935,31 @@ static bool process_position_now(fusion_engine_t* engine,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
double nis = NAN;
|
||||
eskf_update_result_t result = runtime_apply_position(engine, &revised,
|
||||
measurement, &nis);
|
||||
if(result == ESKF_UPDATE_INVALID)
|
||||
double position_nis = NAN;
|
||||
eskf_update_result_t position_result = runtime_apply_position(
|
||||
engine, &revised, position, &position_nis);
|
||||
if(position_result == ESKF_UPDATE_INVALID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(result == ESKF_UPDATE_ACCEPTED)
|
||||
if(position_result == ESKF_UPDATE_ACCEPTED)
|
||||
{
|
||||
position_recovery_clear(engine);
|
||||
}
|
||||
else if((result == ESKF_UPDATE_REJECTED_NIS) &&
|
||||
position_recovery_ready(engine, &revised, measurement) &&
|
||||
eskf_reset_position(&revised.filter, measurement->position_enu_m,
|
||||
measurement->covariance_enu_m2))
|
||||
else if((position_result == ESKF_UPDATE_REJECTED_NIS) &&
|
||||
position_recovery_ready(engine, &revised, position) &&
|
||||
eskf_reset_position(&revised.filter, position->position_enu_m,
|
||||
position->covariance_enu_m2))
|
||||
{
|
||||
/* This is a deliberate position-state reset, not a Kalman update with
|
||||
* a disabled gate. The independent IMU-derived attitude and bias state
|
||||
* survive, while invalid position cross-covariances are discarded. */
|
||||
runtime_record_recovered_position(&revised, measurement);
|
||||
runtime_record_recovered_position(&revised, position);
|
||||
engine->diagnostics.position_recovery_count++;
|
||||
position_recovery_clear(engine);
|
||||
result = ESKF_UPDATE_ACCEPTED;
|
||||
position_result = ESKF_UPDATE_ACCEPTED;
|
||||
}
|
||||
if(result == ESKF_UPDATE_ACCEPTED)
|
||||
if(position_result == ESKF_UPDATE_ACCEPTED)
|
||||
{
|
||||
engine->diagnostics.position_accepted++;
|
||||
}
|
||||
@@ -947,9 +967,34 @@ static bool process_position_now(fusion_engine_t* engine,
|
||||
{
|
||||
engine->diagnostics.position_nis_rejected++;
|
||||
}
|
||||
|
||||
double velocity_nis = NAN;
|
||||
eskf_update_result_t velocity_result = ESKF_UPDATE_INVALID;
|
||||
if(measurement->velocity_valid)
|
||||
{
|
||||
velocity_result = eskf_velocity_update(
|
||||
&revised.filter, &engine->config.eskf,
|
||||
measurement->velocity_enu_mps,
|
||||
measurement->velocity_covariance_enu_mps2,
|
||||
&velocity_nis);
|
||||
if(velocity_result == ESKF_UPDATE_ACCEPTED)
|
||||
{
|
||||
engine->diagnostics.velocity_accepted++;
|
||||
}
|
||||
else if(velocity_result == ESKF_UPDATE_REJECTED_NIS)
|
||||
{
|
||||
engine->diagnostics.velocity_nis_rejected++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* A malformed optional Doppler covariance must not discard a valid
|
||||
* position update from the same epoch. */
|
||||
velocity_result = ESKF_UPDATE_INVALID;
|
||||
}
|
||||
}
|
||||
engine->checkpoint = revised;
|
||||
engine->checkpoint_valid = true;
|
||||
history_drop_through(engine, measurement->timestamp_ticks);
|
||||
history_drop_through(engine, position->timestamp_ticks);
|
||||
for(unsigned int i = 0 ; i < engine->imu_history_count ; i++)
|
||||
{
|
||||
if(!runtime_apply_imu(engine, &revised, history_at(engine, i), false))
|
||||
@@ -962,19 +1007,27 @@ static bool process_position_now(fusion_engine_t* engine,
|
||||
engine->current = revised;
|
||||
engine->diagnostics.replay_count++;
|
||||
engine->diagnostics.replayed_imu_count += replayed;
|
||||
if(result_out != NULL)
|
||||
if(position_result_out != NULL)
|
||||
{
|
||||
*result_out = result;
|
||||
*position_result_out = position_result;
|
||||
}
|
||||
if(nis_out != NULL)
|
||||
if(position_nis_out != NULL)
|
||||
{
|
||||
*nis_out = nis;
|
||||
*position_nis_out = position_nis;
|
||||
}
|
||||
if(velocity_result_out != NULL)
|
||||
{
|
||||
*velocity_result_out = velocity_result;
|
||||
}
|
||||
if(velocity_nis_out != NULL)
|
||||
{
|
||||
*velocity_nis_out = velocity_nis;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pending_insert(fusion_engine_t* engine,
|
||||
const fusion_position_measurement_t* measurement)
|
||||
const fusion_navigation_measurement_t* measurement)
|
||||
{
|
||||
if(engine->pending_position_count >= FUSION_POSITION_PENDING_CAPACITY)
|
||||
{
|
||||
@@ -983,8 +1036,8 @@ static bool pending_insert(fusion_engine_t* engine,
|
||||
}
|
||||
unsigned int index = engine->pending_position_count;
|
||||
while((index != 0u) &&
|
||||
(engine->pending_positions[index - 1u].timestamp_ticks >
|
||||
measurement->timestamp_ticks))
|
||||
(engine->pending_positions[index - 1u].position.timestamp_ticks >
|
||||
measurement->position.timestamp_ticks))
|
||||
{
|
||||
engine->pending_positions[index] = engine->pending_positions[index - 1u];
|
||||
index--;
|
||||
@@ -997,16 +1050,18 @@ static bool pending_insert(fusion_engine_t* engine,
|
||||
static void process_ready_pending_positions(fusion_engine_t* engine)
|
||||
{
|
||||
while((engine->pending_position_count != 0u) &&
|
||||
(engine->pending_positions[0].timestamp_ticks <=
|
||||
(engine->pending_positions[0].position.timestamp_ticks <=
|
||||
engine->current.filter_timestamp_ticks))
|
||||
{
|
||||
fusion_position_measurement_t measurement = engine->pending_positions[0];
|
||||
fusion_navigation_measurement_t measurement =
|
||||
engine->pending_positions[0];
|
||||
for(unsigned int i = 1u ; i < engine->pending_position_count ; i++)
|
||||
{
|
||||
engine->pending_positions[i - 1u] = engine->pending_positions[i];
|
||||
}
|
||||
engine->pending_position_count--;
|
||||
(void)process_position_now(engine, &measurement, NULL, NULL);
|
||||
(void)process_navigation_now(
|
||||
engine, &measurement, NULL, NULL, NULL, NULL);
|
||||
if(engine->state == FUSION_ENGINE_FAULT)
|
||||
{
|
||||
return;
|
||||
@@ -1056,6 +1111,7 @@ bool fusion_engine_push_position(
|
||||
eskf_update_result_t* update_result_out,
|
||||
double* nis_out)
|
||||
{
|
||||
fusion_navigation_measurement_t navigation;
|
||||
if(update_result_out != NULL)
|
||||
{
|
||||
*update_result_out = ESKF_UPDATE_INVALID;
|
||||
@@ -1064,28 +1120,70 @@ bool fusion_engine_push_position(
|
||||
{
|
||||
*nis_out = NAN;
|
||||
}
|
||||
if(measurement == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
memset(&navigation, 0, sizeof(navigation));
|
||||
navigation.position = *measurement;
|
||||
return fusion_engine_push_navigation(
|
||||
engine, &navigation, update_result_out, nis_out, NULL, NULL);
|
||||
}
|
||||
|
||||
bool fusion_engine_push_navigation(
|
||||
fusion_engine_t* engine,
|
||||
const fusion_navigation_measurement_t* measurement,
|
||||
eskf_update_result_t* position_update_result_out,
|
||||
double* position_nis_out,
|
||||
eskf_update_result_t* velocity_update_result_out,
|
||||
double* velocity_nis_out)
|
||||
{
|
||||
if(position_update_result_out != NULL)
|
||||
{
|
||||
*position_update_result_out = ESKF_UPDATE_INVALID;
|
||||
}
|
||||
if(position_nis_out != NULL)
|
||||
{
|
||||
*position_nis_out = NAN;
|
||||
}
|
||||
if(velocity_update_result_out != NULL)
|
||||
{
|
||||
*velocity_update_result_out = ESKF_UPDATE_INVALID;
|
||||
}
|
||||
if(velocity_nis_out != NULL)
|
||||
{
|
||||
*velocity_nis_out = NAN;
|
||||
}
|
||||
if((engine == NULL) || (engine->state == FUSION_ENGINE_FAULT) ||
|
||||
!position_valid(measurement))
|
||||
!navigation_valid(measurement))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
engine->diagnostics.position_received++;
|
||||
if(!measurement->source_valid)
|
||||
if(measurement->velocity_valid)
|
||||
{
|
||||
engine->diagnostics.velocity_received++;
|
||||
}
|
||||
if(!measurement->position.source_valid)
|
||||
{
|
||||
engine->diagnostics.position_source_rejected++;
|
||||
return false;
|
||||
}
|
||||
if(engine->state == FUSION_ENGINE_CALIBRATING)
|
||||
{
|
||||
engine->initial_position = *measurement;
|
||||
engine->initial_position = measurement->position;
|
||||
engine->have_initial_position = true;
|
||||
return true;
|
||||
}
|
||||
if(measurement->timestamp_ticks > engine->current.filter_timestamp_ticks)
|
||||
if(measurement->position.timestamp_ticks >
|
||||
engine->current.filter_timestamp_ticks)
|
||||
{
|
||||
return pending_insert(engine, measurement);
|
||||
}
|
||||
return process_position_now(engine, measurement, update_result_out, nis_out);
|
||||
return process_navigation_now(
|
||||
engine, measurement,
|
||||
position_update_result_out, position_nis_out,
|
||||
velocity_update_result_out, velocity_nis_out);
|
||||
}
|
||||
|
||||
bool fusion_engine_get_runtime(const fusion_engine_t* engine,
|
||||
|
||||
@@ -80,6 +80,14 @@ void fusion_gps_adapter_default_config(fusion_gps_adapter_config_t* config)
|
||||
config->minimum_horizontal_std_m = 0.50;
|
||||
config->minimum_vertical_std_m = 1.00;
|
||||
config->maximum_position_std_m = 1000.0;
|
||||
/*
|
||||
* Board data shows several m/s stationary speed error with weak
|
||||
* four/five-satellite geometry even after Doppler IIR smoothing. These
|
||||
* floors keep repeated, correlated velocity fixes useful but conservative.
|
||||
*/
|
||||
config->minimum_horizontal_velocity_std_mps = 2.0;
|
||||
config->minimum_vertical_velocity_std_mps = 3.0;
|
||||
config->maximum_velocity_std_mps = 100.0;
|
||||
}
|
||||
|
||||
bool fusion_gps_adapter_init(fusion_gps_adapter_t* adapter,
|
||||
@@ -88,7 +96,11 @@ bool fusion_gps_adapter_init(fusion_gps_adapter_t* adapter,
|
||||
if((adapter == NULL) || (config == NULL) ||
|
||||
!(config->minimum_horizontal_std_m > 0.0) ||
|
||||
!(config->minimum_vertical_std_m > 0.0) ||
|
||||
!(config->maximum_position_std_m >= config->minimum_vertical_std_m))
|
||||
!(config->maximum_position_std_m >= config->minimum_vertical_std_m) ||
|
||||
!(config->minimum_horizontal_velocity_std_mps > 0.0) ||
|
||||
!(config->minimum_vertical_velocity_std_mps > 0.0) ||
|
||||
!(config->maximum_velocity_std_mps >=
|
||||
config->minimum_vertical_velocity_std_mps))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -97,19 +109,19 @@ bool fusion_gps_adapter_init(fusion_gps_adapter_t* adapter,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sanitize_covariance(const fusion_gps_adapter_t* adapter,
|
||||
const double input[3][3], double output[9])
|
||||
static bool sanitize_covariance(
|
||||
double input[3][3],
|
||||
double horizontal_std_floor,
|
||||
double vertical_std_floor,
|
||||
double maximum_std,
|
||||
double output[9])
|
||||
{
|
||||
double variance_floor[3] = {
|
||||
adapter->config.minimum_horizontal_std_m *
|
||||
adapter->config.minimum_horizontal_std_m,
|
||||
adapter->config.minimum_horizontal_std_m *
|
||||
adapter->config.minimum_horizontal_std_m,
|
||||
adapter->config.minimum_vertical_std_m *
|
||||
adapter->config.minimum_vertical_std_m
|
||||
horizontal_std_floor * horizontal_std_floor,
|
||||
horizontal_std_floor * horizontal_std_floor,
|
||||
vertical_std_floor * vertical_std_floor
|
||||
};
|
||||
double variance_ceiling = adapter->config.maximum_position_std_m *
|
||||
adapter->config.maximum_position_std_m;
|
||||
double variance_ceiling = maximum_std * maximum_std;
|
||||
for(unsigned int axis = 0 ; axis < 3u ; axis++)
|
||||
{
|
||||
double variance = input[axis][axis];
|
||||
@@ -124,7 +136,8 @@ static bool sanitize_covariance(const fusion_gps_adapter_t* adapter,
|
||||
{
|
||||
for(unsigned int col = row + 1u ; col < 3u ; col++)
|
||||
{
|
||||
double covariance = 0.5 * (input[row][col] + input[col][row]);
|
||||
double covariance = 0.5 *
|
||||
(input[row][col] + input[col][row]);
|
||||
if(!isfinite(covariance))
|
||||
{
|
||||
return false;
|
||||
@@ -146,23 +159,32 @@ static bool sanitize_covariance(const fusion_gps_adapter_t* adapter,
|
||||
(determinant > DBL_EPSILON) && isfinite(determinant);
|
||||
}
|
||||
|
||||
bool fusion_gps_adapter_convert(fusion_gps_adapter_t* adapter,
|
||||
const fusion_gps_measurement_t* gps,
|
||||
fusion_position_measurement_t* position_out)
|
||||
bool fusion_gps_adapter_convert_navigation(
|
||||
fusion_gps_adapter_t* adapter,
|
||||
const fusion_gps_measurement_t* gps,
|
||||
fusion_navigation_measurement_t* navigation_out)
|
||||
{
|
||||
if((adapter == NULL) || (gps == NULL) || (position_out == NULL) ||
|
||||
if((adapter == NULL) || (gps == NULL) || (navigation_out == NULL) ||
|
||||
!llh_valid(gps->latitude_deg, gps->longitude_deg, gps->height_m))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
memset(position_out, 0, sizeof(*position_out));
|
||||
memset(navigation_out, 0, sizeof(*navigation_out));
|
||||
fusion_position_measurement_t* position_out = &navigation_out->position;
|
||||
position_out->timestamp_ticks = gps->timestamp_ticks;
|
||||
position_out->source_valid = gps->solution_valid;
|
||||
position_out->source_quality =
|
||||
(isfinite(gps->chi_square_ratio) && (gps->chi_square_ratio >= 0.0))
|
||||
? 100.0 / (1.0 + gps->chi_square_ratio) : 0.0;
|
||||
if(!sanitize_covariance(adapter, gps->covariance_enu_m2,
|
||||
position_out->covariance_enu_m2))
|
||||
double position_covariance_enu_m2[3][3];
|
||||
memcpy(position_covariance_enu_m2, gps->covariance_enu_m2,
|
||||
sizeof(position_covariance_enu_m2));
|
||||
if(!sanitize_covariance(
|
||||
position_covariance_enu_m2,
|
||||
adapter->config.minimum_horizontal_std_m,
|
||||
adapter->config.minimum_vertical_std_m,
|
||||
adapter->config.maximum_position_std_m,
|
||||
position_out->covariance_enu_m2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -189,9 +211,73 @@ bool fusion_gps_adapter_convert(fusion_gps_adapter_t* adapter,
|
||||
adapter->ecef_to_enu[row][col] * delta_ecef_m[col];
|
||||
}
|
||||
}
|
||||
return isfinite(position_out->position_enu_m[0]) &&
|
||||
isfinite(position_out->position_enu_m[1]) &&
|
||||
isfinite(position_out->position_enu_m[2]);
|
||||
if(!isfinite(position_out->position_enu_m[0]) ||
|
||||
!isfinite(position_out->position_enu_m[1]) ||
|
||||
!isfinite(position_out->position_enu_m[2]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(gps->solution_valid && gps->velocity_valid)
|
||||
{
|
||||
bool velocity_finite = true;
|
||||
for(unsigned int row = 0u; row < 3u; row++)
|
||||
{
|
||||
for(unsigned int column = 0u; column < 3u; column++)
|
||||
{
|
||||
navigation_out->velocity_enu_mps[row] +=
|
||||
adapter->ecef_to_enu[row][column] *
|
||||
gps->velocity_ecef_mps[column];
|
||||
}
|
||||
velocity_finite =
|
||||
velocity_finite &&
|
||||
isfinite(navigation_out->velocity_enu_mps[row]);
|
||||
}
|
||||
|
||||
double covariance_enu_mps2[3][3] = {{0.0}};
|
||||
for(unsigned int row = 0u; row < 3u; row++)
|
||||
{
|
||||
for(unsigned int column = 0u; column < 3u; column++)
|
||||
{
|
||||
for(unsigned int first = 0u; first < 3u; first++)
|
||||
{
|
||||
for(unsigned int second = 0u; second < 3u; second++)
|
||||
{
|
||||
covariance_enu_mps2[row][column] +=
|
||||
adapter->ecef_to_enu[row][first] *
|
||||
gps->velocity_covariance_ecef_mps2[first][second] *
|
||||
adapter->ecef_to_enu[column][second];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
navigation_out->velocity_valid =
|
||||
velocity_finite &&
|
||||
sanitize_covariance(
|
||||
covariance_enu_mps2,
|
||||
adapter->config.minimum_horizontal_velocity_std_mps,
|
||||
adapter->config.minimum_vertical_velocity_std_mps,
|
||||
adapter->config.maximum_velocity_std_mps,
|
||||
navigation_out->velocity_covariance_enu_mps2);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fusion_gps_adapter_convert(fusion_gps_adapter_t* adapter,
|
||||
const fusion_gps_measurement_t* gps,
|
||||
fusion_position_measurement_t* position_out)
|
||||
{
|
||||
if(position_out == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fusion_navigation_measurement_t navigation;
|
||||
if(!fusion_gps_adapter_convert_navigation(adapter, gps, &navigation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*position_out = navigation.position;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fusion_gps_adapter_local_to_geodetic(
|
||||
|
||||
@@ -90,12 +90,14 @@ bool navigation_fusion_push_gps(const fusion_gps_measurement_t* measurement)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fusion_position_measurement_t position;
|
||||
if(!fusion_gps_adapter_convert(&gps_adapter, measurement, &position))
|
||||
fusion_navigation_measurement_t navigation;
|
||||
if(!fusion_gps_adapter_convert_navigation(
|
||||
&gps_adapter, measurement, &navigation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return fusion_engine_push_position(&fusion_engine, &position, NULL, NULL);
|
||||
return fusion_engine_push_navigation(
|
||||
&fusion_engine, &navigation, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
bool navigation_fusion_get_output(navigation_fusion_output_t* output)
|
||||
@@ -111,6 +113,10 @@ bool navigation_fusion_get_output(navigation_fusion_output_t* output)
|
||||
output->gps_nis_rejected = fusion_engine.diagnostics.position_nis_rejected;
|
||||
output->gps_position_recoveries =
|
||||
fusion_engine.diagnostics.position_recovery_count;
|
||||
output->gps_velocity_accepted =
|
||||
fusion_engine.diagnostics.velocity_accepted;
|
||||
output->gps_velocity_nis_rejected =
|
||||
fusion_engine.diagnostics.velocity_nis_rejected;
|
||||
output->zero_update_count =
|
||||
fusion_engine.diagnostics.zero_velocity_update_count;
|
||||
output->replay_count = fusion_engine.diagnostics.replay_count;
|
||||
@@ -136,6 +142,7 @@ bool navigation_fusion_get_output(navigation_fusion_output_t* output)
|
||||
}
|
||||
output->speed_mps = eskf_speed_mps(&runtime->filter);
|
||||
output->last_position_nis = runtime->filter.last_position_nis;
|
||||
output->last_velocity_nis = runtime->filter.last_velocity_nis;
|
||||
output->motion_state = runtime->motion.state;
|
||||
output->vehicle_at_rest = runtime->vehicle_at_rest;
|
||||
output->local_valid = true;
|
||||
|
||||
@@ -945,6 +945,7 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
velocity_measurements,
|
||||
satellite_count,
|
||||
solution.velocity_ecef_mps,
|
||||
solution.velocity_covariance_ecef_mps2,
|
||||
&solution.clock_drift_mps,
|
||||
&solution.velocity_residual_rms_mps);
|
||||
if(solution.velocity_valid)
|
||||
|
||||
@@ -232,6 +232,35 @@ static bool solve_four_by_four(
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool invert_four_by_four(double input[4][4], double inverse[4][4])
|
||||
{
|
||||
for(unsigned int column = 0u; column < 4u; column++)
|
||||
{
|
||||
double unit[4] = {0.0, 0.0, 0.0, 0.0};
|
||||
double solution[4];
|
||||
unit[column] = 1.0;
|
||||
if(!solve_four_by_four(input, unit, solution))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for(unsigned int row = 0u; row < 4u; row++)
|
||||
{
|
||||
inverse[row][column] = solution[row];
|
||||
}
|
||||
}
|
||||
for(unsigned int row = 0u; row < 4u; row++)
|
||||
{
|
||||
for(unsigned int column = row + 1u; column < 4u; column++)
|
||||
{
|
||||
double symmetric =
|
||||
0.5 * (inverse[row][column] + inverse[column][row]);
|
||||
inverse[row][column] = symmetric;
|
||||
inverse[column][row] = symmetric;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gps_tracking_velocity_wls(
|
||||
const double receiver_ecef_m[3],
|
||||
const double satellite_positions_m[][3],
|
||||
@@ -240,6 +269,7 @@ bool gps_tracking_velocity_wls(
|
||||
const gps_tracking_measurement_t measurements[],
|
||||
unsigned int count,
|
||||
double receiver_velocity_ecef_mps_out[3],
|
||||
double receiver_velocity_covariance_ecef_mps2_out[3][3],
|
||||
double* receiver_clock_drift_mps_out,
|
||||
double* residual_rms_mps_out)
|
||||
{
|
||||
@@ -248,6 +278,7 @@ bool gps_tracking_velocity_wls(
|
||||
(satellite_clock_drifts_sps == NULL) ||
|
||||
(measurements == NULL) ||
|
||||
(receiver_velocity_ecef_mps_out == NULL) ||
|
||||
(receiver_velocity_covariance_ecef_mps2_out == NULL) ||
|
||||
(receiver_clock_drift_mps_out == NULL) ||
|
||||
(residual_rms_mps_out == NULL) ||
|
||||
(count < GPS_TRACK_MEASUREMENT_MIN_SATELLITES) ||
|
||||
@@ -260,6 +291,7 @@ bool gps_tracking_velocity_wls(
|
||||
double rhs[4] = {0.0};
|
||||
double design[GPS_TRACK_MEASUREMENT_MAX_SATELLITES][4];
|
||||
double observation[GPS_TRACK_MEASUREMENT_MAX_SATELLITES];
|
||||
double weights[GPS_TRACK_MEASUREMENT_MAX_SATELLITES];
|
||||
unsigned int used = 0u;
|
||||
|
||||
for(unsigned int satellite = 0u; satellite < count; satellite++)
|
||||
@@ -309,6 +341,7 @@ bool gps_tracking_velocity_wls(
|
||||
30.0 * pow(10.0, -measurement->cn0_dbhz / 20.0);
|
||||
sigma_rate_mps = clamp_double(sigma_rate_mps, 0.05, 5.0);
|
||||
double weight = 1.0 / (sigma_rate_mps * sigma_rate_mps);
|
||||
weights[used] = weight;
|
||||
for(unsigned int row = 0u; row < 4u; row++)
|
||||
{
|
||||
rhs[row] +=
|
||||
@@ -329,12 +362,15 @@ bool gps_tracking_velocity_wls(
|
||||
}
|
||||
|
||||
double solution[4];
|
||||
if(!solve_four_by_four(normal, rhs, solution))
|
||||
double normal_inverse[4][4];
|
||||
if(!solve_four_by_four(normal, rhs, solution) ||
|
||||
!invert_four_by_four(normal, normal_inverse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double residual_square_sum = 0.0;
|
||||
double weighted_residual_square_sum = 0.0;
|
||||
for(unsigned int row = 0u; row < used; row++)
|
||||
{
|
||||
double predicted = 0.0;
|
||||
@@ -344,8 +380,36 @@ bool gps_tracking_velocity_wls(
|
||||
}
|
||||
double residual = observation[row] - predicted;
|
||||
residual_square_sum += residual * residual;
|
||||
weighted_residual_square_sum +=
|
||||
weights[row] * residual * residual;
|
||||
}
|
||||
|
||||
/*
|
||||
* The C/N0 model supplies absolute range-rate sigmas. Inflate the formal
|
||||
* inverse-normal covariance when the post-fit residuals are larger than
|
||||
* that model predicts; never shrink it from a fortuitously quiet epoch.
|
||||
* Four-satellite epochs have zero redundancy, so retain scale 1 and let
|
||||
* the poor geometry appear directly in the inverse normal matrix.
|
||||
*/
|
||||
double covariance_scale = 1.0;
|
||||
if(used > 4u)
|
||||
{
|
||||
covariance_scale = fmax(
|
||||
1.0, weighted_residual_square_sum / (double)(used - 4u));
|
||||
}
|
||||
for(unsigned int row = 0u; row < 3u; row++)
|
||||
{
|
||||
for(unsigned int column = 0u; column < 3u; column++)
|
||||
{
|
||||
receiver_velocity_covariance_ecef_mps2_out[row][column] =
|
||||
covariance_scale * normal_inverse[row][column];
|
||||
if(!isfinite(
|
||||
receiver_velocity_covariance_ecef_mps2_out[row][column]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
memcpy(receiver_velocity_ecef_mps_out, solution,
|
||||
3u * sizeof(solution[0]));
|
||||
*receiver_clock_drift_mps_out = solution[3];
|
||||
|
||||
@@ -143,6 +143,7 @@ static void test_velocity_wls(void)
|
||||
}
|
||||
|
||||
double solved_velocity[3];
|
||||
double solved_velocity_covariance[3][3];
|
||||
double solved_clock_drift;
|
||||
double residual_rms;
|
||||
assert(gps_tracking_velocity_wls(
|
||||
@@ -153,6 +154,7 @@ static void test_velocity_wls(void)
|
||||
measurements,
|
||||
TEST_SATELLITES,
|
||||
solved_velocity,
|
||||
solved_velocity_covariance,
|
||||
&solved_clock_drift,
|
||||
&residual_rms));
|
||||
|
||||
@@ -168,6 +170,16 @@ static void test_velocity_wls(void)
|
||||
expected_clock_drift_mps,
|
||||
1.0e-9));
|
||||
assert(residual_rms < 1.0e-9);
|
||||
for(unsigned int component = 0u; component < 3u; component++)
|
||||
{
|
||||
assert(isfinite(
|
||||
solved_velocity_covariance[component][component]));
|
||||
assert(solved_velocity_covariance[component][component] > 0.0);
|
||||
}
|
||||
assert(close_to(
|
||||
solved_velocity_covariance[0][1],
|
||||
solved_velocity_covariance[1][0],
|
||||
1.0e-12));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
|
||||
Reference in New Issue
Block a user