From 741edd4c6569cba6deb54ebe110bf513922b4e1f Mon Sep 17 00:00:00 2001 From: Vladslav P Date: Sat, 4 Jul 2026 04:23:01 +0300 Subject: [PATCH 1/2] PVT: Fix missing SNR in NMEA sentences Propagate SNR from all bands instead of L1 only to rtkpos. Signed-off-by: Vladslav P --- src/algorithms/libs/rtklib/rtklib_pntpos.cc | 5 ++++- src/algorithms/libs/rtklib/rtklib_solution.cc | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/algorithms/libs/rtklib/rtklib_pntpos.cc b/src/algorithms/libs/rtklib/rtklib_pntpos.cc index e0a3097b9..676ade859 100644 --- a/src/algorithms/libs/rtklib/rtklib_pntpos.cc +++ b/src/algorithms/libs/rtklib/rtklib_pntpos.cc @@ -1162,7 +1162,10 @@ int pntpos(const obsd_t *obs, int n, const nav_t *nav, { ssat[obs[i].sat - 1].azel[0] = azel_[i * 2]; ssat[obs[i].sat - 1].azel[1] = azel_[1 + i * 2]; - ssat[obs[i].sat - 1].snr[0] = obs[i].SNR[0]; + for (int j = 0; j < NFREQ; j++) + { + ssat[obs[i].sat - 1].snr[j] = obs[i].SNR[j]; + } if (!vsat[i]) { continue; diff --git a/src/algorithms/libs/rtklib/rtklib_solution.cc b/src/algorithms/libs/rtklib/rtklib_solution.cc index 0b7266019..bf20b3ecb 100644 --- a/src/algorithms/libs/rtklib/rtklib_solution.cc +++ b/src/algorithms/libs/rtklib/rtklib_solution.cc @@ -34,6 +34,7 @@ #include "rtklib_solution.h" #include "rtklib_rtkcmn.h" #include "rtklib_rtksvr.h" +#include #include #include #include @@ -1955,7 +1956,7 @@ int outnmea_gsv(unsigned char *buff, const sol_t *sol, az += 360.0; } el = ssat[sats[k] - 1].azel[1] * R2D; - snr = ssat[sats[k] - 1].snr[0] * 0.25; + snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); } else @@ -2001,7 +2002,7 @@ int outnmea_gsv(unsigned char *buff, const sol_t *sol, az += 360.0; } el = ssat[sats[k] - 1].azel[1] * R2D; - snr = ssat[sats[k] - 1].snr[0] * 0.25; + snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); } else @@ -2046,7 +2047,7 @@ int outnmea_gsv(unsigned char *buff, const sol_t *sol, az += 360.0; } el = ssat[sats[k] - 1].azel[1] * R2D; - snr = ssat[sats[k] - 1].snr[0] * 0.25; + snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); } else @@ -2091,7 +2092,7 @@ int outnmea_gsv(unsigned char *buff, const sol_t *sol, az += 360.0; } el = ssat[sats[k] - 1].azel[1] * R2D; - snr = ssat[sats[k] - 1].snr[0] * 0.25; + snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); } else @@ -2136,7 +2137,7 @@ int outnmea_gsv(unsigned char *buff, const sol_t *sol, az += 360.0; } el = ssat[sats[k] - 1].azel[1] * R2D; - snr = ssat[sats[k] - 1].snr[0] * 0.25; + snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); } else From a5811e62a5f3fcf607ab5337dcaf14e317f6a355 Mon Sep 17 00:00:00 2001 From: Carles Fernandez Date: Thu, 9 Jul 2026 18:04:14 +0200 Subject: [PATCH 2/2] Add multi-band NMEA test and preserve GNSS signal IDs for NMEA GSV SNR reporting --- docs/CHANGELOG.md | 5 + src/algorithms/libs/rtklib/rtklib.h | 1 + src/algorithms/libs/rtklib/rtklib_pntpos.cc | 6 +- src/algorithms/libs/rtklib/rtklib_ppp.cc | 4 +- src/algorithms/libs/rtklib/rtklib_rtkpos.cc | 4 +- src/algorithms/libs/rtklib/rtklib_solution.cc | 519 ++++++++++-------- .../pvt/nmea_printer_test.cc | 89 +++ 7 files changed, 382 insertions(+), 246 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2b7fad6ef..8888b482f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -85,6 +85,11 @@ All notable changes to GNSS-SDR will be documented in this file. satellites used in the PVT solution and in view (with elevation, azimuth, and C/N0), using the QZSS system and signal identifiers defined in NMEA 0183. Contributed by @vladisslav2011 +- Fixed NMEA GSV C/N0 reporting for non-L1 configurations, including GPS L2 and + L5: RTKLIB now preserves per-frequency signal strength and observation-code + metadata in satellite status, and the NMEA printer emits the strongest + available C/N0 with the corresponding NMEA signal identifier. Contributed by + @vladisslav2011. ### Improvements in Maintainability: diff --git a/src/algorithms/libs/rtklib/rtklib.h b/src/algorithms/libs/rtklib/rtklib.h index 1961994d9..96ddd6d6f 100644 --- a/src/algorithms/libs/rtklib/rtklib.h +++ b/src/algorithms/libs/rtklib/rtklib.h @@ -1044,6 +1044,7 @@ typedef struct double resc[NFREQ]; /* residuals of carrier-phase (m) */ unsigned char vsat[NFREQ]; /* valid satellite flag */ unsigned char snr[NFREQ]; /* signal strength (0.25 dBHz) */ + unsigned char code[NFREQ]; /* code indicator (CODE_???) */ unsigned char fix[NFREQ]; /* ambiguity fix flag (1:fix,2:float,3:hold) */ unsigned char slip[NFREQ]; /* cycle-slip flag */ unsigned char half[NFREQ]; /* half-cycle valid flag */ diff --git a/src/algorithms/libs/rtklib/rtklib_pntpos.cc b/src/algorithms/libs/rtklib/rtklib_pntpos.cc index 676ade859..c5eea1526 100644 --- a/src/algorithms/libs/rtklib/rtklib_pntpos.cc +++ b/src/algorithms/libs/rtklib/rtklib_pntpos.cc @@ -1156,7 +1156,10 @@ int pntpos(const obsd_t *obs, int n, const nav_t *nav, ssat[i].vs = 0; ssat[i].azel[0] = ssat[i].azel[1] = 0.0; ssat[i].resp[0] = ssat[i].resc[0] = 0.0; - ssat[i].snr[0] = 0; + for (int j = 0; j < NFREQ; j++) + { + ssat[i].snr[j] = ssat[i].code[j] = 0; + } } for (i = 0; i < n; i++) { @@ -1165,6 +1168,7 @@ int pntpos(const obsd_t *obs, int n, const nav_t *nav, for (int j = 0; j < NFREQ; j++) { ssat[obs[i].sat - 1].snr[j] = obs[i].SNR[j]; + ssat[obs[i].sat - 1].code[j] = obs[i].code[j]; } if (!vsat[i]) { diff --git a/src/algorithms/libs/rtklib/rtklib_ppp.cc b/src/algorithms/libs/rtklib/rtklib_ppp.cc index 2310fcc6f..21b501623 100644 --- a/src/algorithms/libs/rtklib/rtklib_ppp.cc +++ b/src/algorithms/libs/rtklib/rtklib_ppp.cc @@ -1883,7 +1883,9 @@ void pppos(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav) rtk->sol.dtr[1] = rtk->x[IC_PPP(1, opt)] - rtk->x[IC_PPP(0, opt)]; for (i = 0; i < n && i < MAXOBS; i++) { - rtk->ssat[obs[i].sat - 1].snr[0] = MIN_PPP(obs[i].SNR[0], obs[i].SNR[1]); + const int ppp_snr_index = obs[i].SNR[0] <= obs[i].SNR[1] ? 0 : 1; + rtk->ssat[obs[i].sat - 1].snr[0] = obs[i].SNR[ppp_snr_index]; + rtk->ssat[obs[i].sat - 1].code[0] = obs[i].code[ppp_snr_index]; } for (i = 0; i < MAXSAT; i++) { diff --git a/src/algorithms/libs/rtklib/rtklib_rtkpos.cc b/src/algorithms/libs/rtklib/rtklib_rtkpos.cc index 136e9d0f8..a1cfb011e 100644 --- a/src/algorithms/libs/rtklib/rtklib_rtkpos.cc +++ b/src/algorithms/libs/rtklib/rtklib_rtkpos.cc @@ -2398,6 +2398,7 @@ int relpos(rtk_t *rtk, const obsd_t *obs, int nu, int nr, for (j = 0; j < NFREQ; j++) { rtk->ssat[i].vsat[j] = rtk->ssat[i].snr[j] = 0; + rtk->ssat[i].code[j] = 0; } } /* satellite positions/clocks */ @@ -2603,6 +2604,7 @@ int relpos(rtk_t *rtk, const obsd_t *obs, int nu, int nr, { /* output snr of rover receiver */ rtk->ssat[sat[i] - 1].snr[j] = obs[iu[i]].SNR[j]; + rtk->ssat[sat[i] - 1].code[j] = obs[iu[i]].code[j]; } } for (i = 0; i < MAXSAT; i++) @@ -2652,7 +2654,7 @@ void rtkinit(rtk_t *rtk, const prcopt_t *opt) { sol_t sol0 = {{0, 0}, {}, {}, {}, '0', '0', '0', 0.0, 0.0, 0.0}; ambc_t ambc0 = {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}, {}, {}, {}, 0, {}}; - ssat_t ssat0 = {0, 0, {0.0}, {0.0}, {0.0}, {'0'}, {'0'}, {'0'}, {'0'}, {'0'}, {}, {}, {}, {}, 0.0, 0.0, 0.0, 0.0, {{{0, 0}}, {{0, 0}}}, {{}, {}}}; + ssat_t ssat0 = {0, 0, {0.0}, {0.0}, {0.0}, {'0'}, {'0'}, {}, {'0'}, {'0'}, {'0'}, {}, {}, {}, {}, 0.0, 0.0, 0.0, 0.0, {{{0, 0}}, {{0, 0}}}, {{}, {}}}; int i; trace(3, "rtkinit :\n"); diff --git a/src/algorithms/libs/rtklib/rtklib_solution.cc b/src/algorithms/libs/rtklib/rtklib_solution.cc index bf20b3ecb..3999994c7 100644 --- a/src/algorithms/libs/rtklib/rtklib_solution.cc +++ b/src/algorithms/libs/rtklib/rtklib_solution.cc @@ -31,6 +31,7 @@ * ----------------------------------------------------------------------------- */ +#include "gnss_obs_codes.h" #include "rtklib_solution.h" #include "rtklib_rtkcmn.h" #include "rtklib_rtksvr.h" @@ -57,6 +58,276 @@ static const int SOLQ_NMEA[] = {/* nmea quality flags to rtklib sol quality */ SOLQ_NONE, SOLQ_SINGLE, SOLQ_DGPS, SOLQ_PPP, SOLQ_FIX, SOLQ_FLOAT, SOLQ_DR, SOLQ_NONE, SOLQ_NONE, SOLQ_NONE}; +struct nmea_gsv_signal_t +{ + unsigned char snr; + int signal_id; +}; + +static int nmea_gsv_signal_id(int sys, unsigned char code, int fallback_signal_id) +{ + switch (sys) + { + case SYS_GPS: + case SYS_SBS: + switch (code) + { + case CODE_L1C: + return 1; /* L1 C/A */ + case CODE_L1P: + case CODE_L1W: + case CODE_L1Y: + return 2; /* L1 P(Y) */ + case CODE_L1M: + return 3; /* L1 M */ + case CODE_L2P: + case CODE_L2W: + case CODE_L2Y: + return 4; /* L2 P(Y) */ + case CODE_L2S: + return 5; /* L2C-M */ + case CODE_L2L: + case CODE_L2X: + return 6; /* L2C-L */ + case CODE_L5I: + case CODE_L5X: + return 7; /* L5-I */ + case CODE_L5Q: + return 8; /* L5-Q */ + default: + return fallback_signal_id; + } + case SYS_GLO: + switch (code) + { + case CODE_L1C: + return 1; /* L1 C/A */ + case CODE_L1P: + return 2; /* L1 P */ + case CODE_L2C: + return 3; /* L2 C/A */ + case CODE_L2P: + return 4; /* L2 P */ + default: + return fallback_signal_id; + } + case SYS_GAL: + switch (code) + { + case CODE_L5I: + case CODE_L5Q: + case CODE_L5X: + return 1; /* E5a */ + case CODE_L7I: + case CODE_L7Q: + case CODE_L7X: + return 2; /* E5b */ + case CODE_L8I: + case CODE_L8Q: + case CODE_L8X: + return 3; /* E5a+b */ + case CODE_L6A: + return 4; /* E6-A */ + case CODE_L6B: + case CODE_L6C: + case CODE_L6X: + case CODE_L6Z: + return 5; /* E6-BC */ + case CODE_L1A: + return 6; /* L1-A */ + case CODE_L1B: + case CODE_L1C: + case CODE_L1X: + case CODE_L1Z: + return 7; /* L1-BC */ + default: + return fallback_signal_id; + } + case SYS_QZS: + switch (code) + { + case CODE_L1C: + return 1; /* L1 C/A */ + case CODE_L1S: + return 2; /* L1C (D) */ + case CODE_L1L: + case CODE_L1X: + return 3; /* L1C (P) */ + case CODE_L2S: + return 5; /* L2C-M */ + case CODE_L2L: + case CODE_L2X: + return 6; /* L2C-L */ + case CODE_L5I: + case CODE_L5X: + return 7; /* L5-I */ + case CODE_L5Q: + return 8; /* L5-Q */ + case CODE_L6S: + case CODE_L6X: + return 9; /* L6D */ + case CODE_L6L: + return 10; /* L6E */ + default: + return fallback_signal_id; + } + case SYS_BDS: + switch (code) + { + case CODE_L2I: + return 1; /* B1I */ + case CODE_L2Q: + return 2; /* B1Q */ + case CODE_L1C: + case CODE_L1I: + case CODE_L1Q: + return 3; /* B1C */ + case CODE_L5I: + return 5; /* B2-a */ + case CODE_L7I: + case CODE_L7Q: + case CODE_L7X: + return 11; /* B2I */ + case CODE_L6I: + return 8; /* B3I */ + case CODE_L6Q: + return 9; /* B3Q */ + case CODE_L6X: + return 10; /* B3A */ + default: + return fallback_signal_id; + } + default: + return fallback_signal_id; + } +} + +static nmea_gsv_signal_t nmea_gsv_signal(const ssat_t *ssat, int sys, int fallback_signal_id) +{ + int selected_freq = 0; + + for (int i = 1; i < NFREQ; i++) + { + if (ssat->snr[i] > ssat->snr[selected_freq]) + { + selected_freq = i; + } + } + + nmea_gsv_signal_t signal = { + ssat->snr[selected_freq], + nmea_gsv_signal_id(sys, ssat->code[selected_freq], fallback_signal_id)}; + return signal; +} + +static bool nmea_gsv_has_system(int sys, int target_sys) +{ + return target_sys == SYS_GPS ? (sys == SYS_GPS || sys == SYS_SBS) : sys == target_sys; +} + +static int nmea_gsv_prn(int sat) +{ + int prn; + const int sys = satsys(sat, &prn); + + if (sys == SYS_SBS) + { + prn += 33 - MINPRNSBS; + } + else if (sys == SYS_GLO) + { + prn += 64; + } + return prn; +} + +static char *outnmea_gsv_system(char *p, const char *sentence, const ssat_t *ssat, + int target_sys, int fallback_signal_id) +{ + char *q; + char *s; + char sum; + int prn; + int sys; + int n = 0; + std::vector sats(MAXSAT); + std::vector signal_ids; + const int MSG_TAIL = 6; + + for (int sat = 1; sat < MAXSAT && n < 12; sat++) + { + sys = satsys(sat, &prn); + if (!nmea_gsv_has_system(sys, target_sys)) + { + continue; + } + if (ssat[sat - 1].vs && ssat[sat - 1].azel[1] > 0.0) + { + const int signal_id = nmea_gsv_signal(ssat + sat - 1, sys, fallback_signal_id).signal_id; + if (std::find(signal_ids.begin(), signal_ids.end(), signal_id) == signal_ids.end()) + { + signal_ids.push_back(signal_id); + } + sats[n++] = sat; + } + } + + for (const auto signal_id : signal_ids) + { + int nsat = 0; + int nmsg; + int k; + std::vector signal_sats(MAXSAT); + + for (int i = 0; i < n; i++) + { + sys = satsys(sats[i], nullptr); + if (nmea_gsv_signal(ssat + sats[i] - 1, sys, fallback_signal_id).signal_id == signal_id) + { + signal_sats[nsat++] = sats[i]; + } + } + + nmsg = nsat <= 0 ? 0 : (nsat - 1) / 4 + 1; + + for (int i = k = 0; i < nmsg; i++) + { + s = p; + p += std::snprintf(p, MAXSOLBUF, "%s,%d,%d,%02d", sentence, nmsg, i + 1, nsat); + + for (int j = 0; j < 4; j++, k++) + { + if (k < nsat) + { + const int sat = signal_sats[k]; + const nmea_gsv_signal_t signal = nmea_gsv_signal(ssat + sat - 1, + satsys(sat, nullptr), fallback_signal_id); + double az = ssat[sat - 1].azel[0] * R2D; + if (az < 0.0) + { + az += 360.0; + } + const double el = ssat[sat - 1].azel[1] * R2D; + const double snr = signal.snr * 0.25; + p += std::snprintf(p, MAXSOLBUF - (p - s), ",%02d,%02.0f,%03.0f,%02.0f", + nmea_gsv_prn(sat), el, az, snr); + } + else + { + p += std::snprintf(p, MAXSOLBUF - (p - s), ",,,,"); + } + } + p += std::snprintf(p, MAXSOLBUF - (p - s), ",%d", signal_id); + for (q = s + 1, sum = 0; *q; q++) + { + sum ^= *q; /* check-sum */ + } + p += std::snprintf(p, MSG_TAIL, "*%02X%c%c", sum, 0x0D, 0x0A); + } + } + return p; +} + /* solution option to field separator ----------------------------------------*/ const char *opt2sep(const solopt_t *opt) @@ -1892,21 +2163,8 @@ int outnmea_gsa(unsigned char *buff, const sol_t *sol, int outnmea_gsv(unsigned char *buff, const sol_t *sol, const ssat_t *ssat) { - double az; - double el; - double snr; - int i; - int j; - int k; - int n; - int sat; - int prn; - int sys; - int nmsg; - std::vector sats(MAXSAT); char *p = reinterpret_cast(buff); char *q; - char *s; char sum; const int MSG_TAIL = 6; @@ -1922,236 +2180,11 @@ int outnmea_gsv(unsigned char *buff, const sol_t *sol, p += std::snprintf(p, MSG_TAIL, "*%02X%c%c", sum, 0x0D, 0x0A); return p - reinterpret_cast(buff); } - /* GPGSV: gps/sbas */ - for (sat = 1, n = 0; sat < MAXSAT && n < 12; sat++) - { - sys = satsys(sat, &prn); - if (sys != SYS_GPS && sys != SYS_SBS) - { - continue; - } - if (ssat[sat - 1].vs && ssat[sat - 1].azel[1] > 0.0) - { - sats[n++] = sat; - } - } - nmsg = n <= 0 ? 0 : (n - 1) / 4 + 1; - - for (i = k = 0; i < nmsg; i++) - { - s = p; - p += std::snprintf(p, MAXSOLBUF, "$GPGSV,%d,%d,%02d", nmsg, i + 1, n); - - for (j = 0; j < 4; j++, k++) - { - if (k < n) - { - if (satsys(sats[k], &prn) == SYS_SBS) - { - prn += 33 - MINPRNSBS; - } - az = ssat[sats[k] - 1].azel[0] * R2D; - if (az < 0.0) - { - az += 360.0; - } - el = ssat[sats[k] - 1].azel[1] * R2D; - snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; - p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); - } - else - { - p += std::snprintf(p, MAXSOLBUF - (s - p), ",,,,"); - } - } - p += std::snprintf(p, MAXSOLBUF - (s - p), ",1"); /* L1C/A */ - for (q = s + 1, sum = 0; *q; q++) - { - sum ^= *q; /* check-sum */ - } - p += std::snprintf(p, MSG_TAIL, "*%02X%c%c", sum, 0x0D, 0x0A); - } - /* GLGSV: glonass */ - for (sat = 1, n = 0; sat < MAXSAT && n < 12; sat++) - { - if (satsys(sat, &prn) != SYS_GLO) - { - continue; - } - if (ssat[sat - 1].vs && ssat[sat - 1].azel[1] > 0.0) - { - sats[n++] = sat; - } - } - nmsg = n <= 0 ? 0 : (n - 1) / 4 + 1; - - for (i = k = 0; i < nmsg; i++) - { - s = p; - p += std::snprintf(p, MAXSOLBUF, "$GLGSV,%d,%d,%02d", nmsg, i + 1, n); - - for (j = 0; j < 4; j++, k++) - { - if (k < n) - { - satsys(sats[k], &prn); - prn += 64; /* 65-99 */ - az = ssat[sats[k] - 1].azel[0] * R2D; - if (az < 0.0) - { - az += 360.0; - } - el = ssat[sats[k] - 1].azel[1] * R2D; - snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; - p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); - } - else - { - p += std::snprintf(p, MAXSOLBUF - (s - p), ",,,,"); - } - } - p += std::snprintf(p, MAXSOLBUF - (s - p), ",1"); /* L1C/A */ - for (q = s + 1, sum = 0; *q; q++) - { - sum ^= *q; /* check-sum */ - } - p += std::snprintf(p, MSG_TAIL, "*%02X%c%c", sum, 0x0D, 0x0A); - } - /* GAGSV: galileo */ - for (sat = 1, n = 0; sat < MAXSAT && n < 12; sat++) - { - if (satsys(sat, &prn) != SYS_GAL) - { - continue; - } - if (ssat[sat - 1].vs && ssat[sat - 1].azel[1] > 0.0) - { - sats[n++] = sat; - } - } - nmsg = n <= 0 ? 0 : (n - 1) / 4 + 1; - - for (i = k = 0; i < nmsg; i++) - { - s = p; - p += std::snprintf(p, MAXSOLBUF, "$GAGSV,%d,%d,%02d", nmsg, i + 1, n); - - for (j = 0; j < 4; j++, k++) - { - if (k < n) - { - satsys(sats[k], &prn); /* 1-36 */ - az = ssat[sats[k] - 1].azel[0] * R2D; - if (az < 0.0) - { - az += 360.0; - } - el = ssat[sats[k] - 1].azel[1] * R2D; - snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; - p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); - } - else - { - p += std::snprintf(p, MAXSOLBUF - (s - p), ",,,,"); - } - } - p += std::snprintf(p, MAXSOLBUF - (s - p), ",7"); /* L1BC */ - for (q = s + 1, sum = 0; *q; q++) - { - sum ^= *q; /* check-sum */ - } - p += std::snprintf(p, MSG_TAIL, "*%02X%c%c", sum, 0x0D, 0x0A); - } - /* QZGSV: QZSS */ - for (sat = 1, n = 0; sat < MAXSAT && n < 12; sat++) - { - if (satsys(sat, &prn) != SYS_QZS) - { - continue; - } - if (ssat[sat - 1].vs && ssat[sat - 1].azel[1] > 0.0) - { - sats[n++] = sat; - } - } - nmsg = n <= 0 ? 0 : (n - 1) / 4 + 1; - - for (i = k = 0; i < nmsg; i++) - { - s = p; - p += std::snprintf(p, MAXSOLBUF, "$QZGSV,%d,%d,%02d", nmsg, i + 1, n); - - for (j = 0; j < 4; j++, k++) - { - if (k < n) - { - satsys(sats[k], &prn); /* 193-206 */ - az = ssat[sats[k] - 1].azel[0] * R2D; - if (az < 0.0) - { - az += 360.0; - } - el = ssat[sats[k] - 1].azel[1] * R2D; - snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; - p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); - } - else - { - p += std::snprintf(p, MAXSOLBUF - (s - p), ",,,,"); - } - } - p += std::snprintf(p, MAXSOLBUF - (s - p), ",1"); /* L1C/A */ - for (q = s + 1, sum = 0; *q; q++) - { - sum ^= *q; /* check-sum */ - } - p += std::snprintf(p, MSG_TAIL, "*%02X%c%c", sum, 0x0D, 0x0A); - } - /* BDGSV: beidou */ - for (sat = 1, n = 0; sat < MAXSAT && n < 12; sat++) - { - if (satsys(sat, &prn) != SYS_BDS) - { - continue; - } - if (ssat[sat - 1].vs && ssat[sat - 1].azel[1] > 0.0) - { - sats[n++] = sat; - } - } - nmsg = n <= 0 ? 0 : (n - 1) / 4 + 1; - - for (i = k = 0; i < nmsg; i++) - { - s = p; - p += std::snprintf(p, MAXSOLBUF, "$BDGSV,%d,%d,%02d", nmsg, i + 1, n); - - for (j = 0; j < 4; j++, k++) - { - if (k < n) - { - satsys(sats[k], &prn); /* 1-63 */ - az = ssat[sats[k] - 1].azel[0] * R2D; - if (az < 0.0) - { - az += 360.0; - } - el = ssat[sats[k] - 1].azel[1] * R2D; - snr = *std::max_element(std::begin(ssat[sats[k] - 1].snr), std::end(ssat[sats[k] - 1].snr)) * 0.25; - p += std::snprintf(p, MAXSOLBUF - (s - p), ",%02d,%02.0f,%03.0f,%02.0f", prn, el, az, snr); - } - else - { - p += std::snprintf(p, MAXSOLBUF - (s - p), ",,,,"); - } - } - p += std::snprintf(p, MAXSOLBUF - (s - p), ",1"); - for (q = s + 1, sum = 0; *q; q++) - { - sum ^= *q; /* check-sum */ - } - p += std::snprintf(p, MSG_TAIL, "*%02X%c%c", sum, 0x0D, 0x0A); - } + p = outnmea_gsv_system(p, "$GPGSV", ssat, SYS_GPS, 1); + p = outnmea_gsv_system(p, "$GLGSV", ssat, SYS_GLO, 1); + p = outnmea_gsv_system(p, "$GAGSV", ssat, SYS_GAL, 7); + p = outnmea_gsv_system(p, "$QZGSV", ssat, SYS_QZS, 1); + p = outnmea_gsv_system(p, "$BDGSV", ssat, SYS_BDS, 1); return p - reinterpret_cast(buff); } diff --git a/tests/unit-tests/signal-processing-blocks/pvt/nmea_printer_test.cc b/tests/unit-tests/signal-processing-blocks/pvt/nmea_printer_test.cc index 2e83b287f..cddb566eb 100644 --- a/tests/unit-tests/signal-processing-blocks/pvt/nmea_printer_test.cc +++ b/tests/unit-tests/signal-processing-blocks/pvt/nmea_printer_test.cc @@ -15,12 +15,16 @@ */ +#include "gnss_obs_codes.h" #include "nmea_printer.h" #include "pvt_conf.h" +#include "rtklib_rtkcmn.h" #include "rtklib_rtkpos.h" +#include "rtklib_solution.h" #include "rtklib_solver.h" #include "signal_enabled_flags.h" #include +#include #include #include @@ -142,6 +146,91 @@ void NmeaPrinterTest::conf() rtkinit(&rtk, &rtklib_configuration_options); } +std::string PrintGsv(const sol_t& sol, const std::array& ssat) +{ + std::array buff{}; + outnmea_gsv(buff.data(), &sol, ssat.data()); + return std::string(reinterpret_cast(buff.data())); +} + + +TEST_F(NmeaPrinterTest, GsvUsesStrongestNonL1SnrAndMatchingSignalId) +{ + sol_t sol{}; + std::array ssat{}; + const int galileo_sat = satno(SYS_GAL, 1); + ASSERT_GT(galileo_sat, 0); + + sol.stat = SOLQ_FIX; + ssat[galileo_sat - 1].vs = 1; + ssat[galileo_sat - 1].azel[0] = 3.514475; + ssat[galileo_sat - 1].azel[1] = 0.644151; + ssat[galileo_sat - 1].snr[0] = 0; + ssat[galileo_sat - 1].code[0] = CODE_NONE; + ssat[galileo_sat - 1].snr[2] = 220; + ssat[galileo_sat - 1].code[2] = CODE_L7X; + + EXPECT_EQ(PrintGsv(sol, ssat), "$GAGSV,1,1,01,01,37,201,55,,,,,,,,,,,,,2*41\r\n"); +} + + +TEST_F(NmeaPrinterTest, GsvSplitsSatellitesBySelectedSignalId) +{ + sol_t sol{}; + std::array ssat{}; + const int gps_l1_sat = satno(SYS_GPS, 1); + const int gps_l5_sat = satno(SYS_GPS, 2); + ASSERT_GT(gps_l1_sat, 0); + ASSERT_GT(gps_l5_sat, 0); + + sol.stat = SOLQ_FIX; + ssat[gps_l1_sat - 1].vs = 1; + ssat[gps_l1_sat - 1].azel[0] = 3.514475; + ssat[gps_l1_sat - 1].azel[1] = 0.644151; + ssat[gps_l1_sat - 1].snr[0] = 204; + ssat[gps_l1_sat - 1].code[0] = CODE_L1C; + + ssat[gps_l5_sat - 1].vs = 1; + ssat[gps_l5_sat - 1].azel[0] = 2.968734; + ssat[gps_l5_sat - 1].azel[1] = 0.950019; + ssat[gps_l5_sat - 1].snr[0] = 0; + ssat[gps_l5_sat - 1].code[0] = CODE_NONE; + ssat[gps_l5_sat - 1].snr[2] = 220; + ssat[gps_l5_sat - 1].code[2] = CODE_L5X; + + EXPECT_EQ(PrintGsv(sol, ssat), + "$GPGSV,1,1,01,01,37,201,51,,,,,,,,,,,,,1*57\r\n" + "$GPGSV,1,1,01,02,54,170,55,,,,,,,,,,,,,7*56\r\n"); +} + + +TEST_F(NmeaPrinterTest, GsvReportsGpsL2AndL5Snr) +{ + sol_t sol{}; + std::array ssat{}; + const int gps_l2_sat = satno(SYS_GPS, 1); + const int gps_l5_sat = satno(SYS_GPS, 2); + ASSERT_GT(gps_l2_sat, 0); + ASSERT_GT(gps_l5_sat, 0); + + sol.stat = SOLQ_FIX; + ssat[gps_l2_sat - 1].vs = 1; + ssat[gps_l2_sat - 1].azel[0] = 3.514475; + ssat[gps_l2_sat - 1].azel[1] = 0.644151; + ssat[gps_l2_sat - 1].snr[1] = 180; + ssat[gps_l2_sat - 1].code[1] = CODE_L2S; + + ssat[gps_l5_sat - 1].vs = 1; + ssat[gps_l5_sat - 1].azel[0] = 2.968734; + ssat[gps_l5_sat - 1].azel[1] = 0.950019; + ssat[gps_l5_sat - 1].snr[2] = 220; + ssat[gps_l5_sat - 1].code[2] = CODE_L5X; + + EXPECT_EQ(PrintGsv(sol, ssat), + "$GPGSV,1,1,01,01,37,201,45,,,,,,,,,,,,,5*56\r\n" + "$GPGSV,1,1,01,02,54,170,55,,,,,,,,,,,,,7*56\r\n"); +} + TEST_F(NmeaPrinterTest, PrintLine) {