mirror of
https://github.com/gnss-sdr/gnss-sdr
synced 2026-10-09 09:11:51 +00:00
Enable usage of Virtual Reference Stations
This commit is contained in:
+5
-1
@@ -112,7 +112,11 @@ All notable changes to GNSS-SDR will be documented in this file.
|
||||
(`PVT.ntrip_tls_enabled=true`). It reconnects without blocking the GNU Radio
|
||||
work function, filters station changes and stale corrections, redacts
|
||||
credentials from RTKLIB traces, and retains an explicitly labeled single-point
|
||||
fallback when configured. VRS/GGA is not yet supported.
|
||||
fallback when configured. VRS and nearest-station mountpoints are supported:
|
||||
the client periodically reports the rover position upstream as an NMEA GGA
|
||||
sentence (`PVT.ntrip_send_gga`, enabled by default, with the cadence set by
|
||||
`PVT.ntrip_gga_period_ms`, 10 s by default), starting as soon as the receiver
|
||||
produces its first position solution.
|
||||
- QZSS ambiguities are now resolved in their own group instead of jointly with
|
||||
GPS, avoiding integer fixes across the GPS-QZSS inter-system bias, and the
|
||||
RTCM 3 decoder accepts the final RTCM 3.3 BeiDou ephemeris message type 1042
|
||||
|
||||
@@ -583,13 +583,15 @@ PVT.ntrip_reconnect_interval_ms=10000
|
||||
PVT.ntrip_max_correction_age_s=5.0
|
||||
PVT.ntrip_station_id=0
|
||||
PVT.ntrip_fallback_to_single=true
|
||||
PVT.ntrip_send_gga=true
|
||||
PVT.ntrip_gga_period_ms=10000
|
||||
\endverbatim
|
||||
|
||||
The caster address, mountpoint, username, password, and password-environment variable name default to empty strings; the remaining defaults are those shown above, except that <code>PVT.ntrip_client_enabled</code> defaults to <code>false</code>. Set either <code>PVT.ntrip_password</code> or <code>PVT.ntrip_password_env</code>, never both. The latter names an environment variable from which GNSS-SDR reads the password and avoids placing the password in the configuration file. Station ID <code>0</code> accepts any station; otherwise, set the expected RTCM station ID. Setting <code>PVT.ntrip_reconnect_interval_ms=0</code> disables operational reconnect attempts; the one immediate v2-to-v1 compatibility retry still runs and is not counted as an automatic reconnect.
|
||||
|
||||
The correction stream must provide an RTCM 3 base position message 1005 or 1006 and GPS observation messages carrying both L1 and L2, either as message 1004 or as MSM4 through MSM7 (1074 through 1077). Corrections older than <code>PVT.ntrip_max_correction_age_s</code> are not used. Enabling this client forces a separate internal single-point solver for receiver-clock correction, independently of the RTK solver. With <code>PVT.ntrip_fallback_to_single=true</code>, the receiver reports an explicitly labeled <code>SOLQ_SINGLE</code> solution while corrections are unavailable, incomplete, or stale; it does not label that fallback as an RTK solution.
|
||||
|
||||
This client sends an NTRIP version 2 HTTP/1.1 request by default and accepts a legacy v1 reply on that connection. If the fully-sent v2 exchange closes or times out before receiving any response bytes, or returns HTTP 400, 501, or 505, it opens one fresh connection and retries using the v1 HTTP/1.0 request. Once selected, v1 is retained for operational reconnects until the client is stopped and started again. DNS, TCP, TLS, and partial-write failures before the request is sent do not trigger a downgrade; <code>PVT.ntrip_version=1</code> forces v1 from the first connection. Setting <code>PVT.ntrip_tls_enabled=true</code> encrypts both attempts with TLS 1.2 or newer, validating the caster certificate against the system CA store and its host name. Without TLS, Basic authentication only encodes credentials; it does not encrypt them, so use a trusted network or a suitable secure tunnel, and prefer <code>PVT.ntrip_password_env</code> over a password stored in a file. VRS mountpoints requiring rover GGA messages are not supported.
|
||||
This client sends an NTRIP version 2 HTTP/1.1 request by default and accepts a legacy v1 reply on that connection. If the fully-sent v2 exchange closes or times out before receiving any response bytes, or returns HTTP 400, 501, or 505, it opens one fresh connection and retries using the v1 HTTP/1.0 request. Once selected, v1 is retained for operational reconnects until the client is stopped and started again. DNS, TCP, TLS, and partial-write failures before the request is sent do not trigger a downgrade; <code>PVT.ntrip_version=1</code> forces v1 from the first connection. Setting <code>PVT.ntrip_tls_enabled=true</code> encrypts both attempts with TLS 1.2 or newer, validating the caster certificate against the system CA store and its host name. Without TLS, Basic authentication only encodes credentials; it does not encrypt them, so use a trusted network or a suitable secure tunnel, and prefer <code>PVT.ntrip_password_env</code> over a password stored in a file. VRS and nearest-station mountpoints that require rover GGA reports are supported: with <code>PVT.ntrip_send_gga=true</code> (the default), the client periodically sends an NMEA GGA sentence derived from the receiver's current position estimate, every <code>PVT.ntrip_gga_period_ms</code> (10 s by default, accepted range 1 s to 24 hours). No GGA is sent before the receiver produces its first position solution; the single-point bootstrap fix is enough for the caster to start serving the virtual station. Set <code>PVT.ntrip_send_gga=false</code> if the rover position must not be reported to the caster; physical-base mountpoints work either way.
|
||||
|
||||
<code>PVT.flag_rtcm_server</code> is unrelated to the NTRIP correction client. It controls an outbound raw TCP server for RTCM messages generated by GNSS-SDR; it neither connects to a caster nor supplies corrections to the RTK solver. The outbound server and inbound NTRIP client can be enabled independently.
|
||||
|
||||
|
||||
@@ -242,6 +242,8 @@ Rtklib_Pvt::Rtklib_Pvt(const ConfigurationInterface* configuration,
|
||||
pvt_output_parameters.ntrip_fallback_to_single = configuration->property(role + ".ntrip_fallback_to_single", pvt_output_parameters.ntrip_fallback_to_single);
|
||||
pvt_output_parameters.ntrip_tls_enabled = configuration->property(role + ".ntrip_tls_enabled", pvt_output_parameters.ntrip_tls_enabled);
|
||||
pvt_output_parameters.ntrip_version = configuration->property(role + ".ntrip_version", pvt_output_parameters.ntrip_version);
|
||||
pvt_output_parameters.ntrip_send_gga = configuration->property(role + ".ntrip_send_gga", pvt_output_parameters.ntrip_send_gga);
|
||||
pvt_output_parameters.ntrip_gga_period_ms = configuration->property(role + ".ntrip_gga_period_ms", pvt_output_parameters.ntrip_gga_period_ms);
|
||||
|
||||
if (pvt_output_parameters.ntrip_version != 1 && pvt_output_parameters.ntrip_version != 2)
|
||||
{
|
||||
@@ -339,6 +341,11 @@ Rtklib_Pvt::Rtklib_Pvt(const ConfigurationInterface* configuration,
|
||||
{
|
||||
throw std::invalid_argument("NTRIP timeout and reconnect intervals must be within 1000 ms and 24 hours; reconnect may also be zero");
|
||||
}
|
||||
if (pvt_output_parameters.ntrip_send_gga &&
|
||||
(pvt_output_parameters.ntrip_gga_period_ms < 1000 || pvt_output_parameters.ntrip_gga_period_ms > max_ntrip_interval_ms))
|
||||
{
|
||||
throw std::invalid_argument(role + ".ntrip_gga_period_ms must be within 1000 ms and 24 hours");
|
||||
}
|
||||
if (!std::isfinite(pvt_output_parameters.ntrip_max_correction_age_s) || pvt_output_parameters.ntrip_max_correction_age_s <= 0.0)
|
||||
{
|
||||
throw std::invalid_argument(role + ".ntrip_max_correction_age_s must be a finite positive value");
|
||||
|
||||
@@ -685,6 +685,8 @@ rtklib_pvt_gs::rtklib_pvt_gs(uint32_t nchannels,
|
||||
ntrip_config.station_id = conf_.ntrip_station_id;
|
||||
ntrip_config.version = conf_.ntrip_version;
|
||||
ntrip_config.tls_enabled = conf_.ntrip_tls_enabled;
|
||||
ntrip_config.send_gga = conf_.ntrip_send_gga;
|
||||
ntrip_config.gga_period_ms = conf_.ntrip_gga_period_ms;
|
||||
const auto clear_local_credentials = [&ntrip_config]() {
|
||||
const auto clear_string = [](std::string* value) {
|
||||
if (value->empty())
|
||||
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
int32_t ntrip_timeout_ms = 10000;
|
||||
int32_t ntrip_reconnect_interval_ms = 10000;
|
||||
int32_t ntrip_version = 2; // 2: prefer v2 with one compatible v1 retry; 1: force legacy v1
|
||||
int32_t ntrip_gga_period_ms = 10000;
|
||||
|
||||
uint16_t rtcm_tcp_port = 0;
|
||||
uint16_t rtcm_station_id = 0;
|
||||
@@ -107,6 +108,7 @@ public:
|
||||
bool ntrip_client_enabled = false;
|
||||
bool ntrip_fallback_to_single = true;
|
||||
bool ntrip_tls_enabled = false;
|
||||
bool ntrip_send_gga = true; // VRS/nearest-station casters need the rover GGA to serve corrections
|
||||
bool use_e6_for_pvt = true;
|
||||
bool use_has_corrections = true;
|
||||
bool use_unhealthy_sats = false;
|
||||
|
||||
@@ -875,6 +875,12 @@ public:
|
||||
return d_received_first_v2_request;
|
||||
}
|
||||
|
||||
// safe to poll while the caster is serving
|
||||
bool received_gga() const
|
||||
{
|
||||
return d_received_gga.load();
|
||||
}
|
||||
|
||||
private:
|
||||
void record_start_failure(const char* operation)
|
||||
{
|
||||
@@ -1068,6 +1074,7 @@ private:
|
||||
std::chrono::steady_clock::now() +
|
||||
std::chrono::milliseconds(SERVER_TIMEOUT_MS);
|
||||
unsigned char discard[256];
|
||||
std::string upstream;
|
||||
while (!d_stop_requested.load() &&
|
||||
std::chrono::steady_clock::now() < close_deadline)
|
||||
{
|
||||
@@ -1085,6 +1092,21 @@ private:
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (count > 0 && !d_received_gga.load())
|
||||
{
|
||||
upstream.append(reinterpret_cast<const char*>(discard),
|
||||
static_cast<std::size_t>(count));
|
||||
if (upstream.find("$GPGGA") != std::string::npos)
|
||||
{
|
||||
d_received_gga.store(true);
|
||||
}
|
||||
// a GGA sentence is under 128 bytes: keep only enough
|
||||
// to bridge a split across recv() boundaries
|
||||
if (upstream.size() > 512)
|
||||
{
|
||||
upstream.erase(0, upstream.size() - 128);
|
||||
}
|
||||
}
|
||||
}
|
||||
shutdown(client_socket, SHUT_RDWR);
|
||||
close(client_socket);
|
||||
@@ -1129,6 +1151,7 @@ private:
|
||||
bool d_server_closed_after_payload = false;
|
||||
bool d_server_closed_on_accept = false;
|
||||
bool d_received_first_v2_request = false;
|
||||
std::atomic<bool> d_received_gga{false};
|
||||
std::string d_start_failure;
|
||||
int d_start_errno = 0;
|
||||
};
|
||||
@@ -1772,6 +1795,50 @@ TEST(NtripRtcmClientTest, AuthenticatedFragmentedStreamPublishesFixedBaseSnapsho
|
||||
}
|
||||
|
||||
|
||||
TEST(NtripRtcmClientTest, RoverGgaReachesTheCasterWhenEnabled)
|
||||
{
|
||||
using namespace ntrip_rtcm_client_test;
|
||||
|
||||
Loopback_Ntrip_Caster caster;
|
||||
ASSERT_TRUE(caster.start()) << caster.start_failure() << ": "
|
||||
<< std::strerror(caster.start_errno());
|
||||
ASSERT_NE(0, caster.port());
|
||||
|
||||
Ntrip_Rtcm_Client_Config config;
|
||||
config.enabled = true;
|
||||
config.host = "localhost";
|
||||
config.port = caster.port();
|
||||
config.mountpoint = "/BASE";
|
||||
config.username = TRACE_USERNAME;
|
||||
config.password = TRACE_PASSWORD;
|
||||
config.reconnect_interval_ms = 500;
|
||||
config.timeout_ms = 1000;
|
||||
config.max_age_s = 1.0;
|
||||
config.station_id = TEST_STATION_ID;
|
||||
config.send_gga = true;
|
||||
config.gga_period_ms = 100;
|
||||
|
||||
Ntrip_Rtcm_Client client(config);
|
||||
// A VRS caster serves no corrections until it receives the rover GGA,
|
||||
// so the position must be known before the stream connects
|
||||
client.update_rover_position({{4797642.0, 166322.0, 4185504.0}});
|
||||
ASSERT_TRUE(client.start());
|
||||
|
||||
const std::chrono::steady_clock::time_point gga_deadline =
|
||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(2000);
|
||||
while (!caster.received_gga() &&
|
||||
std::chrono::steady_clock::now() < gga_deadline)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
client.stop();
|
||||
caster.join();
|
||||
|
||||
EXPECT_TRUE(caster.accepted_client());
|
||||
EXPECT_TRUE(caster.received_gga());
|
||||
}
|
||||
|
||||
|
||||
TEST(NtripRtcmClientTest, NtripV2ChunkedStreamPublishesFixedBaseSnapshot)
|
||||
{
|
||||
using namespace ntrip_rtcm_client_test;
|
||||
|
||||
@@ -189,6 +189,23 @@ TEST_F(RtklibPvtNtripConfigurationTest, RejectsInvalidAgeOrTimeoutBeforeConnecti
|
||||
}
|
||||
|
||||
|
||||
TEST_F(RtklibPvtNtripConfigurationTest, RejectsInvalidGgaPeriodOnlyWhenGgaIsEnabled)
|
||||
{
|
||||
using namespace rtklib_pvt_ntrip_configuration_test_detail;
|
||||
std::unique_ptr<InMemoryConfiguration> bad_period = make_valid_ntrip_configuration();
|
||||
bad_period->supersede_property("PVT.ntrip_gga_period_ms", "999");
|
||||
expect_invalid_configuration(*bad_period, "ntrip_gga_period_ms");
|
||||
|
||||
// the period is not used while the GGA upload is disabled
|
||||
std::unique_ptr<InMemoryConfiguration> gga_disabled = make_valid_ntrip_configuration();
|
||||
gga_disabled->supersede_property("PVT.ntrip_send_gga", "false");
|
||||
gga_disabled->supersede_property("PVT.ntrip_gga_period_ms", "999");
|
||||
std::unique_ptr<Rtklib_Pvt> adapter;
|
||||
EXPECT_NO_THROW(adapter.reset(new Rtklib_Pvt(gga_disabled.get(), ROLE, 2, 0)));
|
||||
ASSERT_NE(nullptr, adapter);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(RtklibPvtNtripConfigurationTest, RejectsSignalsOutsideGpsL1L2BeforeConnecting)
|
||||
{
|
||||
using namespace rtklib_pvt_ntrip_configuration_test_detail;
|
||||
|
||||
Reference in New Issue
Block a user