Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ set(QFIELD_CORE_SRCS
positioning/udpreceiver.cpp
positioning/positioning.cpp
positioning/positioningsource.cpp
positioning/ntripclient.cpp
positioning/ntripsocketclient.cpp
positioning/positioningdevicemodel.cpp
positioning/geofencer.cpp
positioning/positioninginformationmodel.cpp
Expand Down
54 changes: 52 additions & 2 deletions src/core/positioning/bluetoothreceiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,26 @@ BluetoothReceiver::~BluetoothReceiver()
mSocket = nullptr;
}

void BluetoothReceiver::onCorrectionDataReceived( const QByteArray &data )
{
if ( !mSocket || !mSocket->isOpen() )
{
qWarning() << "Bluetooth socket not open—cannot forward corrections.";
return;
}

qint64 bytesWritten = mSocket->write( data );
if ( bytesWritten == -1 )
{
qWarning() << "Failed to write corrections to Bluetooth socket:" << mSocket->errorString();
}
else
{
qDebug() << "Forwarded" << bytesWritten << "bytes of correction data to Bluetooth.";
}
}


void BluetoothReceiver::handleDisconnectDevice()
{
if ( mSocket->state() != QBluetoothSocket::SocketState::UnconnectedState )
Expand Down Expand Up @@ -137,6 +157,36 @@ void BluetoothReceiver::handleError( QBluetoothSocket::SocketError error )
}

