Conversation
Resolved conflicts: - Updated mReceiver to use std::unique_ptr (from upstream) - Preserved NTRIP client functionality (from ntrip-client branch) - Added onDeviceSocketStateChanged connection for NTRIP support
… request size estimation
…rror messages if need
…ion state and last error
🍎 MacOS DMG universal buildsDownload a MacOS DMG universal build of this PR for testing. 🪟 Windows buildsDownload a Windows build of this PR for testing. 📱 Android buildsDownload an Android arm64 build of this PR for testing. Other Android architectures🐧 Linux AppImage buildsDownload a Linux AppImage build of this PR for testing. |
|
@natuition , that's super nice -- are you cooperating with @edgecase14 or you took his initial submission and pushed it further? In any case, it's super nice, I'm just trying to understand how this came to be :) |
|
Hi, I took his initial proposal and expanded on it :) Having a client ntrip feature in your application is quite requested, and we believe it can open up many new use cases. I work for NATUITION, and we sell an RTK rover (https://natuition.odoo.com/en_GB/shop/n2168-navx-2678); we would be delighted to use it with your software. |
Don't hesitate to give me feedback if I need to change anything; I'm happy to help. I tried building the Android version on my macOS M4, but I couldn't get your Docker image to work, so I'm currently exploring using the NDK version available through Android Studio and compiling it. I'd really appreciate knowing how to compile so I can continue contributing to the project if needed. |
…er to use a virtual mountpoint
… the French file.
|
I'm working on it today :) |
…onnections to handle HTTP errors and chunked transfers. Improved NTRIP versioning in NtripSocketClient and NtripSourceTableFetcher.
…ude version parameter
… destructor handling
…d update destructor to be noexcept
…SourceTableFetcher
…ct attempts when socket is busy
…ed layout consistency
Pull request SummaryGeneration date: 2026-03-10 Compared to Validation note: commit What this pull request adds over master
|
Co-authored-by: Matthias Kuhn <matthias@opengis.ch>
Removed NTRIP-related messages and translations from French localization for automatic addition by transifex.
nirvn
left a comment
There was a problem hiding this comment.
I had a quick look through it, it's progressing super nicely. I've dropped some comments based on a local build (without a Bluetooth device yet, that'll be tomorrow).
| const char *stateStr = nullptr; | ||
| switch ( int( mSocket->state() ) ) | ||
| { | ||
| case int( QAbstractSocket::UnconnectedState ): | ||
| stateStr = "UnconnectedState"; | ||
| break; | ||
| case int( QAbstractSocket::HostLookupState ): | ||
| stateStr = "HostLookupState"; | ||
| break; | ||
| case int( QAbstractSocket::ConnectingState ): | ||
| stateStr = "ConnectingState"; | ||
| break; | ||
| case int( QAbstractSocket::ConnectedState ): | ||
| stateStr = "ConnectedState"; | ||
| break; | ||
| case int( QAbstractSocket::BoundState ): | ||
| stateStr = "BoundState"; | ||
| break; | ||
| case int( QAbstractSocket::ClosingState ): | ||
| stateStr = "ClosingState"; | ||
| break; | ||
| case int( QAbstractSocket::ListeningState ): | ||
| stateStr = "ListeningState"; | ||
| break; | ||
| default: | ||
| stateStr = "UnknownState"; | ||
| break; | ||
| } | ||
|
|
||
| qInfo() << "Bluetooth Socket State: Error:" << stateStr; | ||
|
|
There was a problem hiding this comment.
Is this needed? If it is, I'd rather incorporate that into the error message we already have a few lines above. Also, we don't use const char * in the code, we stick to QString unless a function parameter requires otherwise.
| case QBluetoothSocket::SocketState::ServiceLookupState: | ||
| // Service discovery is part of the connection handshake, do not treat it as disconnected. | ||
| currentState = QAbstractSocket::ConnectingState; | ||
| break; |
There was a problem hiding this comment.
I'm seeing some nice fixes including this one that would be nice to have submitted as separate PRs. It'd make this PR a little smaller, and fixes easier to merge / backport.
| if ( mSocket->state() == QBluetoothSocket::SocketState::ServiceLookupState | ||
| || mSocket->state() == QBluetoothSocket::SocketState::ConnectingState | ||
| || mSocket->state() == QBluetoothSocket::SocketState::ConnectedState ) | ||
| { | ||
| qInfo() << "BluetoothReceiver: Skipping connect attempt, socket busy in state" << mSocket->state(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
We handle the ConnectedState state situation in BluetoothReceiver::handleConnectDevice , if there's a need to add the ConnectingState and ServiceLookupState, we should do that over there, where we gracefully then attempt to disconnect and auto-connecting afterwards.
| begin : 05.02.2026 | ||
| copyright : (C) 2026 by Vincent LAMBERT | ||
| email : |
There was a problem hiding this comment.
Could we have an email from you? :) valid for all 6 files introduced into the source tree.
| Q_PROPERTY( QString ntripHost READ ntripHost WRITE setNtripHost NOTIFY ntripHostChanged ) | ||
| Q_PROPERTY( int ntripPort READ ntripPort WRITE setNtripPort NOTIFY ntripPortChanged ) | ||
| Q_PROPERTY( int ntripVersion READ ntripVersion WRITE setNtripVersion NOTIFY ntripVersionChanged ) | ||
| Q_PROPERTY( QString ntripMountpoint READ ntripMountpoint WRITE setNtripMountpoint NOTIFY ntripMountpointChanged ) | ||
| Q_PROPERTY( QString ntripUsername READ ntripUsername WRITE setNtripUsername NOTIFY ntripUsernameChanged ) | ||
| Q_PROPERTY( QString ntripPassword READ ntripPassword WRITE setNtripPassword NOTIFY ntripPasswordChanged ) |
There was a problem hiding this comment.
Here instead of adding 6 properties, I'd prefer to pass on a single ntrip configuration. The actual configuration would be a NtripConfiguration class flagged as Q_GADGET so remote object handles its properties smoothly (just like we do with GnssPositionInformation). That'll give us a nice way to grow configuration settings without having to expose countless properties attached to the Positioning item itself.
The other big, big advantage here is that ATM, we disconnect and reconnect every time a property is changed. So, it means that when first passing on the properties when QField launches, we call the ntrip client's start() functions 6 distinct times. By passing on a single configuration property, we will only connect once :)
| onClicked: { | ||
| ntripFetcher.fetch(positioningSettings.ntripHost, positioningSettings.ntripPort, positioningSettings.ntripUsername, positioningSettings.ntripPassword, positioningSettings.ntripVersion); |
There was a problem hiding this comment.
We should clear the text in ntripFetchErrorLabel when re-fetching, it's otherwise quite confusing for users to understand a new fetching operation is ongoing.
| const int headerEnd = data.indexOf( "\r\n\r\n" ); | ||
| const QByteArray body = ( headerEnd >= 0 ) ? data.mid( headerEnd + 4 ) : data; |
There was a problem hiding this comment.
The logic needs tweaking here. With the NTRIP server I'm testing here, the \r\n\r\n chunk arrives at the very end of the buffer.
You could trim the data, that'd remove the last \r\n\r\n.
| #ifdef WITH_BLUETOOTH | ||
| if ( auto bluetoothReceiver = dynamic_cast<BluetoothReceiver *>( mReceiver.get() ) ) | ||
| { | ||
| connect( mNtripClient.get(), &NtripClient::correctionDataReceived, bluetoothReceiver, &BluetoothReceiver::onCorrectionDataReceived ); | ||
| } | ||
| #endif |
There was a problem hiding this comment.
I think we can expand that to tcp and serial port.
And here - as well as when setting up the receiver - instead of an #ifdef , we should rely on a new receiver capability flag (see AbstractGnssReceiver::Capability).
| bool mNtripSendNmea = true; | ||
| QString mNtripHost = "crtk.net"; | ||
| int mNtripPort = 2101; | ||
| int mNtripVersion = 1; | ||
| QString mNtripMountpoint = "NEAR"; | ||
| QString mNtripUsername = "QfieldNtripClient"; | ||
| QString mNtripPassword = "QfieldNtripClient"; |
There was a problem hiding this comment.
This will become a NtripConfiguration object. When we finalize the PR, let's make sure the default host, mount point, username and password are empty.
For the time being I understand it makes it super easy for you to test things out :)
| QfSwitch { | ||
| id: enableNtripClient | ||
| Layout.preferredWidth: implicitContentWidth | ||
| Layout.alignment: Qt.AlignTop | ||
| checked: positioningSettings.enableNtripClient | ||
| onCheckedChanged: { | ||
| positioningSettings.enableNtripClient = checked; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
When NTRIP is disabled here, we should hide the NTRIP configuration settings (you can see the network proxy in the general tab as a good example of that UX).
|
@natuition , oh BTW, this branch failed to rebase against current master for me. I assume a manual rebase on your end will be needed. For the time being, I manually inserted changes and made a test branch (https://github.com/opengisch/QField/tree/ntrip_review) to test against latest Qt 6.10.2. |
|
Hello, I have a week of vacation, so I'll look at it later. Thanks for the feedback. |
Add NTRIP client integration for RTK corrections
Add complete NTRIP client implementation to provide real-time kinematic
corrections to external GNSS receivers via Bluetooth.
Changes
Core Implementation
Properties & State Management
ntripStatus(QString) with ntripState (enum: Connected/Disconnected)Bug Fixes
Requirements
The NTRIP client is only enabled when an external receiver is active and
properly configured with all required connection parameters.
Testing
Tested on macOS with NavX GNSS receiver.
Screenshots
RTK positioning active with NTRIP corrections (using Centipede network) :

NTRIP client configuration panel :
