summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@digia.com>2014-09-05 10:49:44 +0200
committerAlex Blasche <alexander.blasche@digia.com>2014-09-08 14:40:25 +0200
commit225dd8128133236c73b86e6a933f45ac4cf92867 (patch)
tree63c2cbadaf705ab83bc9e929e24e407cc68e6242
parent5ec215a6cf1046846c0f5f0dd948502fdff45ff4 (diff)
Support writing of long characteristic values
In this context long means the characteristic value is longer than the maximal ATT message size. In such cases the Bluetooth spec demands a combination of PrepareWrite commands finished by an ExecuteWrite. This is very similar to reliable writes which use the same combination of prepare/execute but permit the atomic writing of multiple characteristics at the same time. Reliable writes are not (yet) supported by the public API. Also, this works for characteristics only. Long descriptor writes will follow. Change-Id: Id139ad1848c92ca5ebf1bd1dfe8e3da297312d4f Reviewed-by: Fabian Bumberger <fbumberger@rim.com>
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp2
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp154
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h6
-rw-r--r--tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp8
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;