1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtBluetooth module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qdeclarativebluetoothsocket_p.h" |
41 | |
42 | #include <QtCore/QLoggingCategory> |
43 | #include <QtCore/QPointer> |
44 | #include <QtCore/QStringList> |
45 | #include <QtCore/QDataStream> |
46 | #include <QtCore/QByteArray> |
47 | |
48 | #include <QtBluetooth/QBluetoothDeviceInfo> |
49 | #include <QtBluetooth/QBluetoothAddress> |
50 | #include <QtBluetooth/QBluetoothSocket> |
51 | |
52 | /*! |
53 | \qmltype BluetoothSocket |
54 | \instantiates QDeclarativeBluetoothSocket |
55 | \inqmlmodule QtBluetooth |
56 | \since 5.2 |
57 | \brief Enables connecting and communicating with a Bluetooth service or |
58 | device. |
59 | |
60 | \sa QBluetoothSocket |
61 | \sa QDataStream |
62 | |
63 | It allows a QML class connect to another Bluetooth device and exchange strings |
64 | with it. Data is sent and received using a QDataStream object allowing type |
65 | safe transfers of QStrings. QDataStream is a well known format and can be |
66 | decoded by non-Qt applications. Note that for the ease of use, BluetoothSocket |
67 | is only well suited for use with strings. If you want to |
68 | use a binary protocol for your application's communication you should |
69 | consider using its C++ counterpart QBluetoothSocket. |
70 | |
71 | Connections to remote devices can be over RFCOMM or L2CAP. Either the remote port |
72 | or service UUID is required. This is specified by creating a BluetoothService, |
73 | or passing in the service return from BluetoothDiscoveryModel. |
74 | */ |
75 | |
76 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_QML) |
77 | |
78 | class QDeclarativeBluetoothSocketPrivate |
79 | { |
80 | public: |
81 | QDeclarativeBluetoothSocketPrivate(QDeclarativeBluetoothSocket *bs) |
82 | : m_dbs(bs), |
83 | m_error(QDeclarativeBluetoothSocket::NoError), |
84 | m_state(QDeclarativeBluetoothSocket::NoServiceSet), |
85 | m_componentCompleted(false), |
86 | m_connected(false) |
87 | { |
88 | |
89 | } |
90 | |
91 | ~QDeclarativeBluetoothSocketPrivate() |
92 | { |
93 | delete m_socket; |
94 | } |
95 | |
96 | void connect() |
97 | { |
98 | Q_ASSERT(m_service); |
99 | //qDebug() << "Connecting to: " << m_service->serviceInfo()->device().address().toString(); |
100 | m_error = QDeclarativeBluetoothSocket::NoError; |
101 | |
102 | if (m_socket) |
103 | m_socket->deleteLater(); |
104 | |
105 | QBluetoothServiceInfo::Protocol socketProtocol; |
106 | if (m_service->serviceInfo()->socketProtocol() == QBluetoothServiceInfo::L2capProtocol) |
107 | socketProtocol = QBluetoothServiceInfo::L2capProtocol; |
108 | else if (m_service->serviceInfo()->socketProtocol() == QBluetoothServiceInfo::RfcommProtocol) |
109 | socketProtocol = QBluetoothServiceInfo::RfcommProtocol; |
110 | else |
111 | socketProtocol = QBluetoothServiceInfo::UnknownProtocol; |
112 | |
113 | m_socket = new QBluetoothSocket(socketProtocol); |
114 | m_socket->connectToService(service: *m_service->serviceInfo()); |
115 | QObject::connect(sender: m_socket, signal: &QBluetoothSocket::connected, |
116 | receiver: m_dbs, slot: &QDeclarativeBluetoothSocket::socket_connected); |
117 | QObject::connect(sender: m_socket, signal: &QBluetoothSocket::disconnected, |
118 | receiver: m_dbs, slot: &QDeclarativeBluetoothSocket::socket_disconnected); |
119 | QObject::connect(sender: m_socket, signal: QOverload<QBluetoothSocket::SocketError>::of(ptr: &QBluetoothSocket::error), |
120 | receiver: m_dbs, slot: &QDeclarativeBluetoothSocket::socket_error); |
121 | QObject::connect(sender: m_socket, signal: &QBluetoothSocket::stateChanged, |
122 | receiver: m_dbs, slot: &QDeclarativeBluetoothSocket::socket_state); |
123 | QObject::connect(sender: m_socket, signal: &QIODevice::readyRead, |
124 | receiver: m_dbs, slot: &QDeclarativeBluetoothSocket::socket_readyRead); |
125 | } |
126 | |
127 | QDeclarativeBluetoothSocket *m_dbs; |
128 | QDeclarativeBluetoothService *m_service = nullptr; |
129 | QBluetoothSocket *m_socket = nullptr; |
130 | QDeclarativeBluetoothSocket::Error m_error; |
131 | QDeclarativeBluetoothSocket::SocketState m_state; |
132 | bool m_componentCompleted; |
133 | bool m_connected; |
134 | }; |
135 | |
136 | QDeclarativeBluetoothSocket::QDeclarativeBluetoothSocket(QObject *parent) : |
137 | QObject(parent) |
138 | { |
139 | d = new QDeclarativeBluetoothSocketPrivate(this); |
140 | } |
141 | |
142 | QDeclarativeBluetoothSocket::QDeclarativeBluetoothSocket(QDeclarativeBluetoothService *service, QObject *parent) |
143 | : QObject(parent) |
144 | { |
145 | d = new QDeclarativeBluetoothSocketPrivate(this); |
146 | d->m_service = service; |
147 | } |
148 | |
149 | QDeclarativeBluetoothSocket::QDeclarativeBluetoothSocket(QBluetoothSocket *socket, QDeclarativeBluetoothService *service, QObject *parent) |
150 | : QObject(parent) |
151 | { |
152 | d = new QDeclarativeBluetoothSocketPrivate(this); |
153 | d->m_service = service; |
154 | d->m_socket = socket; |
155 | d->m_connected = true; |
156 | d->m_componentCompleted = true; |
157 | |
158 | QObject::connect(sender: socket, SIGNAL(connected()), receiver: this, SLOT(socket_connected())); |
159 | QObject::connect(sender: socket, SIGNAL(disconnected()), receiver: this, SLOT(socket_disconnected())); |
160 | QObject::connect(sender: socket, SIGNAL(error(QBluetoothSocket::SocketError)), receiver: this, SLOT(socket_error(QBluetoothSocket::SocketError))); |
161 | QObject::connect(sender: socket, SIGNAL(stateChanged(QBluetoothSocket::SocketState)), receiver: this, SLOT(socket_state(QBluetoothSocket::SocketState))); |
162 | QObject::connect(sender: socket, SIGNAL(readyRead()), receiver: this, SLOT(socket_readyRead())); |
163 | } |
164 | |
165 | |
166 | QDeclarativeBluetoothSocket::~QDeclarativeBluetoothSocket() |
167 | { |
168 | delete d; |
169 | } |
170 | |
171 | void QDeclarativeBluetoothSocket::componentComplete() |
172 | { |
173 | d->m_componentCompleted = true; |
174 | |
175 | if (d->m_connected && d->m_service) |
176 | d->connect(); |
177 | } |
178 | |
179 | /*! |
180 | \qmlproperty BluetoothService BluetoothSocket::service |
181 | |
182 | This property holds the details of the remote service to connect to. It can be |
183 | set to a static BluetoothService with a fixed description, or a service returned |
184 | by service discovery. |
185 | */ |
186 | |
187 | |
188 | QDeclarativeBluetoothService *QDeclarativeBluetoothSocket::service() |
189 | { |
190 | return d->m_service; |
191 | } |
192 | |
193 | void QDeclarativeBluetoothSocket::setService(QDeclarativeBluetoothService *service) |
194 | { |
195 | d->m_service = service; |
196 | |
197 | if (!d->m_componentCompleted) |
198 | return; |
199 | |
200 | if (d->m_connected) |
201 | d->connect(); |
202 | emit serviceChanged(); |
203 | } |
204 | |
205 | /*! |
206 | \qmlproperty bool BluetoothSocket::connected |
207 | |
208 | This property holds the connection state of the socket. If the socket is |
209 | connected to peer, it returns true. It can be set true or false to control the |
210 | connection. When set to true, the property will not return true until the |
211 | connection is established. |
212 | |
213 | */ |
214 | |
215 | |
216 | bool QDeclarativeBluetoothSocket::connected() const |
217 | { |
218 | if (!d->m_socket) |
219 | return false; |
220 | |
221 | return d->m_socket->state() == QBluetoothSocket::ConnectedState; |
222 | } |
223 | |
224 | void QDeclarativeBluetoothSocket::setConnected(bool connected) |
225 | { |
226 | d->m_connected = connected; |
227 | if (connected && d->m_componentCompleted) { |
228 | if (d->m_service) { |
229 | d->connect(); |
230 | } |
231 | else { |
232 | qCWarning(QT_BT_QML) << "BluetoothSocket::setConnected called before a service was set" ; |
233 | } |
234 | } |
235 | |
236 | if (!connected && d->m_socket){ |
237 | d->m_socket->close(); |
238 | } |
239 | } |
240 | |
241 | /*! |
242 | \qmlproperty enumeration BluetoothSocket::error |
243 | |
244 | This property holds the last error that happened. |
245 | \list |
246 | \li \c{NoError} |
247 | \li \c{UnknownSocketError} |
248 | \li \c{HostNotFoundError} |
249 | \li \c{ServiceNotFoundError} |
250 | \li \c{NetworkError} |
251 | \li \c{UnsupportedProtocolError} |
252 | \li \c{RemoteHostClosedError} |
253 | \endlist |
254 | |
255 | The errors are derived from \l QBluetoothSocket::SocketError. This property is read-only. |
256 | */ |
257 | |
258 | |
259 | QDeclarativeBluetoothSocket::Error QDeclarativeBluetoothSocket::error() const |
260 | { |
261 | return d->m_error; |
262 | } |
263 | |
264 | void QDeclarativeBluetoothSocket::socket_connected() |
265 | { |
266 | emit connectedChanged(); |
267 | } |
268 | |
269 | void QDeclarativeBluetoothSocket::socket_disconnected() |
270 | { |
271 | d->m_socket->deleteLater(); |
272 | d->m_socket = nullptr; |
273 | emit connectedChanged(); |
274 | } |
275 | |
276 | void QDeclarativeBluetoothSocket::socket_error(QBluetoothSocket::SocketError error) |
277 | { |
278 | d->m_error = static_cast<QDeclarativeBluetoothSocket::Error>(error); |
279 | |
280 | emit errorChanged(); |
281 | } |
282 | |
283 | void QDeclarativeBluetoothSocket::socket_state(QBluetoothSocket::SocketState state) |
284 | { |
285 | d->m_state = static_cast<QDeclarativeBluetoothSocket::SocketState>(state); |
286 | |
287 | emit stateChanged(); |
288 | } |
289 | |
290 | /*! |
291 | \qmlproperty enumeration BluetoothSocket::state |
292 | |
293 | This property holds the current state of the socket. |
294 | |
295 | \list |
296 | \li \c{NoServiceSet} |
297 | \li \c{Unconnected} |
298 | \li \c{ServiceLookup} |
299 | \li \c{Connecting} |
300 | \li \c{Connected} |
301 | \li \c{Closing} |
302 | \li \c{Listening} |
303 | \li \c{Bound} |
304 | \endlist |
305 | |
306 | The states (except \c{NoServiceSet}) are derived from \l QBluetoothSocket::SocketState. This property is read-only. |
307 | \c{NoServiceSet} indicates that the socket state is not yet available due to the \l service not being |
308 | set yet. |
309 | */ |
310 | QDeclarativeBluetoothSocket::SocketState QDeclarativeBluetoothSocket::state() const |
311 | { |
312 | return d->m_state; |
313 | } |
314 | |
315 | void QDeclarativeBluetoothSocket::socket_readyRead() |
316 | { |
317 | emit dataAvailable(); |
318 | } |
319 | |
320 | /*! |
321 | \qmlproperty string BluetoothSocket::stringData |
322 | |
323 | This property receives or sends data to a remote Bluetooth device. Arrival of |
324 | data can be detected by connecting to this properties changed signal and can be read via |
325 | stringData. Setting stringData will transmit the string. |
326 | If excessive amounts of data are sent, the function may block sending. Reading will |
327 | never block. |
328 | */ |
329 | |
330 | QString QDeclarativeBluetoothSocket::stringData() |
331 | { |
332 | if (!d->m_socket|| !d->m_socket->bytesAvailable()) |
333 | return QString(); |
334 | |
335 | QString data; |
336 | while (d->m_socket->canReadLine()) { |
337 | QByteArray line = d->m_socket->readLine(); |
338 | data += QString::fromUtf8(str: line.constData(), size: line.length()); |
339 | } |
340 | return data; |
341 | } |
342 | |
343 | /*! |
344 | This method transmits the string data passed with "data" to the remote device. |
345 | If excessive amounts of data are sent, the function may block sending. |
346 | */ |
347 | |
348 | void QDeclarativeBluetoothSocket::sendStringData(const QString &data) |
349 | { |
350 | if (!d->m_connected || !d->m_socket){ |
351 | qCWarning(QT_BT_QML) << "Writing data to unconnected socket" ; |
352 | return; |
353 | } |
354 | |
355 | QByteArray text = data.toUtf8() + '\n'; |
356 | d->m_socket->write(data: text); |
357 | } |
358 | |
359 | void QDeclarativeBluetoothSocket::newSocket(QBluetoothSocket *socket, QDeclarativeBluetoothService *service) |
360 | { |
361 | if (d->m_socket){ |
362 | delete d->m_socket; |
363 | } |
364 | |
365 | d->m_service = service; |
366 | d->m_socket = socket; |
367 | d->m_connected = true; |
368 | d->m_componentCompleted = true; |
369 | d->m_error = NoError; |
370 | |
371 | QObject::connect(sender: socket, signal: &QBluetoothSocket::connected, |
372 | receiver: this, slot: &QDeclarativeBluetoothSocket::socket_connected); |
373 | QObject::connect(sender: socket, signal: &QBluetoothSocket::disconnected, |
374 | receiver: this, slot: &QDeclarativeBluetoothSocket::socket_disconnected); |
375 | QObject::connect(sender: socket, signal: QOverload<QBluetoothSocket::SocketError>::of(ptr: &QBluetoothSocket::error), |
376 | receiver: this, slot: &QDeclarativeBluetoothSocket::socket_error); |
377 | QObject::connect(sender: socket, signal: &QBluetoothSocket::stateChanged, |
378 | receiver: this, slot: &QDeclarativeBluetoothSocket::socket_state); |
379 | QObject::connect(sender: socket, signal: &QIODevice::readyRead, |
380 | receiver: this, slot: &QDeclarativeBluetoothSocket::socket_readyRead); |
381 | |
382 | socket_state(state: socket->state()); |
383 | |
384 | emit connectedChanged(); |
385 | } |
386 | |