QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
BluetoothClassicWorker.cc
Go to the documentation of this file.
3
4QGC_LOGGING_CATEGORY(BluetoothClassicWorkerLog, "Comms.Bluetooth.BluetoothClassicWorker")
5QGC_LOGGING_CATEGORY(BluetoothClassicWorkerVerboseLog, "Comms.Bluetooth.BluetoothClassicWorker.Verbose")
6
8 : BluetoothWorker(config, parent)
9{
10}
11
13{
14 _setupClassicSocket();
15}
16
18{
19 qCDebug(BluetoothClassicWorkerLog) << "Attempting to connect to" << _device.name() << "Mode: Classic";
20
21 // Destroy stale socket — QBluetoothSocket must be recreated after errors
22 if (_socket) {
23 _socket->disconnect();
24 _socket->deleteLater();
25 _socket = nullptr;
26 }
27
28 _setupClassicSocket();
29
30 if (!_socket) {
31 emit errorOccurred(tr("Socket not available"));
32 return;
33 }
34
35 _socket->connectToService(_device.address(), SPP_UUID);
36}
37
39{
40 if (_classicDiscovery && _classicDiscovery->isActive()) {
41 _classicDiscovery->stop();
42 }
43
44 if (_socket) {
45 _socket->disconnectFromService();
46 }
47}
48
49void BluetoothClassicWorker::onWriteData(const QByteArray &data)
50{
51 _writeClassicData(data);
52}
53
55{
56 if (_classicDiscovery && _classicDiscovery->isActive()) {
57 _classicDiscovery->stop();
58 }
59}
60
62{
63 // Classic Bluetooth has no special reset — just let reconnect proceed
64}
65
67{
70
71 if (_socket) {
72 _socket->disconnect();
73 if (_socket->state() == QBluetoothSocket::SocketState::ConnectedState) {
74 _socket->disconnectFromService();
75 }
76 _socket->deleteLater();
77 }
78}
79
80void BluetoothClassicWorker::_setupClassicSocket()
81{
82 if (_socket) {
83 return;
84 }
85
86 _socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this);
87
88 (void) connect(_socket.data(), &QBluetoothSocket::connected, this, &BluetoothClassicWorker::_onSocketConnected);
89 (void) connect(_socket.data(), &QBluetoothSocket::disconnected, this, &BluetoothClassicWorker::_onSocketDisconnected);
90 (void) connect(_socket.data(), &QBluetoothSocket::readyRead, this, &BluetoothClassicWorker::_onSocketReadyRead);
91 (void) connect(_socket.data(), &QBluetoothSocket::errorOccurred, this, &BluetoothClassicWorker::_onSocketErrorOccurred);
92
93 if (BluetoothClassicWorkerLog().isDebugEnabled()) {
94 (void) connect(_socket.data(), &QBluetoothSocket::bytesWritten, this, &BluetoothClassicWorker::_onSocketBytesWritten);
95
96 (void) connect(_socket.data(), &QBluetoothSocket::stateChanged, this,
97 [](QBluetoothSocket::SocketState state) {
98 qCDebug(BluetoothClassicWorkerLog) << "Bluetooth Socket State Changed:" << state;
99 });
100 }
101}
102
103void BluetoothClassicWorker::_writeClassicData(const QByteArray &data)
104{
105 if (!_socket || !_socket->isWritable()) {
106 emit errorOccurred(tr("Socket is not writable"));
107 return;
108 }
109
110 qint64 totalBytesWritten = 0;
111 while (totalBytesWritten < data.size()) {
112 const qint64 bytesWritten = _socket->write(data.constData() + totalBytesWritten,
113 data.size() - totalBytesWritten);
114 if (bytesWritten == -1) {
115 emit errorOccurred(tr("Write failed: %1").arg(_socket->errorString()));
116 return;
117 } else if (bytesWritten == 0) {
118 emit errorOccurred(tr("Write returned 0 bytes"));
119 return;
120 }
121 totalBytesWritten += bytesWritten;
122 }
123
124 emit dataSent(data.first(totalBytesWritten));
125}
126
127void BluetoothClassicWorker::_onSocketConnected()
128{
129 qCDebug(BluetoothClassicWorkerLog) << "=== Classic Bluetooth Connection Established ===" << _device.name();
130 qCDebug(BluetoothClassicWorkerLog) << " Address:" << _device.address().toString();
131 qCDebug(BluetoothClassicWorkerLog) << " Protocol: RFCOMM (SPP)";
132
133 _connected = true;
136 emit connected();
137}
138
139void BluetoothClassicWorker::_onSocketDisconnected()
140{
141 qCDebug(BluetoothClassicWorkerLog) << "Socket disconnected from device:" << _device.name();
142 _connected = false;
143 emit disconnected();
144
145 if (!_intentionalDisconnect.load() && _socket && _reconnectTimer) {
146 qCDebug(BluetoothClassicWorkerLog) << "Starting reconnect timer";
147 _reconnectTimer->start();
148 }
149}
150
151void BluetoothClassicWorker::_onSocketReadyRead()
152{
153 if (!_socket) {
154 return;
155 }
156
157 const QByteArray data = _socket->readAll();
158 if (!data.isEmpty()) {
159 qCDebug(BluetoothClassicWorkerVerboseLog) << _device.name() << "Received" << data.size() << "bytes";
160 emit dataReceived(data);
161 }
162}
163
164void BluetoothClassicWorker::_onSocketBytesWritten(qint64 bytes)
165{
166 qCDebug(BluetoothClassicWorkerVerboseLog) << _device.name() << "Wrote" << bytes << "bytes";
167}
168
169void BluetoothClassicWorker::_onSocketErrorOccurred(QBluetoothSocket::SocketError socketError)
170{
171 QString errorString;
172 if (_socket) {
173 errorString = _socket->errorString();
174 } else {
175 errorString = tr("Socket error: Null Socket");
176 }
177
178 qCWarning(BluetoothClassicWorkerLog) << "Socket error:" << socketError << errorString;
180
182
183 // If we never successfully connected, emit disconnected to reset UI state
184 if (!_connected.load()) {
185 qCDebug(BluetoothClassicWorkerLog) << "Connection attempt failed, emitting disconnected signal";
186 emit disconnected();
187 }
188
189 // Attempt service discovery for service-related errors only
190 if ((socketError == QBluetoothSocket::SocketError::ServiceNotFoundError) ||
191 (socketError == QBluetoothSocket::SocketError::UnsupportedProtocolError)) {
192 qCDebug(BluetoothClassicWorkerLog) << "Service-related error, attempting fallback discovery";
193 _startClassicServiceDiscovery();
194 }
195}
196
197void BluetoothClassicWorker::_startClassicServiceDiscovery()
198{
199 if (_classicDiscovery && _classicDiscovery->isActive()) {
200 return;
201 }
202
203 if (!_classicDiscovery) {
204 _classicDiscovery = new QBluetoothServiceDiscoveryAgent(_device.address(), this);
205 (void) connect(_classicDiscovery.data(), &QBluetoothServiceDiscoveryAgent::serviceDiscovered,
206 this, &BluetoothClassicWorker::_onClassicServiceDiscovered);
207 (void) connect(_classicDiscovery.data(), &QBluetoothServiceDiscoveryAgent::finished,
208 this, &BluetoothClassicWorker::_onClassicServiceDiscoveryFinished);
209 (void) connect(_classicDiscovery.data(), &QBluetoothServiceDiscoveryAgent::canceled,
210 this, &BluetoothClassicWorker::_onClassicServiceDiscoveryCanceled);
211 (void) connect(_classicDiscovery.data(), &QBluetoothServiceDiscoveryAgent::errorOccurred,
212 this, &BluetoothClassicWorker::_onClassicServiceDiscoveryError);
213 }
214
215 qCDebug(BluetoothClassicWorkerLog) << "Starting classic service discovery on" << _device.name();
216 _classicDiscoveredService = QBluetoothServiceInfo();
217 _classicDiscovery->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
218
221 _serviceDiscoveryTimer->start();
222 }
223}
224
225void BluetoothClassicWorker::_onClassicServiceDiscovered(const QBluetoothServiceInfo &serviceInfo)
226{
227 qCDebug(BluetoothClassicWorkerLog) << "Classic service discovered: UUIDs=" << serviceInfo.serviceClassUuids()
228 << "RFCOMM channel=" << serviceInfo.serverChannel()
229 << "L2CAP PSM=" << serviceInfo.protocolServiceMultiplexer();
230
231 const QList<QBluetoothUuid> serviceUuids = serviceInfo.serviceClassUuids();
232 const bool isSerial = serviceUuids.contains(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::SerialPort));
233 const bool hasRfcommChannel = serviceInfo.serverChannel() > 0;
234
235 if (isSerial || (hasRfcommChannel && !_classicDiscoveredService.isValid())) {
236 _classicDiscoveredService = serviceInfo;
237 }
238}
239
240void BluetoothClassicWorker::_onClassicServiceDiscoveryFinished()
241{
244 }
245
246 if (!_classicDiscoveredService.isValid()) {
247 qCWarning(BluetoothClassicWorkerLog) << "No suitable classic service found";
248 return;
249 }
250
251 if (!_socket) {
252 _setupClassicSocket();
253 }
254
255 qCDebug(BluetoothClassicWorkerLog) << "Connecting using discovered service";
256 _socket->connectToService(_classicDiscoveredService);
257}
258
259void BluetoothClassicWorker::_onClassicServiceDiscoveryCanceled()
260{
263 }
264
265 qCDebug(BluetoothClassicWorkerLog) << "Classic service discovery canceled";
266}
267
268void BluetoothClassicWorker::_onClassicServiceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error error)
269{
272 }
273
274 const QString e = _classicDiscovery ? _classicDiscovery->errorString() : tr("Service discovery error: %1").arg(error);
275 qCWarning(BluetoothClassicWorkerLog) << e;
276 emit errorOccurred(e);
277}
QString errorString
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void onWriteData(const QByteArray &data) override
void onResetAfterConsecutiveFailures() override
void onServiceDiscoveryTimeout() override
void dataReceived(const QByteArray &data)
QPointer< QTimer > _serviceDiscoveryTimer
void dataSent(const QByteArray &data)
void errorOccurred(const QString &errorString)
QPointer< QTimer > _reconnectTimer
std::atomic< int > _reconnectAttempts
std::atomic< bool > _connected
const QBluetoothDeviceInfo _device
std::atomic< bool > _intentionalDisconnect