qInfo() << QStringLiteral( "BluetoothReceiver: Error: %1" ).arg( mLastError );
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;
if ( mSocket->isOpen() )
{
mSocket->close();
Expand Down Expand Up @@ -184,7 +234,7 @@ void BluetoothReceiver::repairDevice( const QBluetoothAddress &address )
case QBluetoothLocalDevice::Paired:
case QBluetoothLocalDevice::AuthorizedPaired:
{
mSocket->connectToService( address, QBluetoothUuid( QBluetoothUuid::ServiceClassUuid::SerialPort ), QBluetoothSocket::ReadOnly );
mSocket->connectToService( address, QBluetoothUuid( QBluetoothUuid::ServiceClassUuid::SerialPort ), QBluetoothSocket::ReadWrite );
break;
}

Expand All @@ -206,7 +256,7 @@ void BluetoothReceiver::pairingFinished( const QBluetoothAddress &address, QBlue
case QBluetoothLocalDevice::Paired:
case QBluetoothLocalDevice::AuthorizedPaired:
{
mSocket->connectToService( address, QBluetoothUuid( QBluetoothUuid::ServiceClassUuid::SerialPort ), QBluetoothSocket::ReadOnly );
mSocket->connectToService( address, QBluetoothUuid( QBluetoothUuid::ServiceClassUuid::SerialPort ), QBluetoothSocket::ReadWrite );
break;
}

Expand Down
1 change: 1 addition & 0 deletions src/core/positioning/bluetoothreceiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class BluetoothReceiver : public NmeaGnssReceiver

public slots:
QString socketStateString() override;
void onCorrectionDataReceived( const QByteArray &data );

private slots:
/**
Expand Down
128 changes: 128 additions & 0 deletions src/core/positioning/ntripclient.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include "ntripclient.h"
#include "ntripsocketclient.h"

#include <QDebug>

NtripClient::NtripClient( QObject *parent )
: QObject( parent )
{
}

NtripClient::~NtripClient()
{
stop();
}

void NtripClient::start( const QString &ntripHost, const quint16 &port, const QString &mountpoint, const QString &username, const QString &password )
{
if ( mReply )
{
qWarning() << "NtripClient already running";
return;
}

mBytesSent = 0;
mBytesReceived = 0;

NtripSocketClient *client = new NtripSocketClient( this );

connect( client, &NtripSocketClient::correctionDataReceived, [this]( const QByteArray &data ) {
mBytesReceived += data.size();

quint8 firstByte = quint8( data.at( 0 ) );
if ( firstByte == 0xD3 )
{
qDebug() << "RTCM chunk:";
}
else if ( firstByte == 0x73 )
{
qDebug() << "SPARTN chunk:";
}
else
{
qDebug() << "UNKNOWN chunk:";
}

qDebug() << data.size() << "bytes";
// send to your GNSS device
Comment on lines +33 to +47
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's clean some of that debug prior to merging so we don't get too verbose when running QField in debug mode.

emit correctionDataReceived( data );
emit bytesCountersChanged();
} );

connect( client, &NtripSocketClient::errorOccurred, [this]( const QString &msg ) {
qWarning() << msg;
emit errorOccurred( msg );
} );

connect( client, &NtripSocketClient::streamConnected, [this]() {
emit streamConnected();
} );

mBytesSent = client->start(
ntripHost,
port,
"/" + mountpoint,
username,
password );

// Emit immediately to show sent bytes
emit bytesCountersChanged();


//connect(mReply, &QNetworkReply::readyRead, this, &NtripClient::onReadyRead);
//connect(mReply, &QNetworkReply::finished, this, &NtripClient::onFinished);
//connect(mReply, xxxQOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred),
// this, &NtripClient::onError);
Comment on lines +72 to +75
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover?

}

void NtripClient::stop()
{
if ( mReply )
{
disconnect( mReply, nullptr, this, nullptr ); // ✅ Disconnect all signals
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No emojis in source file please :)


if ( mReply->isRunning() )
{
mReply->abort(); // ✅ Cancel the request
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

}
mReply->deleteLater();
mReply = nullptr;
}
}

/*
void NtripClient::onReadyRead()
{
QByteArray data = mReply->readAll();
qInfo() << data + "\n";
emit correctionDataReceived(data);
}
*/
Comment on lines +93 to +100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be removed?


void NtripClient::onFinished()
{
if ( mReply )
{
emit errorOccurred( "NTRIP connection closed" );
}
// Schedule cleanup after Qt finishes emitting signals
QMetaObject::invokeMethod( this, "stop", Qt::QueuedConnection );
}

void NtripClient::onError( QNetworkReply::NetworkError code )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be connected.

{
int status = mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
QString reason = mReply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();

qWarning() << "HTTP status during error:" << status << reason;
qWarning() << "Network error code:" << code;

emit errorOccurred(
QStringLiteral( "Network error %1, HTTP %2 %3" )
.arg( code )
.arg( status )
.arg( reason ) );
//emit errorOccurred(QStringLiteral("NTRIP error: %1").arg(code));
// Schedule cleanup after Qt finishes emitting signals
QMetaObject::invokeMethod( this, "stop", Qt::QueuedConnection );
}
39 changes: 39 additions & 0 deletions src/core/positioning/ntripclient.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QUrl>

class NtripClient : public QObject
{
Q_OBJECT
public:
explicit NtripClient( QObject *parent = nullptr );
~NtripClient();

void start( const QString &ntripHost, const quint16 &port, const QString &mountpoint, const QString &username, const QString &password );

qint64 bytesSent() const { return mBytesSent; }
qint64 bytesReceived() const { return mBytesReceived; }

public slots:
void stop();
Comment on lines +20 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move the stop() into public to sit just below start(). Just a styling preference here.


signals:
void correctionDataReceived( const QByteArray &rtcmData );
void errorOccurred( const QString &message );
void bytesCountersChanged();
void streamConnected();

private slots:
//void onReadyRead();
void onFinished();
void onError( QNetworkReply::NetworkError code );

private:
QNetworkAccessManager mNetworkManager;
QNetworkReply *mReply = nullptr;
Comment on lines +35 to +36
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two members don't appear to be used here?

qint64 mBytesSent = 0;
qint64 mBytesReceived = 0;
};
103 changes: 103 additions & 0 deletions src/core/positioning/ntripsocketclient.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include "ntripsocketclient.h"

#include <QDebug>

NtripSocketClient::NtripSocketClient( QObject *parent )
: QObject( parent )
{
connect( &mSocket, &QTcpSocket::connected, this, &NtripSocketClient::onConnected );
connect( &mSocket, &QTcpSocket::readyRead, this, &NtripSocketClient::onReadyRead );
connect( &mSocket, &QTcpSocket::disconnected, this, &NtripSocketClient::onDisconnected );
connect( &mSocket, QOverload<QAbstractSocket::SocketError>::of( &QTcpSocket::errorOccurred ),
this, &NtripSocketClient::onSocketError );
}

NtripSocketClient::~NtripSocketClient()
{
stop();
}

qint64 NtripSocketClient::start(
const QString &host,
quint16 port,
const QString &mountpoint,
const QString &username,
const QString &password )
{
mHeadersSent = false;
mSocket.connectToHost( host, port );

QString credentials = username + ":" + password;
QByteArray base64 = credentials.toUtf8().toBase64();

QByteArray request;
request.append( "GET " + mountpoint.toUtf8() + " HTTP/1.0\r\n" );
request.append( "Host: " + host.toUtf8() + ":" + QByteArray::number( port ) + "\r\n" );
request.append( "User-Agent: QField NTRIP QtSocketClient/1.0\r\n" );
request.append( "Accept: */*\r\n" );
request.append( "Authorization: Basic " + base64 + "\r\n" );
request.append( "Connection: close\r\n" );
//request.append("Ntrip-Version: Ntrip/2.0\r\n");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we support both ntrip v1 and ntrip v2 protocol? Is there anything to do here other than advertising the protocol version in the header?

Either way, we need to clean this up :)

request.append( "\r\n" );

connect( &mSocket, &QTcpSocket::connected, [this, request]() {
mSocket.write( request );
mSocket.flush();
} );

return request.size();
}
Comment on lines +20 to +49
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's save the host/port/mountpoint/username/password into members of the class, it'll also us to provide more context when reporting connection status / error / etc.


void NtripSocketClient::stop()
{
if ( mSocket.isOpen() )
{
mSocket.disconnectFromHost();
mSocket.close();
}
}

void NtripSocketClient::onConnected()
{
qDebug() << "Connected to NTRIP caster.";
}

void NtripSocketClient::onReadyRead()
{
QByteArray data = mSocket.readAll();

// If headers not processed yet, discard them
if ( !mHeadersSent )
{
int headerEnd = data.indexOf( "\r\n\r\n" );
if ( headerEnd != -1 )
{
QByteArray headerData = data.left( headerEnd );
qDebug() << "Received HTTP headers:\n"
<< headerData;
data = data.mid( headerEnd + 4 );
mHeadersSent = true;
emit streamConnected();
}
else
{
// Wait for more data
return;
}
}
Comment on lines +82 to +87
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you read all the data here, the buffer will be gone, meaning that as your waiting for more data, you'll lose the data that came before, is there a need to preserve the data to append it to the next frame until the \r\n\r\n bit arrives?


if ( !data.isEmpty() )
{
emit correctionDataReceived( data );
}
}

void NtripSocketClient::onDisconnected()
{
emit errorOccurred( "Disconnected from NTRIP caster." );
}

void NtripSocketClient::onSocketError( QAbstractSocket::SocketError error )
{
emit errorOccurred( "Socket error: " + QString::number( error ) + " (" + mSocket.errorString() + ")" );
}
Loading
Loading