Reading and writing characteristics
All the APIs described in the following sections have an enhanced counterpart of the form GattClient_Enhanced[procedure]. A bearer id parameter was added to specify on which bearer the transaction should take place. A value of 0 for the bearer id identifies the Unenhanced ATT bearer. Values higher than 0 are used to identify the Enhanced ATT bearer used for the ATT procedure.
Characteristic value read procedure
The main API for reading a Characteristic Value is shown here:
bleResult_t GattClient_ReadCharacteristicValue
(
deviceId_t deviceId,
gattCharacteristic_t * pIoCharacteristic,
uint16_t maxReadBytes
);
This procedure assumes that the application knows the Characteristic Value Handle, usually from a previous Characteristic Discovery procedure. Therefore, the value.handle field of the structure pointed by pIoCharacteristic must be completed.
Also, the application must allocate a large enough array of bytes where the received value (from the ATT packet exchange) is written. The maxReadBytes parameter is set to the size of this allocated array.
The GATT Client module takes care of long characteristics, whose values have a greater length than can fit in a single ATT packet, by issuing repeated ATT Read Blob Requests when needed.
The following examples assume that the application knows the Characteristic Value Handle and that the value length is variable, but limited to 50 bytes.
gattCharacteristic_t myCharacteristic;
myCharacteristic. value . handle = 0x10AB;
#define mcMaxValueLength_c 50
static uint8_t aValue[mcMaxValueLength_c];
myCharacteristic. value . paValue = aValue;
bleResult_t result = GattClient_ReadCharacteristicValue
(
deviceId,
&myCharacteristic,
mcMaxValueLength_c
);
if (gBleSuccess_c != result)
{
/* Handle error */
}
Regardless of the value length, the Client Procedure Callback is triggered when the reading is complete. The received value length is also filled in the value structure.
void gattClientProcedureCallback
(
deviceId_t deviceId,
gattProcedureType_t procedureType,
gattProcedureResult_t procedureResult,
bleResult_t error
)
{
switch (procedureType)
{
/* ... */
case gGattProcReadCharacteristicValue_c:
if (*gGattProcSuccess_c == procedureResult)
{
/* Read value length */
PRINT(myCharacteristic. value . valueLength );
/* Read data */
for ( uint16_t j = 0; j < myCharacteristic. value . valueLength ; j++)
{
PRINT(myCharacteristic. value . paValue [j]);
}
}
else
{
/* Handle error */
PRINT(error);
}
break;
/* ... */
}
}
Parent topic:Reading and writing characteristics
Characteristic read by UUID procedure
This API for this procedure is shown here:
bleResult_t GattClient_ReadUsingCharacteristicUuid
(
deviceId_t deviceId,
bleUuidType_t uuidType,
const bleUuid_t* pUuid,
const gattHandleRange_t* pHandleRange,
uint8_t* aOutBuffer,
uint16_t maxReadBytes,
uint16_t* pOutActualReadBytes
);
This provides support for an important optimization, which involves reading a Characteristic Value without performing any Service or Characteristic Discovery.
For example, the following is the process to write an application that connects to any Server and wants to read the device name.
The device name is contained in the Device Name Characteristic from the GAP Service. Therefore, the necessary steps involve discovering all primary services, identifying the GAP Service by its UUID, discovering all Characteristics of the GAP Service and identifying the Device Name Characteristic (alternatively, discovering Characteristic by UUID inside GAP Service), and, finally, reading the device name by using the Characteristic Read Procedure.
Instead, the Characteristic Read by UUID Procedure allows reading a Characteristic with a specified UUID, assuming one exists on the Server, without knowing the Characteristic Value Handle.
The described example is implemented as follows:
#define mcMaxValueLength_c
/* First byte is for handle-value pair length. Next 2 bytes are the handle */
static uint8_t aValue[1 + 2 + mcMaxValueLength_c];
static uint16_t deviceNameLength;
bleUuid_t uuid = {
.uuid16 = gBleSig_GapDeviceName_d
};
bleResult_t result = GattClient_ReadUsingCharacteristicUuid
(
deviceId,
gBleUuidType16_c,
&uuid,
&pHandleRange,
aValue,
1 + 2 + mcMaxValueLength_c,
deviceNameLength
);
if (gBleSuccess_c != result)
{
/* Handle error */
}
The Client Procedure Callback is triggered when the reading is complete. Because only one air packet is exchanged during this procedure, it can only be used as a quick reading of Characteristic Values with length no greater than ATT_MTU – 1.
void gattClientProcedureCallback
(
deviceId_t deviceId,
gattProcedureType_t procedureType,
gattProcedureResult_t procedureResult,
bleResult_t error
)
{
switch (procedureType)
{
/* ... */
case gGattProcReadUsingCharacteristicUuid_c:
if (gGattProcSuccess_c == procedureResult)
{
/* Read handle-value pair length */
PRINT(aValue[0]);
deviceNameLength -= 1;
/* Read characteristic value handle */
PRINT(aValue[1] | (aValue[2] << 8));
deviceNameLength -= 2;
/* Read value length */
PRINT(deviceNameLength);
/* Read data */
for ( uint8_t j = 0; j < deviceNameLength; j++)
{
PRINT(aValue[3 + j]);
}
}
else
{
/* Handle error */
PRINT(error);
}
break;
/* ... */
}
}
Parent topic:Reading and writing characteristics
Characteristic read multiple procedure
The API for this procedure is shown here:
bleResult_t GattClient_ReadMultipleCharacteristicValues
(
deviceId_t deviceId,
uint8_t cNumCharacteristics,
gattCharacteristic_t * aIoCharacteristics
);
This procedure also allows an optimization for a specific situation, which occurs when multiple Characteristics, whose values are of known, fixed-length, can be all read in one single ATT transaction (usually one single over-the-air packet).
The application must know the value handle and value length of each Characteristic. It must also write the value.handleand value.maxValueLength with the aforementioned values, respectively, and then link the value.paValue field with an allocated array of size maxValueLength.
The following example involves reading three characteristics in a single packet.
#define mcNumCharacteristics_c 3
#define mcChar1Length_c 4
#define mcChar2Length_c 5
#define mcChar3Length_c 6
static uint8_t aValue1[mcChar1Length_c];
static uint8_t aValue2[mcChar2Length_c];
static uint8_t aValue3[mcChar3Length_c];
static gattCharacteristic_t myChars[mcNumCharacteristics_c];
myChars[0]. value . handle = 0x0015;
myChars[1]. value . handle = 0x0025;
myChars[2]. value . handle = 0x0035;
myChars[0]. value . maxValueLength = mcChar1Length_c;
myChars[1]. value . maxValueLength = mcChar2Length_c;
myChars[2]. value . maxValueLength = mcChar3Length_c;
myChars[0]. value . paValue = aValue1;
myChars[1]. value . paValue = aValue2;
myChars[2]. value . paValue = aValue3;
bleResult_t result = GattClient_ReadMultipleCharacteristicValues
(
deviceId,
mcNumCharacteristics_c,
myChars
);
if (gBleSuccess_c != result)
{
/* Handle error */
}
When the Client Procedure Callback is triggered, if no error occurs, each Characteristic’s value length should be equal to the requested lengths.
void gattClientProcedureCallback
(
deviceId_t deviceId,
gattProcedureType_t p rocedureType,
gattProcedureResult_t procedureResult,
bleResult_t error
)
{
switch (procedureType)
{
/* ... */
case gGattProcReadMultipleCharacteristicValues_c:
if (gGattProcSuccess_c == procedureResult)
{
for ( uint8_t i = 0; i < mcNumCharacteristics_c; i++)
{
/* Read value length */
PRINT(myChars[i]. value . valueLength );
/* Read data */
for ( uint8_t j = 0; j < myChars[i]. value . valueLength ; j++)
{
PRINT(myChars[i]. value . paValue [j]);
}
}
}
else
{
/* Handle error */
PRINT(error);
}
break;
/* ... */
}
}
If the server does not know the length of the characteristic values, then the Read Multiple Variable Characteristic Values procedure can be used. This sub-procedure is used to read multiple characteristic values of variable length from a server when the client knows the characteristic value handles. The response returns the characteristic values and their corresponding lengths in the Length Value Tuple List parameter.
bleResult_t GattClient_ReadMultipleVariableCharacteristicValues
(
deviceId_t deviceId,
uint8_t cNumCharacteristics,
gattCharacteristic_t* pIoCharacteristics
);
The following example involves reading three characteristics of variable length in a single packet.
#define mcNumCharacteristics_c 3
#define mcCharLengthMax_c 10
static uint8_t aValue1[mcCharLengthMax _c];
static uint8_t aValue2[mcCharLengthMax _c];
static uint8_t aValue3[mcCharLengthMax _c];
static gattCharacteristic_t myChars[mcNumCharacteristics_c];
myChars[0].value .handle = 0x0015;
myChars[1].value .handle = 0x0025;
myChars[2].value .handle = 0x0035;
myChars[0].value .paValue = aValue1;
myChars[1].value .paValue = aValue2;
myChars[2].value .paValue = aValue3;
bleResult_t result = GattClient_ReadMultipleVariableCharacteristicValues
(
deviceId,
mcNumCharacteristics_c,
pIoCharacteristics
);
if (gBleSuccess_c != result)
{
/* Handle error */
}
The result of this procedure is sent to the application via the GATT procedure callback. The response includes the characteristic value together with a handle, length pair corresponding to each characteristic.
static void BleApp_GattClientCallback
(
deviceId_t serverDeviceId,
gattProcedureType_t procedureType,
gattProcedureResult_t procedureResult,
bleResult_t error
)
{
switch (procedureResult)
{
/* ... */
case gGattProcReadMultipleVarLengthCharValues_c:
if (gGattProcSuccess_c == procedureResult)
{
for (uint8_t i = 0; i < mcNumCharacteristics_c; i++)
{
/* Print characteristic handle and length */
PRINT(myChars[i].value.handle);
PRINT(myChars[i].value.valueLength);
for (uint8_t j = 0; j < myChars[i].value.maxValueLength; j++)
{
/* Print characteristic value */
PRINT(myChars[i].value.paValue[j]);
}
}
}
else
{
/* Handle error */
}
break;
}
Parent topic:Reading and writing characteristics
Characteristic write procedure
There is a general API that may be used for writing Characteristic Values:
bleResult_t GattClient_WriteCharacteristicValue
(
deviceId_t deviceId,
const gattCharacteristic_t * pCharacteristic,
uint16_t valueLength,
const uint8_t * aValue,
bool_t withoutResponse,
bool_t signedWrite,
bool_t doReliableLongCharWrites,
const uint8_t * aCsrk
);
It has many parameters to support different combinations of Characteristic Write Procedures.
The structure pointed to by the pCharacteristic is only used for the value.handlefield which indicates the Characteristic Value Handle. The value to be written is contained in the aValue array of size valueLength.
The withoutResponse parameter can be set to TRUE if the application wishes to perform a Write Without Response Procedure, which translates into an ATT Write Command. If this value is selected, the signedWrite parameter indicates whether data should be signed (Signed Write Procedure over ATT Signed Write Command), in which case the aCsrk parameters must not be NULL and contains the CSRK to sign the data with. Otherwise, both signedWrite and aCsrk are ignored.
Finally, doReliableLongCharWrites should be sent to TRUE if the application is writing a long Characteristic Value (one that requires multiple air packets due to ATT_MTU limitations) and wants the Server to confirm each part of the attribute that is sent over the air.
To simplify the application code, the following macros are defined:
#define GattClient_SimpleCharacteristicWrite(deviceId, pChar, valueLength, aValue) \
GattClient_WriteCharacteristicValue\
(deviceId, pChar, valueLength, aValue, FALSE, FALSE, FALSE, NULL)
This is the simplest usage for writing a Characteristic. It sends an ATT Write Request if the value length does not exceed the maximum space for an over-the-air packet (ATT_MTU – 3). Otherwise, it sends ATT Prepare Write Requests with parts of the attribute, without checking the ATT Prepare Write Response data for consistency, and in the end an ATT Execute Write Request.
#define GattClient_CharacteristicWriteWithoutResponse(deviceId, pChar, valueLength, aValue) \
GattClient_WriteCharacteristicValue\
(deviceId, pChar, valueLength, aValue, TRUE, FALSE, FALSE, NULL)
This usage sends an ATT Write Command. Long Characteristic values are not allowed here and trigger a gBleInvalidParameter_c error.
#define GattClient_CharacteristicSignedWrite(deviceId, pChar, valueLength, aValue, aCsrk) \
GattClient_WriteCharacteristicValue\
(deviceId, pChar, valueLength, aValue, TRUE, TRUE, FALSE, aCsrk)
This usage sends an ATT Signed Write Command. The CSRK used to sign data must be provided.
This is a short example to write a 3-byte long Characteristic Value.
gattCharacteristic_t myChar;
myChar. value . handle = 0x00A0; /* Or maybe it was previously discovered? */
#define mcValueLength_c 3
uint8_t aValue[mcValueLength_c] = { 0x01, 0x02, 0x03 };
bleResult_t result = GattClient_SimpleCharacteristicWrite
(
deviceId,
&myChar,
mcValueLength_c,
aValue
);
if (gBleSuccess_c != result)
{
/* Handle error */
}
The Client Procedure Callback is triggered when writing is complete.
void gattClientProcedureCallback
(
deviceId_t deviceId,
gattProcedureType_t procedureType,
gattProcedureResult_t procedureResult,
bleResult_t error
)
{
switch (procedureType)
{
/* ... */
case gGattProcWriteCharacteristicValue_c:
if (gGattProcSuccess_c == procedureResult)
{
/* Continue */
}
else
{
/* Handle error */
PRINT(error);
}
break;
/* ... */
}
}
Parent topic:Reading and writing characteristics
Parent topic:Client APIs