summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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;