fix(fusion): harden ESKF GPS failure handling
This commit is contained in:
@@ -165,7 +165,8 @@ uzakta kalirsa, yalniz hard NIS rejection kullanmak filtreyi yanlis baslangica
|
||||
kilitler. Fusion engine bu nedenle ardisik reddedilen position fix'lerinin
|
||||
zamansal surekliligini izler. Son başarılı fusion'dan sonra yedi saniye timeout
|
||||
dolmuşsa ve en az uc fix birbirinin devamı
|
||||
ise yalniz ESKF konumu ve konum kovaryansi son olcume yeniden ankrajlanir;
|
||||
ise ve platform IMU/GNSS hareket vetolariyla duragan kabul ediliyorsa yalniz
|
||||
ESKF konumu ve konum kovaryansi son olcume yeniden ankrajlanir;
|
||||
attitude, hiz ve IMU bias state'leri korunur, gecersiz hale gelen position
|
||||
cross-covariance'lari sifirlanir. Tek aykiri fix bu yolu tetiklemez. Bu davranis
|
||||
100/200/1000 ms GPS oranlarinda `gps_reacquisition_test.c` ile dogrulanir ve
|
||||
|
||||
@@ -390,11 +390,16 @@ static bool eskf_propagate_step(eskf_t* filter,
|
||||
return false;
|
||||
}
|
||||
|
||||
double system[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
double transition[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
for(unsigned int i = 0 ; i < 3u ; i++)
|
||||
{
|
||||
system[ESKF_POSITION_INDEX + i][ESKF_VELOCITY_INDEX + i] = 1.0;
|
||||
system[ESKF_ATTITUDE_INDEX + i][ESKF_GYRO_BIAS_INDEX + i] = -1.0;
|
||||
transition[i][i] = 1.0;
|
||||
transition[ESKF_VELOCITY_INDEX + i][ESKF_VELOCITY_INDEX + i] = 1.0;
|
||||
transition[ESKF_ATTITUDE_INDEX + i][ESKF_ATTITUDE_INDEX + i] = 1.0;
|
||||
transition[ESKF_ACCEL_BIAS_INDEX + i][ESKF_ACCEL_BIAS_INDEX + i] = 1.0;
|
||||
transition[ESKF_GYRO_BIAS_INDEX + i][ESKF_GYRO_BIAS_INDEX + i] = 1.0;
|
||||
transition[ESKF_POSITION_INDEX + i][ESKF_VELOCITY_INDEX + i] = dt_s;
|
||||
transition[ESKF_ATTITUDE_INDEX + i][ESKF_GYRO_BIAS_INDEX + i] = -dt_s;
|
||||
}
|
||||
double force_skew[3][3];
|
||||
double rate_skew[3][3];
|
||||
@@ -409,27 +414,16 @@ static bool eskf_propagate_step(eskf_t* filter,
|
||||
{
|
||||
rotated_skew += rotation[row][k] * force_skew[k][col];
|
||||
}
|
||||
system[ESKF_VELOCITY_INDEX + row][ESKF_ATTITUDE_INDEX + col] =
|
||||
-rotated_skew;
|
||||
system[ESKF_VELOCITY_INDEX + row][ESKF_ACCEL_BIAS_INDEX + col] =
|
||||
-rotation[row][col];
|
||||
system[ESKF_ATTITUDE_INDEX + row][ESKF_ATTITUDE_INDEX + col] =
|
||||
-rate_skew[row][col];
|
||||
transition[ESKF_VELOCITY_INDEX + row][ESKF_ATTITUDE_INDEX + col] +=
|
||||
-rotated_skew * dt_s;
|
||||
transition[ESKF_VELOCITY_INDEX + row][ESKF_ACCEL_BIAS_INDEX + col] =
|
||||
-rotation[row][col] * dt_s;
|
||||
transition[ESKF_ATTITUDE_INDEX + row][ESKF_ATTITUDE_INDEX + col] +=
|
||||
-rate_skew[row][col] * dt_s;
|
||||
}
|
||||
}
|
||||
|
||||
double transition[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
transition[row][col] = system[row][col] * dt_s;
|
||||
}
|
||||
transition[row][row] += 1.0;
|
||||
}
|
||||
|
||||
double intermediate[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
double propagated[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
for(unsigned int k = 0 ; k < ESKF_ERROR_STATE_DIM ; k++)
|
||||
@@ -448,21 +442,18 @@ static bool eskf_propagate_step(eskf_t* filter,
|
||||
}
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
for(unsigned int k = 0 ; k < ESKF_ERROR_STATE_DIM ; k++)
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
double value = intermediate[row][k];
|
||||
if(value == 0.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
double value = 0.0;
|
||||
for(unsigned int k = 0 ; k < ESKF_ERROR_STATE_DIM ; k++)
|
||||
{
|
||||
double coefficient = transition[col][k];
|
||||
if(coefficient != 0.0)
|
||||
{
|
||||
propagated[row][col] += value * coefficient;
|
||||
value += intermediate[row][k] * coefficient;
|
||||
}
|
||||
}
|
||||
filter->covariance[row][col] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,17 +468,20 @@ static bool eskf_propagate_step(eskf_t* filter,
|
||||
square(config->accel_noise_density[axis]) *
|
||||
rotation[col][axis];
|
||||
}
|
||||
propagated[ESKF_VELOCITY_INDEX + row][ESKF_VELOCITY_INDEX + col] +=
|
||||
filter->covariance[ESKF_VELOCITY_INDEX + row]
|
||||
[ESKF_VELOCITY_INDEX + col] +=
|
||||
accel_noise * dt_s;
|
||||
}
|
||||
propagated[ESKF_ATTITUDE_INDEX + row][ESKF_ATTITUDE_INDEX + row] +=
|
||||
filter->covariance[ESKF_ATTITUDE_INDEX + row]
|
||||
[ESKF_ATTITUDE_INDEX + row] +=
|
||||
square(config->gyro_noise_density[row]) * dt_s;
|
||||
propagated[ESKF_ACCEL_BIAS_INDEX + row][ESKF_ACCEL_BIAS_INDEX + row] +=
|
||||
filter->covariance[ESKF_ACCEL_BIAS_INDEX + row]
|
||||
[ESKF_ACCEL_BIAS_INDEX + row] +=
|
||||
square(config->accel_bias_random_walk[row]) * dt_s;
|
||||
propagated[ESKF_GYRO_BIAS_INDEX + row][ESKF_GYRO_BIAS_INDEX + row] +=
|
||||
filter->covariance[ESKF_GYRO_BIAS_INDEX + row]
|
||||
[ESKF_GYRO_BIAS_INDEX + row] +=
|
||||
square(config->gyro_bias_random_walk[row]) * dt_s;
|
||||
}
|
||||
memcpy(filter->covariance, propagated, sizeof(propagated));
|
||||
covariance_symmetrize_and_floor(filter, config->covariance_diagonal_floor);
|
||||
filter->propagation_count++;
|
||||
return eskf_state_is_finite(filter);
|
||||
@@ -591,10 +585,10 @@ static bool eskf_inject_error(eskf_t* filter, const double error[ESKF_ERROR_STAT
|
||||
}
|
||||
|
||||
/* Right attitude-error reset after q <- q (x) Exp(delta_theta). */
|
||||
double reset[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
for(unsigned int i = 0 ; i < ESKF_ERROR_STATE_DIM ; i++)
|
||||
double attitude_reset[3][3] = {{0.0}};
|
||||
for(unsigned int i = 0 ; i < 3u ; i++)
|
||||
{
|
||||
reset[i][i] = 1.0;
|
||||
attitude_reset[i][i] = 1.0;
|
||||
}
|
||||
double attitude_skew[3][3];
|
||||
skew_matrix(attitude_error, attitude_skew);
|
||||
@@ -602,47 +596,48 @@ static bool eskf_inject_error(eskf_t* filter, const double error[ESKF_ERROR_STAT
|
||||
{
|
||||
for(unsigned int col = 0 ; col < 3u ; col++)
|
||||
{
|
||||
reset[ESKF_ATTITUDE_INDEX + row][ESKF_ATTITUDE_INDEX + col] -=
|
||||
0.5 * attitude_skew[row][col];
|
||||
attitude_reset[row][col] -= 0.5 * attitude_skew[row][col];
|
||||
}
|
||||
}
|
||||
double intermediate[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
double reset_covariance[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
|
||||
/* G differs from identity only in the 3x3 attitude block. Apply G*P
|
||||
* using three temporary rows, then apply G^T using three scalar columns.
|
||||
* This keeps the Joseph/reset path below the bare-metal stack budget. */
|
||||
double attitude_rows[3][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
for(unsigned int row = 0u ; row < 3u ; row++)
|
||||
{
|
||||
for(unsigned int k = 0 ; k < ESKF_ERROR_STATE_DIM ; k++)
|
||||
for(unsigned int col = 0u ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
double coefficient = reset[row][k];
|
||||
if(coefficient == 0.0)
|
||||
for(unsigned int k = 0u ; k < 3u ; k++)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
intermediate[row][col] += coefficient * filter->covariance[k][col];
|
||||
attitude_rows[row][col] += attitude_reset[row][k] *
|
||||
filter->covariance[ESKF_ATTITUDE_INDEX + k][col];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
for(unsigned int row = 0u ; row < 3u ; row++)
|
||||
{
|
||||
for(unsigned int k = 0 ; k < ESKF_ERROR_STATE_DIM ; k++)
|
||||
memcpy(filter->covariance[ESKF_ATTITUDE_INDEX + row],
|
||||
attitude_rows[row], sizeof(attitude_rows[row]));
|
||||
}
|
||||
for(unsigned int row = 0u ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
double attitude_columns[3] = {0.0, 0.0, 0.0};
|
||||
for(unsigned int col = 0u ; col < 3u ; col++)
|
||||
{
|
||||
double value = intermediate[row][k];
|
||||
if(value == 0.0)
|
||||
for(unsigned int k = 0u ; k < 3u ; k++)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
double coefficient = reset[col][k];
|
||||
if(coefficient != 0.0)
|
||||
{
|
||||
reset_covariance[row][col] += value * coefficient;
|
||||
}
|
||||
attitude_columns[col] +=
|
||||
filter->covariance[row][ESKF_ATTITUDE_INDEX + k] *
|
||||
attitude_reset[col][k];
|
||||
}
|
||||
}
|
||||
for(unsigned int col = 0u ; col < 3u ; col++)
|
||||
{
|
||||
filter->covariance[row][ESKF_ATTITUDE_INDEX + col] =
|
||||
attitude_columns[col];
|
||||
}
|
||||
}
|
||||
memcpy(filter->covariance, reset_covariance, sizeof(reset_covariance));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -719,76 +714,44 @@ static bool eskf_selected_update(eskf_t* filter,
|
||||
}
|
||||
}
|
||||
|
||||
double joseph_left[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
joseph_left[row][row] = 1.0;
|
||||
for(unsigned int col = 0 ; col < 3u ; col++)
|
||||
{
|
||||
joseph_left[row][selected_state_index + col] -= gain[row][col];
|
||||
}
|
||||
}
|
||||
/* Joseph form with A = I-KH. H selects three direct state components, so
|
||||
* A*P can be formed without materializing a second 15x15 matrix. */
|
||||
double intermediate[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
double updated_covariance[ESKF_ERROR_STATE_DIM][ESKF_ERROR_STATE_DIM] = {{0.0}};
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
for(unsigned int k = 0 ; k < ESKF_ERROR_STATE_DIM ; k++)
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
double coefficient = joseph_left[row][k];
|
||||
if(coefficient == 0.0)
|
||||
double value = filter->covariance[row][col];
|
||||
for(unsigned int selected = 0u ; selected < 3u ; selected++)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
intermediate[row][col] +=
|
||||
coefficient * filter->covariance[k][col];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
for(unsigned int k = 0 ; k < ESKF_ERROR_STATE_DIM ; k++)
|
||||
{
|
||||
double value = intermediate[row][k];
|
||||
if(value == 0.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
double coefficient = joseph_left[col][k];
|
||||
if(coefficient != 0.0)
|
||||
{
|
||||
updated_covariance[row][col] += value * coefficient;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
double gain_times_covariance[ESKF_ERROR_STATE_DIM][3] = {{0.0}};
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
for(unsigned int second = 0 ; second < 3u ; second++)
|
||||
{
|
||||
for(unsigned int first = 0 ; first < 3u ; first++)
|
||||
{
|
||||
gain_times_covariance[row][second] += gain[row][first] *
|
||||
measurement_covariance[first * 3u + second];
|
||||
value -= gain[row][selected] *
|
||||
filter->covariance[selected_state_index + selected][col];
|
||||
}
|
||||
intermediate[row][col] = value;
|
||||
}
|
||||
}
|
||||
for(unsigned int row = 0 ; row < ESKF_ERROR_STATE_DIM ; row++)
|
||||
{
|
||||
for(unsigned int col = 0 ; col < ESKF_ERROR_STATE_DIM ; col++)
|
||||
{
|
||||
for(unsigned int second = 0 ; second < 3u ; second++)
|
||||
double value = intermediate[row][col];
|
||||
for(unsigned int selected = 0u ; selected < 3u ; selected++)
|
||||
{
|
||||
updated_covariance[row][col] +=
|
||||
gain_times_covariance[row][second] * gain[col][second];
|
||||
value -= intermediate[row][selected_state_index + selected] *
|
||||
gain[col][selected];
|
||||
}
|
||||
for(unsigned int first = 0u ; first < 3u ; first++)
|
||||
{
|
||||
for(unsigned int second = 0u ; second < 3u ; second++)
|
||||
{
|
||||
value += gain[row][first] *
|
||||
measurement_covariance[first * 3u + second] *
|
||||
gain[col][second];
|
||||
}
|
||||
}
|
||||
filter->covariance[row][col] = value;
|
||||
}
|
||||
}
|
||||
memcpy(filter->covariance, updated_covariance, sizeof(updated_covariance));
|
||||
if(!eskf_inject_error(filter, error))
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -136,8 +136,8 @@ void fusion_engine_default_config(fusion_engine_config_t* config,
|
||||
config->gnss_moving_confirmations = 2u;
|
||||
config->gnss_still_confirmations = 2u;
|
||||
/* A hard innovation gate protects against isolated GNSS outliers, but it
|
||||
* must not permanently lock the filter to a bad startup position. After a
|
||||
* full second of mutually continuous rejected fixes, re-anchor position
|
||||
* must not permanently lock a stationary filter to a bad startup position.
|
||||
* After a timeout of mutually continuous rejected fixes, re-anchor position
|
||||
* while preserving inertial velocity, attitude and bias states. */
|
||||
config->position_recovery_timeout_s = 7.0;
|
||||
config->position_recovery_max_speed_mps = 100.0;
|
||||
@@ -301,6 +301,31 @@ static bool runtime_apply_imu(fusion_engine_t* engine,
|
||||
const fusion_imu_measurement_t* measurement,
|
||||
bool count_zero_updates);
|
||||
|
||||
static bool runtime_allows_zero_velocity_update(
|
||||
const fusion_engine_t* engine,
|
||||
const fusion_runtime_t* runtime)
|
||||
{
|
||||
(void)engine;
|
||||
return runtime->vehicle_at_rest &&
|
||||
(runtime->gnss_evidence != FUSION_GNSS_MOTION_MOVING);
|
||||
}
|
||||
|
||||
static bool runtime_allows_zero_gyro_update(
|
||||
const fusion_engine_t* engine,
|
||||
const fusion_runtime_t* runtime)
|
||||
{
|
||||
return runtime_allows_zero_velocity_update(engine, runtime) &&
|
||||
(runtime->motion.last_gyro_norm_rps <=
|
||||
engine->config.motion.stationary_gyro_limit_rps);
|
||||
}
|
||||
|
||||
static bool runtime_allows_position_recovery(
|
||||
const fusion_engine_t* engine,
|
||||
const fusion_runtime_t* runtime)
|
||||
{
|
||||
return runtime_allows_zero_gyro_update(engine, runtime);
|
||||
}
|
||||
|
||||
static void history_push(fusion_engine_t* engine,
|
||||
const fusion_imu_measurement_t* measurement)
|
||||
{
|
||||
@@ -415,9 +440,12 @@ static bool runtime_apply_imu(fusion_engine_t* engine,
|
||||
runtime->gnss_still_count = 0u;
|
||||
}
|
||||
runtime->vehicle_at_rest = measurement->vehicle_at_rest;
|
||||
bool at_rest_continuing = measurement->vehicle_at_rest &&
|
||||
start_measurement.vehicle_at_rest;
|
||||
if(measurement->vehicle_at_rest)
|
||||
bool zero_velocity_allowed =
|
||||
runtime_allows_zero_velocity_update(engine, runtime);
|
||||
bool zero_gyro_allowed =
|
||||
runtime_allows_zero_gyro_update(engine, runtime);
|
||||
bool at_rest_continuing = start_measurement.vehicle_at_rest;
|
||||
if(zero_gyro_allowed && at_rest_continuing)
|
||||
{
|
||||
for(unsigned int axis = 0u ; axis < 3u ; axis++)
|
||||
{
|
||||
@@ -435,36 +463,51 @@ static bool runtime_apply_imu(fusion_engine_t* engine,
|
||||
}
|
||||
uint64_t zero_period_ticks =
|
||||
seconds_to_ticks(engine, engine->config.zero_update_period_s);
|
||||
if(at_rest_continuing &&
|
||||
if(at_rest_continuing && (zero_velocity_allowed || zero_gyro_allowed) &&
|
||||
((measurement->timestamp_ticks - runtime->last_zero_update_ticks) >=
|
||||
zero_period_ticks))
|
||||
{
|
||||
double mean_gyro_rps[3];
|
||||
if(!(runtime->zero_gyro_duration_s > 0.0))
|
||||
bool update_applied = false;
|
||||
if(zero_velocity_allowed)
|
||||
{
|
||||
return false;
|
||||
if(!eskf_zero_velocity_update(
|
||||
&runtime->filter, engine->config.zero_velocity_std_mps))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
update_applied = true;
|
||||
if(count_zero_updates)
|
||||
{
|
||||
engine->diagnostics.zero_velocity_update_count++;
|
||||
}
|
||||
}
|
||||
for(unsigned int axis = 0u ; axis < 3u ; axis++)
|
||||
if(zero_gyro_allowed && (runtime->zero_gyro_duration_s > 0.0))
|
||||
{
|
||||
mean_gyro_rps[axis] = runtime->zero_gyro_integral_rad[axis] /
|
||||
runtime->zero_gyro_duration_s;
|
||||
double mean_gyro_rps[3];
|
||||
for(unsigned int axis = 0u ; axis < 3u ; axis++)
|
||||
{
|
||||
mean_gyro_rps[axis] = runtime->zero_gyro_integral_rad[axis] /
|
||||
runtime->zero_gyro_duration_s;
|
||||
}
|
||||
if(!eskf_zero_angular_rate_update(
|
||||
&runtime->filter, mean_gyro_rps,
|
||||
engine->config.zero_gyro_std_rps))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
update_applied = true;
|
||||
if(count_zero_updates)
|
||||
{
|
||||
engine->diagnostics.zero_gyro_update_count++;
|
||||
}
|
||||
}
|
||||
if(!eskf_zero_velocity_update(
|
||||
&runtime->filter, engine->config.zero_velocity_std_mps) ||
|
||||
!eskf_zero_angular_rate_update(
|
||||
&runtime->filter, mean_gyro_rps, engine->config.zero_gyro_std_rps))
|
||||
if(update_applied)
|
||||
{
|
||||
return false;
|
||||
runtime->last_zero_update_ticks = measurement->timestamp_ticks;
|
||||
}
|
||||
memset(runtime->zero_gyro_integral_rad, 0,
|
||||
sizeof(runtime->zero_gyro_integral_rad));
|
||||
runtime->zero_gyro_duration_s = 0.0;
|
||||
runtime->last_zero_update_ticks = measurement->timestamp_ticks;
|
||||
if(count_zero_updates)
|
||||
{
|
||||
engine->diagnostics.zero_velocity_update_count++;
|
||||
engine->diagnostics.zero_gyro_update_count++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -706,6 +749,15 @@ static bool position_recovery_ready(
|
||||
const fusion_runtime_t* runtime,
|
||||
const fusion_position_measurement_t* measurement)
|
||||
{
|
||||
/* Position-only GNSS cannot distinguish a coherent receiver restart error
|
||||
* or spoofed track from a true position discontinuity while moving. Keep
|
||||
* automatic re-anchoring as a stationary startup/recovery mechanism; a
|
||||
* mobile reset needs independent velocity/yaw aiding or an external command. */
|
||||
if(!runtime_allows_position_recovery(engine, runtime))
|
||||
{
|
||||
position_recovery_clear(engine);
|
||||
return false;
|
||||
}
|
||||
if(!engine->position_recovery_active)
|
||||
{
|
||||
position_recovery_begin(engine, measurement);
|
||||
|
||||
Reference in New Issue
Block a user