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