/** Decode an RTCMv3 message type 1002 (Extended L1-Only GPS RTK Observables) * * \param buff A pointer to the RTCM data message buffer. * \param id Reference station ID (DF003). * \param tow GPS time of week of epoch (DF004). * \param n_sat Number of GPS satellites included in the message (DF006). * \param nm Struct containing the observation. * \param sync Synchronous GNSS Flag (DF005). * \return If valid then return 0. * Returns a negative number if the message is invalid: * - `-1` : Message type mismatch * - `-2` : Message uses unsupported P(Y) code */ s8 rtcm3_decode_1002(u8 *buff, u16 *id, double *tow, u8 *n_sat, navigation_measurement_t *nm, u8 *sync) { u16 type; u8 div_free, smooth; rtcm3_read_header(buff, &type, id, tow, sync, n_sat, &div_free, &smooth); if (type != 1002) /* Unexpected message type. */ return -1; /* TODO: Fill in t->wn. */ if (!nm) /* No nav meas pointer, probably just interested in * n_sat so we are all done. */ return 0; u16 bit = 64; for (u8 i=0; i<*n_sat; i++) { /* TODO: Handle SBAS prns properly, numbered differently in RTCM? */ nm[i].sid.sat = getbitu(buff, bit, 6); bit += 6; u8 code = getbitu(buff, bit, 1); bit += 1; /* TODO: When we start storing the signal/system etc. properly we can * store the code flag in the nav meas struct. */ if (code == 1) /* P(Y) code not currently supported. */ return -2; u32 pr = getbitu(buff, bit,24); bit += 24; s32 ppr = getbits(buff, bit,20); bit += 20; u8 lock = getbitu(buff, bit, 7); bit += 7; u8 amb = getbitu(buff, bit, 8); bit += 8; u8 cnr = getbitu(buff, bit, 8); bit += 8; nm[i].raw_pseudorange = 0.02*pr + PRUNIT_GPS*amb; nm[i].carrier_phase = (nm[i].raw_pseudorange + 0.0005*ppr) / (CLIGHT / FREQ1); nm[i].lock_time = from_lock_ind(lock); nm[i].snr = pow(10.0, ((cnr / 4.0) - 40.0) / 10.0); } return 0; }
END_TEST START_TEST(test_rtcm3_read_write_header) { u8 buff[22]; gps_time_t t = { .wn = 22, .tow = 22.222 }; rtcm3_write_header(buff, 1234, 2269, t, 1, 22, 1, 6); u16 type, id; u8 sync, n_sat, div_free, smooth; double tow; rtcm3_read_header(buff, &type, &id, &tow, &sync, &n_sat, &div_free, &smooth); fail_unless(type == 1234, "type decode error, decoded %d, expected 1234", type); fail_unless(id == 2269, "id decode error, decoded %d, expected 2269", id); fail_unless(fabs(tow - t.tow) < 1e3, "TOW decode error, decoded %f, expected %f", tow, t.tow); fail_unless(sync == 1, "id decode error, decoded %d, expected 1", id); fail_unless(n_sat == 22, "n_sat decode error, decoded %d, expected 22", n_sat); fail_unless(div_free == 1, "div_free decode error, decoded %d, expected 1", div_free); fail_unless(smooth == 6, "smooth decode error, decoded %d, expected 6", smooth); } END_TEST START_TEST(test_rtcm3_encode_decode) { navigation_measurement_t nm_orig[22]; navigation_measurement_t nm[22]; seed_rng(); for (u8 i=0; i<22; i++) { nm[i].raw_pseudorange = frand(19e6, 21e6); nm[i].carrier_phase = frand(-5e5, 5e5); nm[i].lock_time = frand(0, 1000); nm[i].snr = frand(0, 20); } memcpy(nm_orig, nm, sizeof(nm)); gps_time_t t = { .wn = 1234, .tow = frand(0, 604800) }; u8 buff[355]; rtcm3_encode_1002(buff, 1234, t, 22, nm, 0); navigation_measurement_t nm_out[22]; double tow_out; u8 sync, n_sat = 222; u16 id; s8 ret = rtcm3_decode_1002(buff, &id, &tow_out, &n_sat, 0, &sync); fail_unless(ret >= 0, "rtcm3_decode_1002 returned an error (%d)", ret); fail_unless(id == 1234, "decoded station id as %d, expected 1234", id); fail_unless(n_sat == 22, "decoded n_sat as %d, expected 22", n_sat); fail_unless(fabs(tow_out - t.tow) < 1e-3, "decoded TOW as %f, expected %f, error %f", tow_out, t.tow, tow_out - t.tow); ret = rtcm3_decode_1002(buff, &id, &tow_out, &n_sat, nm_out, &sync); for (u8 i=0; i<22; i++) { double pr_err = nm[i].raw_pseudorange - nm_out[i].raw_pseudorange; fail_unless(fabs(pr_err) < 0.02, "[%d] pseudorange error > 0.04m - " "decoded %f, expected %f, error %f", i, nm_out[i].raw_pseudorange, nm[i].raw_pseudorange, pr_err); double carr_err = nm[i].carrier_phase - nm_out[i].carrier_phase; fail_unless(fabs(carr_err) < 0.003, "carrier phase error (fractional part) > 0.003 cycles - " "[%d] decoded %f, expected %f, error %f", i, nm_out[i].carrier_phase, nm[i].carrier_phase, carr_err); double snr_err = nm[i].snr - nm_out[i].snr; /* Calculate error bound on SNR given logarithmic error bound on CNR. */ double err_bound = nm[i].snr * (pow(10.0, 1.0 / 40.0) - 1); fail_unless(fabs(snr_err) < err_bound, "SNR error > 0.003 - " "[%d] decoded %f, expected %f, error %f, bound %f", i, nm_out[i].snr, nm[i].snr, snr_err, err_bound); fail_unless((nm_out[i].lock_time == 0) && (nm[i].lock_time == 0), "lock time should be zero when adjusting int. amb. - [%d] decoded %f", i, nm_out[i].lock_time, nm[i].lock_time); double cp_adj = nm[i].carrier_phase - nm_orig[i].carrier_phase; fail_unless(fmod(cp_adj, 1.0) == 0, "carrier phase adjusted by non integer amount %f -> %f (%f)", nm_orig[i].carrier_phase, nm[i].carrier_phase, cp_adj); } /* Re-encode after adjustment, now there should be no further adjustment and * the lock time should be correct. */ for (u8 i=0; i<22; i++) nm[i].lock_time = frand(0, 1000); rtcm3_encode_1002(buff, 1234, t, 22, nm, 0); rtcm3_decode_1002(buff, &id, &tow_out, &n_sat, nm_out, &sync); for (u8 i=0; i<22; i++) { double cp_adj = nm_out[i].carrier_phase - nm[i].carrier_phase; fail_unless(cp_adj < 0.003, "carrier phase re-adjusted %f -> %f (%f)", nm[i].carrier_phase, nm_out[i].carrier_phase, cp_adj); fail_unless(nm_out[i].lock_time <= nm[i].lock_time, "lock time error, should always be less than input lock time - [%d] decoded %f, expected %f", i, nm_out[i].lock_time, nm[i].lock_time); } } END_TEST Suite* rtcm3_suite(void) { Suite *s = suite_create("RTCMv3"); TCase *tc_core = tcase_create("Core"); tcase_add_test(tc_core, test_rtcm3_check_frame); tcase_add_test(tc_core, test_rtcm3_write_frame); tcase_add_test(tc_core, test_rtcm3_read_write_header); tcase_add_test(tc_core, test_rtcm3_encode_decode); suite_add_tcase(s, tc_core); return s; }