feat(gps): add timer-based assisted reacquisition

Remove the fixed pseudorange start offset, preserve per-PRN navigation assistance across RF loss and power cycles, and seed coherent navigation measurement phase on reacquisition. Add full LNAV almanac visibility scheduling, power-safe QSPI journaling, RF-domain reset isolation, and PPS Gray CDC hardening with Linux, sanitizer, RTL, and end-to-end coverage.
This commit is contained in:
onur
2026-08-04 02:52:35 +03:00
parent 347e1c671d
commit 30179df8e6
69 changed files with 6162 additions and 247 deletions
+6 -3
View File
@@ -5,6 +5,7 @@ LDLIBS ?= -lm
GPS_TIME_SOURCE := ../../gps_system/src/gps/gps_time_anchor.c
GPS_EPOCH_SOURCE := ../../gps_system/src/gps/gps_nav_epoch.c
GPS_EPHEMERIS_CACHE_SOURCE := ../../gps_system/src/gps/gps_ephemeris_cache.c
ESKF_SOURCES := ../../gps_system/src/fusion/eskf.c ../../gps_system/src/fusion/fusion_motion.c ../../gps_system/src/fusion/fusion_rest_detector.c ../../gps_system/src/fusion/fusion_engine.c ../../gps_system/src/fusion/fusion_gps_adapter.c ../../gps_system/src/fusion/fusion_bus.c
IMU_PROFILE_SOURCE := ../../gps_system/src/adapters/imu_fusion_profile.c
@@ -12,8 +13,8 @@ IMU_PROFILE_SOURCE := ../../gps_system/src/adapters/imu_fusion_profile.c
all: standalone_gps_integrity_test eskf_motion_test fusion_rest_detector_test fusion_synthetic_test imu_quality_synthetic_test async_tilt_recovery_test async_rate_matrix_test gps_reacquisition_test platform_scenario_matrix_test gps_failure_scenario_test
standalone_gps_integrity_test: standalone_gps_integrity_test.c $(GPS_TIME_SOURCE) $(GPS_EPOCH_SOURCE)
$(CC) $(CFLAGS) $^ -o $@ $(LDLIBS)
standalone_gps_integrity_test: standalone_gps_integrity_test.c $(GPS_TIME_SOURCE) $(GPS_EPOCH_SOURCE) $(GPS_EPHEMERIS_CACHE_SOURCE)
$(CC) $(CFLAGS) -Wno-comment -I../../gps_system/tests/fake_bsp $^ -o $@ $(LDLIBS)
eskf_motion_test: eskf_motion_test.c $(ESKF_SOURCES)
$(CC) $(CFLAGS) $^ -o $@ $(LDLIBS)
@@ -72,8 +73,10 @@ scenario-logs: platform_scenario_matrix_test
sanitize:
$(CC) -std=c11 -O1 -g -Wall -Wextra -Wpedantic -Werror \
-fsanitize=address,undefined -fno-omit-frame-pointer \
-Wno-comment \
-I../../gps_system/header standalone_gps_integrity_test.c \
$(GPS_TIME_SOURCE) $(GPS_EPOCH_SOURCE) \
-I../../gps_system/tests/fake_bsp \
$(GPS_TIME_SOURCE) $(GPS_EPOCH_SOURCE) $(GPS_EPHEMERIS_CACHE_SOURCE) \
-o standalone_gps_integrity_test_san $(LDLIBS)
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \
UBSAN_OPTIONS=halt_on_error=1 ./standalone_gps_integrity_test_san
@@ -10,12 +10,15 @@
#include <time.h>
#include "gps/gps_nav_epoch.h"
#include "gps/gps_ephemeris_cache.h"
#include "gps/gps_time_anchor.h"
#define MAX_SATELLITES 9
#define STATE_DIM 4
#define PI 3.14159265358979323846
#define TEST_PI 3.14159265358979323846
#define RECEIVER_SAMPLES_PER_SECOND 16368000.0
#define RECEIVER_COUNTER_PPM 0.20
#define SPEED_OF_LIGHT_MPS 299792458.0
#define EPOCH_SAMPLE_WINDOW 409200u
#define EPOCH_TOW_TOLERANCE_SEC 0.001
@@ -229,8 +232,8 @@ static void create_measurements(measurement_t measurements[MAX_SATELLITES],
enu_axes(latitude_rad, longitude_rad, axes);
for (unsigned index = 0; index < count; index++) {
double azimuth = azimuth_deg[index] * PI / 180.0;
double elevation = elevation_deg[index] * PI / 180.0;
double azimuth = azimuth_deg[index] * TEST_PI / 180.0;
double elevation = elevation_deg[index] * TEST_PI / 180.0;
double los_enu[3] = {
cos(elevation) * sin(azimuth),
cos(elevation) * cos(azimuth),
@@ -572,6 +575,7 @@ typedef struct {
bool have_sf[3];
unsigned char iod[3];
bool ephemeris_valid;
unsigned ephemeris_decode_count;
} linux_nav_channel_t;
typedef struct {
@@ -582,6 +586,9 @@ typedef struct {
unsigned fixes;
unsigned fde_fixes;
unsigned epoch_exclusions;
unsigned sample_observations;
unsigned ephemeris_cache_restores;
unsigned ephemeris_decodes;
double east_sum_m;
double north_sum_m;
double up_sum_m;
@@ -627,7 +634,7 @@ static double synthetic_rng_gaussian(synthetic_rng_t* rng)
{
double u1 = synthetic_rng_uniform(rng);
double u2 = synthetic_rng_uniform(rng);
return sqrt(-2.0 * log(u1)) * cos(2.0 * PI * u2);
return sqrt(-2.0 * log(u1)) * cos(2.0 * TEST_PI * u2);
}
static void linux_nav_receive_ephemeris_subframe(linux_nav_channel_t* channel,
@@ -640,13 +647,161 @@ static void linux_nav_receive_ephemeris_subframe(linux_nav_channel_t* channel,
unsigned slot = subframe_id - 1u;
channel->have_sf[slot] = true;
channel->iod[slot] = iod;
if (channel->have_sf[0] && channel->have_sf[1] && channel->have_sf[2] &&
if (!channel->ephemeris_valid &&
channel->have_sf[0] && channel->have_sf[1] && channel->have_sf[2] &&
channel->iod[0] == channel->iod[1] &&
channel->iod[1] == channel->iod[2]) {
channel->ephemeris_valid = true;
channel->ephemeris_decode_count++;
}
}
static bool linux_nav_cache_ephemeris(gps_ephemeris_cache_t* cache,
unsigned satellite,
unsigned char iod,
uint64_t tick)
{
gps_ephemeris_t ephemeris;
memset(&ephemeris, 0, sizeof(ephemeris));
ephemeris.SVID = (unsigned char)satellite;
ephemeris.iode = iod;
ephemeris.ephemeris_valid = true;
ephemeris.toe.week = 2300;
ephemeris.toe.sec = 604000.0;
return gps_ephemeris_cache_store(
cache, satellite, &ephemeris, tick);
}
static bool linux_nav_restore_ephemeris(gps_ephemeris_cache_t* cache,
linux_nav_channel_t* channel,
unsigned satellite,
uint64_t tick)
{
gps_ephemeris_t ephemeris;
if (!gps_ephemeris_cache_restore(
cache, satellite, tick, &ephemeris)) {
return false;
}
channel->ephemeris_valid = ephemeris.ephemeris_valid;
memset(channel->have_sf, 0, sizeof(channel->have_sf));
memset(channel->iod, 0, sizeof(channel->iod));
return channel->ephemeris_valid;
}
static bool synthetic_apply_receiver_sample_datum(
measurement_t measurements[MAX_SATELLITES],
unsigned count,
unsigned epoch,
int week,
double tow_sec,
double absolute_clock_bias_m,
double receiver_counter_ppm)
{
if (count == 0u) {
return false;
}
const double actual_sample_hz =
RECEIVER_SAMPLES_PER_SECOND *
(1.0 + receiver_counter_ppm * 1.0e-6);
/* Production unwraps every channel's shared 32-bit RF sample latch around
* the epoch reference. Keep an arbitrary moving origin here to prove that
* neither boot time nor a counter rollover is a range datum. */
const double epoch_sample = 1000000000.375 +
fmod((double)epoch * 0.2 * actual_sample_hz, 4294967296.0);
double capture_sample[MAX_SATELLITES];
for (unsigned satellite = 0u; satellite < count; satellite++) {
double propagation_sec =
(measurements[satellite].pseudorange_m - absolute_clock_bias_m) /
SPEED_OF_LIGHT_MPS;
if (!isfinite(propagation_sec) || propagation_sec < 0.0) {
return false;
}
capture_sample[satellite] = epoch_sample +
propagation_sec * actual_sample_hz;
}
for (unsigned satellite = 0u; satellite < count; satellite++) {
double relative_observation_sec = 0.0;
if (!gps_time_sample_differential_observation(
week, tow_sec, capture_sample[0],
week, tow_sec, capture_sample[satellite],
RECEIVER_SAMPLES_PER_SECOND,
&relative_observation_sec)) {
return false;
}
measurements[satellite].pseudorange_m =
relative_observation_sec * SPEED_OF_LIGHT_MPS;
}
return true;
}
static void verify_common_start_offset_cannot_change_position(
const measurement_t absolute_measurements[MAX_SATELLITES],
unsigned count,
const double receiver_truth[3],
double latitude_rad,
double longitude_rad)
{
measurement_t differential[MAX_SATELLITES];
measurement_t shifted[MAX_SATELLITES];
const double old_start_offset_m =
0.068802 * SPEED_OF_LIGHT_MPS;
const double reference_observation_m =
absolute_measurements[0].pseudorange_m;
for (unsigned row = 0u; row < count; row++) {
differential[row] = absolute_measurements[row];
shifted[row] = absolute_measurements[row];
differential[row].pseudorange_m -= reference_observation_m;
shifted[row].pseudorange_m =
differential[row].pseudorange_m + old_start_offset_m;
}
double differential_seed[STATE_DIM] = {
receiver_truth[0] + 120.0,
receiver_truth[1] - 90.0,
receiver_truth[2] + 60.0,
-20200000.0
};
double shifted_seed[STATE_DIM] = {
receiver_truth[0] + 120.0,
receiver_truth[1] - 90.0,
receiver_truth[2] + 60.0,
400000.0
};
solution_t differential_solution;
solution_t differential_full;
solution_t shifted_solution;
solution_t shifted_full;
bool differential_ok = solve_with_fde(
differential, count, differential_seed,
latitude_rad, longitude_rad,
&differential_solution, &differential_full);
bool shifted_ok = solve_with_fde(
shifted, count, shifted_seed,
latitude_rad, longitude_rad,
&shifted_solution, &shifted_full);
CHECK(differential_ok && shifted_ok &&
differential_solution.solution_valid &&
shifted_solution.solution_valid,
"common-datum WLS invariance solutions must be valid");
if (!differential_ok || !shifted_ok) {
return;
}
double position_delta[3] = {
shifted_solution.state[0] - differential_solution.state[0],
shifted_solution.state[1] - differential_solution.state[1],
shifted_solution.state[2] - differential_solution.state[2]
};
CHECK(vector_norm3(position_delta) < 1.0e-4,
"68.802 ms common offset changed WLS XYZ");
CHECK(fabs((shifted_solution.state[3] -
differential_solution.state[3]) -
old_start_offset_m) < 1.0e-4,
"68.802 ms common offset was not confined to WLS clock bias");
}
static unsigned synthetic_stage_for_epoch(unsigned epoch)
{
static const unsigned ends[SYNTHETIC_STAGE_COUNT] =
@@ -773,6 +928,7 @@ static void synthetic_print_metrics(synthetic_stage_metrics_t metrics)
}
printf("%-21s epochs=%3u fixes=%3u avg=%4.1f->%4.1f "
"sample=%4u cache=%2u decode=%2u "
"epoch_excl=%3u fde=%3u hRMSE=%6.2f hSTD=%6.2f "
"hP95=%6.2f hMAX=%6.2f vRMSE=%6.2f vSTD=%6.2f\n",
metrics.name, metrics.epochs, metrics.fixes,
@@ -780,6 +936,9 @@ static void synthetic_print_metrics(synthetic_stage_metrics_t metrics)
? (double)metrics.candidate_total / (double)metrics.epochs : 0.0,
metrics.epochs != 0u
? (double)metrics.selected_total / (double)metrics.epochs : 0.0,
metrics.sample_observations,
metrics.ephemeris_cache_restores,
metrics.ephemeris_decodes,
metrics.epoch_exclusions, metrics.fde_fixes,
horizontal_rmse, horizontal_spread, p95, metrics.horizontal_max_m,
vertical_rmse, vertical_spread);
@@ -809,9 +968,17 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
linux_nav_channel_t channels[MAX_SATELLITES];
memset(channels, 0, sizeof(channels));
gps_ephemeris_cache_t ephemeris_cache;
gps_ephemeris_cache_init(&ephemeris_cache, 1000u);
for (unsigned satellite = 0u; satellite < 8u; satellite++) {
channels[satellite].ephemeris_valid = true;
channels[satellite].ephemeris_decode_count = 1u;
CHECK(linux_nav_cache_ephemeris(
&ephemeris_cache, satellite,
(unsigned char)(17u + satellite), 0u),
"initial per-PRN ephemeris cache store failed");
}
metrics[STAGE_BASELINE].ephemeris_decodes = 8u;
linux_nav_channel_t iod_regression = {0};
linux_nav_receive_ephemeris_subframe(&iod_regression, 1u, 17u);
@@ -830,7 +997,7 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
receiver_truth[0] + 120.0,
receiver_truth[1] - 90.0,
receiver_truth[2] + 60.0,
clock_bias_m - 200.0
-20200000.0
};
unsigned delayed_selection_failures = 0u;
@@ -838,14 +1005,40 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
unsigned precommit_join_count_failures = 0u;
unsigned postcommit_join_count_failures = 0u;
unsigned fde_wrong_exclusion = 0u;
unsigned sample_datum_failures = 0u;
unsigned ephemeris_cache_restore_total = 0u;
bool saw_week_rollover = false;
bool saw_sample_rollover = false;
uint32_t previous_raw_sample = 0u;
uint32_t previous_active_mask = 0x0ffu;
for (unsigned epoch = 0u; epoch < 540u; epoch++) {
unsigned stage = synthetic_stage_for_epoch(epoch);
synthetic_stage_metrics_t* stage_metrics = &metrics[stage];
stage_metrics->epochs++;
uint32_t active_mask = synthetic_active_mask(stage);
/* A tracking slot is volatile; the per-PRN cache is not. Model a
* building/bridge obstruction by clearing the channel copy on loss and
* restoring it from RAM exactly once when acquisition returns. */
for (unsigned satellite = 0u; satellite < MAX_SATELLITES; satellite++) {
bool was_active = (previous_active_mask & (1u << satellite)) != 0u;
bool is_active = (active_mask & (1u << satellite)) != 0u;
if (was_active && !is_active) {
channels[satellite].ephemeris_valid = false;
memset(channels[satellite].have_sf, 0,
sizeof(channels[satellite].have_sf));
memset(channels[satellite].iod, 0,
sizeof(channels[satellite].iod));
} else if (!was_active && is_active &&
linux_nav_restore_ephemeris(
&ephemeris_cache, &channels[satellite],
satellite, epoch)) {
stage_metrics->ephemeris_cache_restores++;
ephemeris_cache_restore_total++;
}
}
previous_active_mask = active_mask;
if (stage == STAGE_EPHEMERIS_JOIN && epoch == 420u) {
linux_nav_receive_ephemeris_subframe(&channels[8], 1u, 29u);
@@ -853,6 +1046,12 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
}
if (stage == STAGE_EPHEMERIS_JOIN && epoch == 450u) {
linux_nav_receive_ephemeris_subframe(&channels[8], 3u, 29u);
if (channels[8].ephemeris_valid) {
CHECK(linux_nav_cache_ephemeris(
&ephemeris_cache, 8u, 29u, epoch),
"newly decoded PRN ephemeris was not cached");
stage_metrics->ephemeris_decodes++;
}
}
const double zero_noise[MAX_SATELLITES] = {0.0};
@@ -875,6 +1074,11 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
if (week > 2300) {
saw_week_rollover = true;
}
if (!synthetic_apply_receiver_sample_datum(
master, MAX_SATELLITES, epoch, week, tow_sec,
clock_bias_m, RECEIVER_COUNTER_PPM)) {
sample_datum_failures++;
}
uint32_t raw_sample =
(UINT32_MAX - 100000u) + epoch * 3273600u;
if (epoch != 0u && raw_sample < previous_raw_sample) {
@@ -885,7 +1089,6 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
gps_nav_epoch_key_t candidates[GPS_NAV_EPOCH_MAX_CANDIDATES];
unsigned candidate_to_satellite[GPS_NAV_EPOCH_MAX_CANDIDATES];
unsigned candidate_count = 0u;
uint32_t active_mask = synthetic_active_mask(stage);
for (unsigned satellite = 0u; satellite < MAX_SATELLITES; satellite++) {
if ((active_mask & (1u << satellite)) == 0u ||
!channels[satellite].ephemeris_valid) {
@@ -907,6 +1110,7 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
candidate_count++;
}
stage_metrics->candidate_total += candidate_count;
stage_metrics->sample_observations += candidate_count;
gps_nav_epoch_selection_t selection;
bool selected = gps_nav_epoch_validate_complete(
@@ -970,13 +1174,23 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
}
puts("\nGeneral synthetic GPS channel/epoch scenario");
puts("stage epochs/fixes candidates->selected and position scatter");
puts("stage epochs/fixes sample/cache/decode telemetry and position scatter");
for (unsigned stage = 0u; stage < SYNTHETIC_STAGE_COUNT; stage++) {
synthetic_print_metrics(metrics[stage]);
}
CHECK(saw_week_rollover, "general scenario must cross a GPS week boundary");
CHECK(saw_sample_rollover, "general scenario must cross uint32 ABS_SAMPLE rollover");
CHECK(sample_datum_failures == 0u,
"free-running RF sample counter failed to form an end-to-end observation");
CHECK(ephemeris_cache_restore_total == 5u,
"every returning cached PRN must restore exactly once");
CHECK(metrics[STAGE_REACQUIRE].ephemeris_cache_restores == 4u &&
metrics[STAGE_REACQUIRE].ephemeris_decodes == 0u,
"obstruction recovery must restore RAM ephemeris without SF1/SF2/SF3 decode");
CHECK(channels[8].ephemeris_decode_count == 1u &&
metrics[STAGE_EPHEMERIS_JOIN].ephemeris_decodes == 1u,
"an uncached PRN must join only after one matching ephemeris decode");
CHECK(metrics[STAGE_BASELINE].fixes >= 98u,
"baseline must produce stable fixes across time/sample rollover");
CHECK(metrics[STAGE_ONE_DROPOUT].fixes >= 58u,
@@ -1010,11 +1224,198 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
"minimum four-satellite geometry must stay within the synthetic bound");
}
typedef struct {
const char* name;
unsigned outage_sec;
uint32_t dropped_mask;
uint64_t cache_max_age_sec;
unsigned expected_cache_restores;
unsigned expected_reparses;
double receiver_counter_ppm;
} counter_reacquisition_case_t;
static unsigned synthetic_mask_count(uint32_t mask)
{
unsigned count = 0u;
while (mask != 0u) {
count += mask & 1u;
mask >>= 1u;
}
return count;
}
static bool synthetic_solve_counter_epoch(
unsigned epoch,
uint32_t active_mask,
const double receiver_truth[3],
double latitude_rad,
double longitude_rad,
double receiver_counter_ppm,
double* position_error_out)
{
const double no_noise[MAX_SATELLITES] = {0.0};
measurement_t all_measurements[MAX_SATELLITES];
create_measurements(all_measurements, 7u, receiver_truth,
latitude_rad, longitude_rad, 74000.0, no_noise);
int week = 0;
double tow_sec = 0.0;
synthetic_epoch_time(epoch, &week, &tow_sec);
if (!synthetic_apply_receiver_sample_datum(
all_measurements, 7u, epoch, week, tow_sec, 74000.0,
receiver_counter_ppm)) {
return false;
}
measurement_t selected[MAX_SATELLITES];
unsigned count = 0u;
for (unsigned satellite = 0u; satellite < 7u; satellite++) {
if ((active_mask & (1u << satellite)) != 0u) {
selected[count++] = all_measurements[satellite];
}
}
if (count < STATE_DIM) {
return false;
}
double seed[STATE_DIM] = {
receiver_truth[0] + 120.0,
receiver_truth[1] - 90.0,
receiver_truth[2] + 60.0,
-20200000.0
};
solution_t solution;
solution_t full_solution;
if (!solve_with_fde(selected, count, seed,
latitude_rad, longitude_rad,
&solution, &full_solution) ||
!solution.solution_valid) {
return false;
}
if (position_error_out != NULL) {
*position_error_out = position_error_m(&solution, receiver_truth);
}
return true;
}
static void run_counter_reacquisition_matrix(const double receiver_truth[3],
double latitude_rad,
double longitude_rad)
{
static const counter_reacquisition_case_t cases[] = {
{"tunnel-all-30s", 30u, 0x07fu, 7200u, 7u, 0u, 0.2},
{"drop-6of7-15s", 15u, 0x07eu, 7200u, 6u, 0u, 0.2},
{"drop-6of7-2s", 2u, 0x07eu, 7200u, 6u, 0u, 0.2},
{"drop-1of7-15s", 15u, 0x040u, 7200u, 1u, 0u, 0.2},
{"stale-all-7201s", 7201u, 0x07fu, 7200u, 0u, 7u, 0.2},
{"tunnel-30s-15ppm", 30u, 0x07fu, 7200u, 7u, 0u, 15.0}
};
const uint32_t full_mask = 0x07fu;
puts("\nRF free-counter + PPS epoch + per-PRN RAM reacquisition matrix");
puts("scenario outage drop remain ppm outage_fix cache reparse nav_wait recovery_error");
for (unsigned case_index = 0u;
case_index < sizeof(cases) / sizeof(cases[0]);
case_index++) {
const counter_reacquisition_case_t* scenario = &cases[case_index];
gps_ephemeris_cache_t cache;
gps_ephemeris_cache_init(&cache, scenario->cache_max_age_sec);
linux_nav_channel_t channels[7];
memset(channels, 0, sizeof(channels));
for (unsigned satellite = 0u; satellite < 7u; satellite++) {
channels[satellite].ephemeris_valid = true;
channels[satellite].ephemeris_decode_count = 1u;
CHECK(linux_nav_cache_ephemeris(
&cache, satellite,
(unsigned char)(40u + satellite), 0u),
"reacquisition matrix cache initialization failed");
}
uint32_t outage_active_mask =
full_mask & ~scenario->dropped_mask;
for (unsigned satellite = 0u; satellite < 7u; satellite++) {
if ((scenario->dropped_mask & (1u << satellite)) != 0u) {
channels[satellite].ephemeris_valid = false;
}
}
unsigned remain = synthetic_mask_count(outage_active_mask);
double outage_error_m = NAN;
bool outage_fix = synthetic_solve_counter_epoch(
(scenario->outage_sec * 5u) / 2u,
outage_active_mask, receiver_truth,
latitude_rad, longitude_rad,
scenario->receiver_counter_ppm, &outage_error_m);
CHECK(outage_fix == (remain >= STATE_DIM),
"outage fix availability did not match the four-satellite minimum");
if (outage_fix) {
CHECK(outage_error_m < 25.0,
"partial-outage RF-counter solution exceeded the recovery bound");
}
unsigned cache_restores = 0u;
unsigned reparses = 0u;
for (unsigned satellite = 0u; satellite < 7u; satellite++) {
if ((scenario->dropped_mask & (1u << satellite)) == 0u) {
continue;
}
if (linux_nav_restore_ephemeris(
&cache, &channels[satellite], satellite,
scenario->outage_sec)) {
cache_restores++;
continue;
}
/* Expired/missing RAM data follows the real matching-IOD commit
* rule; a partial or mismatched set is never made usable. */
linux_nav_receive_ephemeris_subframe(
&channels[satellite], 1u, (unsigned char)(70u + satellite));
linux_nav_receive_ephemeris_subframe(
&channels[satellite], 2u, (unsigned char)(70u + satellite));
linux_nav_receive_ephemeris_subframe(
&channels[satellite], 3u, (unsigned char)(70u + satellite));
if (channels[satellite].ephemeris_valid) {
reparses++;
CHECK(linux_nav_cache_ephemeris(
&cache, satellite,
(unsigned char)(70u + satellite),
scenario->outage_sec + 18u),
"fresh post-expiry ephemeris was not returned to RAM");
}
}
CHECK(cache_restores == scenario->expected_cache_restores,
"reacquisition matrix restored the wrong number of cached PRNs");
CHECK(reparses == scenario->expected_reparses,
"reacquisition matrix reparsed the wrong number of PRNs");
unsigned nav_wait_sec = (reparses == 0u) ? 6u : 18u;
unsigned recovery_epoch =
(scenario->outage_sec + nav_wait_sec) * 5u;
double recovery_error_m = NAN;
bool recovery_fix = synthetic_solve_counter_epoch(
recovery_epoch, full_mask, receiver_truth,
latitude_rad, longitude_rad,
scenario->receiver_counter_ppm, &recovery_error_m);
CHECK(recovery_fix && recovery_error_m < 25.0,
"post-reacquisition RF-counter position did not recover correctly");
printf("%-20s %6u %4u %6u %5.1f %10s %5u %7u %8u %14.3f m\n",
scenario->name,
scenario->outage_sec,
synthetic_mask_count(scenario->dropped_mask),
remain,
scenario->receiver_counter_ppm,
outage_fix ? "valid" : "suppressed",
cache_restores,
reparses,
nav_wait_sec,
recovery_error_m);
}
}
int main(int argc, char** argv)
{
const unsigned satellite_count = 7;
const double latitude_rad = 39.925 * PI / 180.0;
const double longitude_rad = 32.837 * PI / 180.0;
const double latitude_rad = 39.925 * TEST_PI / 180.0;
const double longitude_rad = 32.837 * TEST_PI / 180.0;
const double receiver_height_m = 890.0;
const double clock_bias_m = 74000.0;
double receiver_truth[3];
@@ -1040,6 +1441,10 @@ int main(int argc, char** argv)
seed, latitude_rad, longitude_rad);
}
verify_common_start_offset_cannot_change_position(
clean, satellite_count, receiver_truth,
latitude_rad, longitude_rad);
puts("Synthetic standalone GPS integrity/FDE comparison");
puts("quality = 100/(1 + chi_square/threshold); 50 is the 99% reject boundary\n");
@@ -1286,6 +1691,8 @@ int main(int argc, char** argv)
run_general_synthetic_scenario(receiver_truth, latitude_rad,
longitude_rad, clock_bias_m);
run_counter_reacquisition_matrix(receiver_truth,
latitude_rad, longitude_rad);
if (failures == 0) {
puts("\nPASS: all synthetic integrity checks passed");