diff options
author | Christian Kandeler <christian.kandeler@theqtcompany.com> | 2015-10-15 12:00:13 +0200 |
---|---|---|
committer | Alex Blasche <alexander.blasche@theqtcompany.com> | 2015-11-19 06:52:35 +0000 |
commit | 57353d3f91f89e6e364b9798277048e25b9c63b8 (patch) | |
tree | f8bd609be5ade91e7b09dfb8f1d48578e51021e7 | |
parent | a8d80a81d56f0fb85c41bf6ec5716c5ce7e5dac0 (diff) |
QLowEnergyController: Listen for and accept GATT connection requests.
BlueZ only. No requests are handled yet.
Change-Id: I25058989beb5b3ae02a4f43eeaec09c8225198dc
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
-rw-r--r-- | src/bluetooth/qlowenergycontroller.cpp | 8 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller.h | 2 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_bluez.cpp | 122 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.h | 5 |
4 files changed, 132 insertions, 5 deletions
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index 9a9327a5..f8832f1a 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -306,6 +306,10 @@ void QLowEnergyControllerPrivate::setState( return; state = newState; + if (state == QLowEnergyController::UnconnectedState + && role == QLowEnergyController::PeripheralRole) { + remoteDevice.clear(); + } emit q->stateChanged(state); } @@ -769,10 +773,8 @@ QLowEnergyService *QLowEnergyController::createServiceObject( /*! Starts advertising the data given in \a advertisingData and \a scanResponseData, using the parameters set in \a parameters. The controller has to be in the \l PeripheralRole. - \omit - If \a parameters indicates that the advertisement should be connectable, then this call + If \a parameters indicates that the advertisement should be connectable, then this function also starts listening for incoming client connections. - \endomit Providing \a scanResponseData is not required, as it is not applicable for certain configurations of \c parameters. diff --git a/src/bluetooth/qlowenergycontroller.h b/src/bluetooth/qlowenergycontroller.h index 66928da8..1abc395c 100644 --- a/src/bluetooth/qlowenergycontroller.h +++ b/src/bluetooth/qlowenergycontroller.h @@ -93,6 +93,8 @@ public: QObject *parent = 0); static QLowEnergyController *createPeripheral(QObject *parent = 0); + // TODO: Allow to set connection timeout (disconnect when no data has been exchanged for n seconds). + ~QLowEnergyController(); QBluetoothAddress localAddress() const; diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 45d392d8..ea7a763a 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -43,6 +43,9 @@ #include <QtBluetooth/QLowEnergyService> #include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> #define ATTRIBUTE_CHANNEL_ID 4 @@ -205,7 +208,8 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() securityLevelValue(-1), encryptionChangePending(false), hciManager(0), - advertiser(0) + advertiser(0), + serverSocketNotifier(0) { qRegisterMetaType<QList<QLowEnergyHandle> >(); @@ -220,7 +224,58 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() { + closeServerSocket(); } + +class ServerSocket +{ +public: + bool listen(const QBluetoothAddress &localAdapter) + { + m_socket = ::socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (m_socket == -1) { + qCWarning(QT_BT_BLUEZ) << "socket creation failed:" << qt_error_string(errno); + return false; + } + sockaddr_l2 addr; + + // memset should be in std namespace for C++ compilers, but we also need to support + // broken ones that put it in the global one. + using namespace std; + memset(&addr, 0, sizeof addr); + + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = htobs(ATTRIBUTE_CHANNEL_ID); + addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; + convertAddress(localAdapter.toUInt64(), addr.l2_bdaddr.b); + if (::bind(m_socket, reinterpret_cast<sockaddr *>(&addr), sizeof addr) == -1) { + qCWarning(QT_BT_BLUEZ) << "bind() failed:" << qt_error_string(errno); + return false; + } + if (::listen(m_socket, 1)) { + qCWarning(QT_BT_BLUEZ) << "listen() failed:" << qt_error_string(errno); + return false; + } + return true; + } + + ~ServerSocket() + { + if (m_socket != -1) + close(m_socket); + } + + int takeSocket() + { + const int socket = m_socket; + m_socket = -1; + return socket; + } + +private: + int m_socket = -1; +}; + void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData) @@ -234,6 +289,24 @@ void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingPa } setState(QLowEnergyController::AdvertisingState); advertiser->startAdvertising(); + if (params.mode() == QLowEnergyAdvertisingParameters::AdvNonConnInd + || params.mode() == QLowEnergyAdvertisingParameters::AdvScanInd) { + qCDebug(QT_BT_BLUEZ) << "Non-connectable advertising requested, " + "not listening for connections."; + return; + } + + ServerSocket serverSocket; + if (!serverSocket.listen(localAdapter)) { + setError(QLowEnergyController::AdvertisingError); + setState(QLowEnergyController::UnconnectedState); + return; + } + + const int socketFd = serverSocket.takeSocket(); + serverSocketNotifier = new QSocketNotifier(socketFd, QSocketNotifier::Read, this); + connect(serverSocketNotifier, &QSocketNotifier::activated, this, + &QLowEnergyControllerPrivate::handleConnectionRequest); } void QLowEnergyControllerPrivate::stopAdvertising() @@ -315,7 +388,8 @@ void QLowEnergyControllerPrivate::l2cpDisconnected() { Q_Q(QLowEnergyController); - securityLevelValue = -1; + invalidateServices(); + resetController(); setState(QLowEnergyController::UnconnectedState); emit q->disconnected(); } @@ -1772,4 +1846,48 @@ void QLowEnergyControllerPrivate::handleAdvertisingError() setState(QLowEnergyController::UnconnectedState); } +void QLowEnergyControllerPrivate::handleConnectionRequest() +{ + if (state != QLowEnergyController::AdvertisingState) { + qCWarning(QT_BT_BLUEZ) << "Incoming connection request in unexpected state" << state; + return; + } + Q_ASSERT(serverSocketNotifier); + serverSocketNotifier->setEnabled(false); + sockaddr_l2 clientAddr; + socklen_t clientAddrSize = sizeof clientAddr; + const int clientSocket = accept(serverSocketNotifier->socket(), + reinterpret_cast<sockaddr *>(&clientAddr), &clientAddrSize); + if (clientSocket == -1) { + // Not fatal in itself. The next one might succeed. + qCWarning(QT_BT_BLUEZ) << "accept() failed:" << qt_error_string(errno); + serverSocketNotifier->setEnabled(true); + return; + } + remoteDevice = QBluetoothAddress(convertAddress(clientAddr.l2_bdaddr.b)); + qCDebug(QT_BT_BLUEZ) << "GATT connection from device" << remoteDevice; + + closeServerSocket(); + l2cpSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol, this); + connect(l2cpSocket, &QBluetoothSocket::disconnected, + this, &QLowEnergyControllerPrivate::l2cpDisconnected); + connect(l2cpSocket, static_cast<void (QBluetoothSocket::*)(QBluetoothSocket::SocketError)> + (&QBluetoothSocket::error), this, &QLowEnergyControllerPrivate::l2cpErrorChanged); + connect(l2cpSocket, &QIODevice::readyRead, this, &QLowEnergyControllerPrivate::l2cpReadyRead); + l2cpSocket->d_ptr->lowEnergySocketType = addressType == QLowEnergyController::PublicAddress + ? BDADDR_LE_PUBLIC : BDADDR_LE_RANDOM; + l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol); + setState(QLowEnergyController::ConnectedState); +} + +void QLowEnergyControllerPrivate::closeServerSocket() +{ + if (!serverSocketNotifier) + return; + serverSocketNotifier->disconnect(); + close(serverSocketNotifier->socket()); + serverSocketNotifier->deleteLater(); + serverSocketNotifier = nullptr; +} + QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 722de692..d91c2c73 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -80,6 +80,7 @@ QT_BEGIN_NAMESPACE #if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE) class HciManager; +class QSocketNotifier; #elif defined(QT_ANDROID_BLUETOOTH) class LowEnergyNotificationHub; #endif @@ -180,6 +181,10 @@ private: HciManager *hciManager; QLeAdvertiser *advertiser; + QSocketNotifier *serverSocketNotifier; + + void handleConnectionRequest(); + void closeServerSocket(); void sendCommand(const QByteArray &packet); void sendNextPendingRequest(); |