diff options
-rw-r--r-- | src/bluetooth/qlowenergycontroller.cpp | 2 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_bluez.cpp | 154 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.h | 6 | ||||
-rw-r--r-- | tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp | 8 |
4 files changed, 157 insertions, 13 deletions
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index 00c51adf..bdbb2e48 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -297,7 +297,7 @@ QLowEnergyCharacteristic QLowEnergyControllerPrivate::characteristicForHandle( } /*! - Returns a valid descriptor if \a handle blongs to a descriptor; + Returns a valid descriptor if \a handle belongs to a descriptor; otherwise an invalid one. */ QLowEnergyDescriptor QLowEnergyControllerPrivate::descriptorForHandle( diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 79d5add6..6c74fdc9 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -66,6 +66,10 @@ #define ATT_OP_READ_BY_GROUP_RESPONSE 0x11 #define ATT_OP_WRITE_REQUEST 0x12 //write characteristic with response #define ATT_OP_WRITE_RESPONSE 0x13 +#define ATT_OP_PREPARE_WRITE_REQUEST 0x16 //write values longer than MTU-3 -> queueing +#define ATT_OP_PREPARE_WRITE_RESPONSE 0x17 +#define ATT_OP_EXECUTE_WRITE_REQUEST 0x18 //write values longer than MTU-3 -> execute queue +#define ATT_OP_EXECUTE_WRITE_RESPONSE 0x19 #define ATT_OP_HANDLE_VAL_NOTIFICATION 0x1b //informs about value change #define ATT_OP_HANDLE_VAL_INDICATION 0x1d //informs about value change -> requires reply #define ATT_OP_HANDLE_VAL_CONFIRMATION 0x1e //answer for ATT_OP_HANDLE_VAL_INDICATION @@ -78,6 +82,8 @@ #define READ_REQUEST_SIZE 3 #define READ_BLOB_REQUEST_SIZE 5 #define WRITE_REQUEST_SIZE 3 // same size for WRITE_COMMAND +#define PREPARE_WRITE_SIZE 5 +#define EXECUTE_WRITE_SIZE 2 #define MTU_EXCHANGE_SIZE 3 // GATT error codes @@ -793,16 +799,65 @@ void QLowEnergyControllerPrivate::processReply( const QByteArray newValue = request.reference2.toByteArray(); if (!descriptorHandle) { + //TODO use updateValueOfCharacteristic() service->characteristicList[charHandle].value = newValue; QLowEnergyCharacteristic ch(service, charHandle); emit service->characteristicWritten(ch, newValue); } else { + //TODO use updateValueOfDescriptor() service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue; QLowEnergyDescriptor descriptor(service, charHandle, descriptorHandle); emit service->descriptorWritten(descriptor, newValue); } } break; + case ATT_OP_PREPARE_WRITE_REQUEST: //error case + case ATT_OP_PREPARE_WRITE_RESPONSE: + { + //Prepare write command response + Q_ASSERT(request.command == ATT_OP_PREPARE_WRITE_REQUEST); + + uint handleData = request.reference.toUInt(); + const QLowEnergyHandle charHandle = (handleData & 0xffff); + const QByteArray newValue = request.reference2.toByteArray(); + const int writtenPayload = ((handleData >> 16) & 0xffff); + + if (isErrorResponse) { + //emits error on cancellation and aborts existing prepare reuqests + sendExecuteWriteRequest(charHandle, newValue, true); + } else { + if (writtenPayload < newValue.size()) { + sendNextPrepareWriteRequest(charHandle, newValue, writtenPayload); + } else { + sendExecuteWriteRequest(charHandle, newValue, false); + } + } + } + break; + case ATT_OP_EXECUTE_WRITE_REQUEST: //error case + case ATT_OP_EXECUTE_WRITE_RESPONSE: + { + //right now used in connection with long characteristic value writes + Q_ASSERT(request.command == ATT_OP_EXECUTE_WRITE_REQUEST); + + uint handleData = request.reference.toUInt(); + const QLowEnergyHandle charHandle = handleData & 0xffff; + bool wasCancellation = !((handleData >> 16) & 0xffff); + const QByteArray newValue = request.reference2.toByteArray(); + + QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + Q_ASSERT(!service.isNull()); + + if (isErrorResponse || wasCancellation) { + // charHandle == 0 -> cancellation + service->setError(QLowEnergyService::CharacteristicWriteError); + } else { + service->characteristicList[charHandle].value = newValue; + QLowEnergyCharacteristic ch(service, charHandle); + emit service->characteristicWritten(ch, newValue); + } + } + break; default: qCDebug(QT_BT_BLUEZ) << "Unknown packet: " << response.toHex(); break; @@ -1120,6 +1175,84 @@ void QLowEnergyControllerPrivate::discoverNextDescriptor( sendNextPendingRequest(); } +void QLowEnergyControllerPrivate::sendNextPrepareWriteRequest( + const QLowEnergyHandle charHandle, const QByteArray &newValue, + quint16 offset) +{ + QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + Q_ASSERT(service); + const QLowEnergyHandle valueHandle = service->characteristicList[charHandle].valueHandle; + + quint8 packet[PREPARE_WRITE_SIZE]; + packet[0] = ATT_OP_PREPARE_WRITE_REQUEST; + bt_put_unaligned(htobs(valueHandle), (quint16 *) &packet[1]); // attribute handle + bt_put_unaligned(htobs(offset), (quint16 *) &packet[3]); // offset into newValue + + qCDebug(QT_BT_BLUEZ) << "Writing long characteristic (prepare):" + << hex << charHandle; + + + const int maxAvailablePayload = mtuSize - PREPARE_WRITE_SIZE; + const int requiredPayload = qMin(newValue.size() - offset, maxAvailablePayload); + const int dataSize = PREPARE_WRITE_SIZE + requiredPayload; + + Q_ASSERT((offset + requiredPayload) <= newValue.size()); + Q_ASSERT(dataSize <= mtuSize); + + QByteArray data(dataSize, Qt::Uninitialized); + memcpy(data.data(), packet, PREPARE_WRITE_SIZE); + memcpy(&(data.data()[PREPARE_WRITE_SIZE]), &(newValue.constData()[offset]), + requiredPayload); + + Request request; + request.payload = data; + request.command = ATT_OP_PREPARE_WRITE_REQUEST; + request.reference = (charHandle | ((offset + requiredPayload) << 16)); + request.reference2 = newValue; + openRequests.enqueue(request); +} + +/*! + Sends an "Execute Write Request" for a long characteristic write. + This cannot be used for executes in relation to long descriptor writes or + reliable write requests. + + A cancellation removes all pending prepare write request on the GATT server. + Otherwise this function sends an execute request for all pending prepare + write requests. + */ +void QLowEnergyControllerPrivate::sendExecuteWriteRequest( + const QLowEnergyHandle charHandle, const QByteArray &newValue, + bool isCancelation) +{ + quint8 packet[EXECUTE_WRITE_SIZE]; + packet[0] = ATT_OP_EXECUTE_WRITE_REQUEST; + if (isCancelation) + packet[1] = 0x00; // cancel pending write prepare requests + else + packet[1] = 0x01; // execute pending write prepare requests + + QByteArray data(EXECUTE_WRITE_SIZE, Qt::Uninitialized); + memcpy(data.data(), packet, EXECUTE_WRITE_SIZE); + + qCDebug(QT_BT_BLUEZ) << "Sending Execute Write Request for long characteristic value" + << hex << charHandle; + + Request request; + request.payload = data; + request.command = ATT_OP_EXECUTE_WRITE_REQUEST; + request.reference = (charHandle | ((isCancelation ? 0x00 : 0x01) << 16)); + request.reference2 = newValue; + openRequests.prepend(request); +} + + +/*! + Writes long (prepare write request), short (write request) + and writeWithoutResponse characteristic values. + + TODO Reliable/prepare write across multiple characteristics is not supported + */ void QLowEnergyControllerPrivate::writeCharacteristic( const QSharedPointer<QLowEnergyServicePrivate> service, const QLowEnergyHandle charHandle, @@ -1132,14 +1265,22 @@ void QLowEnergyControllerPrivate::writeCharacteristic( return; const QLowEnergyHandle valueHandle = service->characteristicList[charHandle].valueHandle; - // sizeof(command) + sizeof(handle) + sizeof(newValue) - const int size = 1 + 2 + newValue.size(); + const int size = WRITE_REQUEST_SIZE + newValue.size(); quint8 packet[WRITE_REQUEST_SIZE]; - if (writeWithResponse) - packet[0] = ATT_OP_WRITE_REQUEST; - else + if (writeWithResponse) { + if (newValue.size() > (mtuSize - WRITE_REQUEST_SIZE)) { + sendNextPrepareWriteRequest(charHandle, newValue, 0); + sendNextPendingRequest(); + return; + } else { + // write value fits into single package + packet[0] = ATT_OP_WRITE_REQUEST; + } + } else { + // write without response packet[0] = ATT_OP_WRITE_COMMAND; + } bt_put_unaligned(htobs(valueHandle), (quint16 *) &packet[1]); @@ -1148,7 +1289,7 @@ void QLowEnergyControllerPrivate::writeCharacteristic( memcpy(&(data.data()[WRITE_REQUEST_SIZE]), newValue.constData(), newValue.size()); qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << hex << charHandle - << "(size:" << size << "response:" << writeWithResponse << ")"; + << "(size:" << size << "with response:" << writeWithResponse << ")"; // Advantage of write without response is the quick turnaround. // It can be send at any time and does not produce responses. @@ -1174,6 +1315,7 @@ void QLowEnergyControllerPrivate::writeDescriptor( const QLowEnergyHandle descriptorHandle, const QByteArray &newValue) { + //TODO Support for long descriptor writes missing Q_ASSERT(!service.isNull()); // sizeof(command) + sizeof(handle) + sizeof(newValue) diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index d9f75625..fe3726e5 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -143,7 +143,11 @@ private: QLowEnergyHandle startingHandle); void processUnsolicitedReply(const QByteArray &msg); void exchangeMTU(); - + void sendExecuteWriteRequest(const QLowEnergyHandle charHandle, + const QByteArray &newValue, + bool isCancelation); + void sendNextPrepareWriteRequest(const QLowEnergyHandle charHandle, + const QByteArray &newValue, quint16 offset); private slots: void l2cpConnected(); diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp index 84223c91..b5e9dd31 100644 --- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp +++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp @@ -67,9 +67,8 @@ private slots: void tst_concurrentDiscovery(); void tst_defaultBehavior(); void tst_writeCharacteristic(); + void tst_writeCharacteristicNoResponse(); void tst_writeDescriptor(); - void tst_writeDescriptorNoResponse(); - private: void verifyServiceProperties(const QLowEnergyService *info); @@ -113,6 +112,7 @@ void tst_QLowEnergyController::initTestCase() return; } + devAgent = new QBluetoothDeviceDiscoveryAgent(this); QSignalSpy finishedSpy(devAgent, SIGNAL(finished())); @@ -1847,7 +1847,7 @@ void tst_QLowEnergyController::tst_writeDescriptor() Tests write without responses. We utilize the Over-The-Air image update service of the SensorTag. */ -void tst_QLowEnergyController::tst_writeDescriptorNoResponse() +void tst_QLowEnergyController::tst_writeCharacteristicNoResponse() { QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); if (localAdapters.isEmpty() || remoteDevice.isNull()) @@ -1932,8 +1932,6 @@ void tst_QLowEnergyController::tst_writeDescriptorNoResponse() } // 4. Trigger image identity announcement (using traditional write) - - QByteArray imageAValue, imageBValue; QList<QVariant> entry; bool foundOneImage = false; |