How to create a Custom Profile - Client

Document created by Iliana Berenice Tejeda Hernandez Employee on Sep 28, 2015Last modified by Iliana Berenice Tejeda Hernandez Employee on Jan 11, 2016
Version 6Show Document
  • View in full screen mode

I was investigating about how to create a current profile and I found interesting information I would like to share with the community. So, I decided to create an example to accomplish this task using BLE stack included in the MKW40Z Connectivity Software package.

 

The demo to create is an Humidity Collector which make use of the Humidity custom profile and is based on the Temperature Collector demonstration application.

The first thing to know is that the Generic Attribute Profile (GATT) establishes in detail how to exchange all profile and user data over a BLE connection. GATT deals only with actual data transfer procedures and formats. All standard BLE profiles are based on GATT and must comply with it to operate correctly. This makes GATT a key section of the BLE specification, because every single item of data relevant to applications and users must be formatted, packed, and sent according to the rules.

                                    

GATT defines two roles: Server and Client.

  • The GATT server stores the data transported over the Attribute Protocol (ATT) and accepts Attribute Protocol requests, commands and confirmations from the GATT client.
  • The GATT client accesses data on the remote GATT server via read, write, notify, or indicate operations. Notify and indicate operations are enabled by the client but initiated by the server, providing a way to push data to the client. Notifications are unacknowledged, while indications are acknowledged. Notifications are therefore faster, but less reliable.


client-server.png

Figure 1. GATT Client-Server

 

     GATT Database establishes a hierarchy to organize attributes. These are the Profile, Service, Characteristic and Descriptor. Profiles are high level definitions that define how services can be used to enable an application and Services are collections of characteristics. Descriptors defined attributes that describe a characteristic value.

To define a GATT Database several macros are provided by the GATT_DB API in the Freescale BLE Stack, which is part KW40Z Connectivity Software package.

gatt_database.PNG

Figure 2. GATT database

 

     To know if the Profile or service is already defined in the specification, you have to look for in Bluetooth SIG profiles and check in the ble_sig_defines.h file if this is already declared in the code. In our case, the service is not declared, but the characteristic of the humidity is declared in the specification. Then, we need to check if the characteristic is already included in ble_sig_defines.h. Since, the characteristic is not included, we need to define it as shown next:


/*! Humidity Charactristic UUID */
#define gBleSig_Humidity_d                      0x2A6F

 

     The Humidity Collector is going to have the GATT client; this is the device that will receive all information from  the GATT server.

Demo provided in this post works like the Temperature Collector. When the Collector enables the notifications from the sensor, received notifications will be printed in the serial terminal.

 

In order to create the demo we need to define or develop a service that has to be the same as in the GATT Server, this is declared in the gatt_uuid128.h.If the new service is not the same, they will never be able to communicate each other. All macros function or structures in BLE stack of KW40Z Connectivity Software have a common template. Hence, we need to define this service in the gatt_uuid128.h as shown next:

 

/* Humidity */ 
UUID128(uuid_service_humidity, 0xfe ,0x34 ,0x9b ,0x5f ,0x80 ,0x00 ,0x00 ,0x80 ,0x00 ,0x10 ,0x00 ,0x02 ,0x00 ,0xfa ,0x10 ,0x10)

 

     During the scanning process is when the client is going to connect with the Server. Hence, function CheckScanEvent can help us to ensure that peer device or server device support the specified service, in this case, it will be the humidity service we just created in the previous step. Then, CheckScanEvent needs to check which device is on advertising mode and with MatchDataInAdvElementList to verify if it is the same uuid_service_humidity, if the service is not in the list, client is not going to connect.

CheckScanEvent function should look as shown next:

 

static bool_t CheckScanEvent(gapScannedDevice_t* pData)
{
 uint8_t index = 0;
 uint8_t name[10];
 uint8_t nameLength;
 bool_t foundMatch = FALSE;

 while (index < pData->dataLength)
 {
        gapAdStructure_t adElement;
        
        adElement.length = pData->data[index];
        adElement.adType = (gapAdType_t)pData->data[index + 1];
        adElement.aData = &pData->data[index + 2];

         /* Search for Humidity Custom Service */
        if ((adElement.adType == gAdIncomplete128bitServiceList_c) ||
          (adElement.adType == gAdComplete128bitServiceList_c))
        {
            foundMatch = MatchDataInAdvElementList(&adElement, &uuid_service_humidity, 16);
        }
        
        if ((adElement.adType == gAdShortenedLocalName_c) ||
          (adElement.adType == gAdCompleteLocalName_c))
        {
            nameLength = MIN(adElement.length, 10);
            FLib_MemCpy(name, adElement.aData, nameLength);
        }
        
        /* Move on to the next AD elemnt type */
        index += adElement.length + sizeof(uint8_t);
 }

 if (foundMatch)
 {
        /* UI */
        shell_write("\r\nFound device: \r\n");
        shell_writeN((char*)name, nameLength-1);
        SHELL_NEWLINE();
        shell_writeHex(pData->aAddress, 6);
 }
 return foundMatch;
}

 

 

The humidity_interface.h file should define the client structure and the server structure. For this demo, we only need the client structure, however, both are defined for reference.

 

The Client Structure has all the data of the Humidity Service, in this case is a Service, characteristic, descriptor and CCCD handle and the format of the value.

/*! Humidity Client - Configuration */
typedef struct humcConfig_tag
{
 uint16_t    hService;
 uint16_t    hHumidity;
 uint16_t    hHumCccd; 
 uint16_t    hHumDesc; 
 gattDbCharPresFormat_t  humFormat;
} humcConfig_t;

 

The next configuration structure is for the Server; in this case we don’t need it.

/*! Humidity Service - Configuration */
typedef struct humsConfig_tag
{
 uint16_t    serviceHandle;
 int16_t     initialHumidity;        
} humsConfig_t;

 

    Now that the Client Structure is declared, go to the app.c and modify some functions. There are functions that help to store all the data of the humidity service. In our case they are 3 functions for the service, characteristic and descriptor. You have to be sure that the service that you create and the characteristics of humidity are in the functions. The Handle of each data is stored in the structure of the client. The three functions that need to be modify are the next:

 

BleApp_StoreServiceHandles() stores handles for the specified service and characteristic.

static void BleApp_StoreServiceHandles
(
    gattService_t   *pService
)
{
    uint8_t i;
      
    if ((pService->uuidType == gBleUuidType128_c) &&
        FLib_MemCmp(pService->uuid.uuid128, uuid_service_humidity, 16))
    {
        /* Found Humidity Service */
        mPeerInformation.customInfo.humClientConfig.hService = pService->startHandle;
        
        for (i = 0; i < pService->cNumCharacteristics; i++)
        {
            if ((pService->aCharacteristics[i].value.uuidType == gBleUuidType16_c) &&
                (pService->aCharacteristics[i].value.uuid.uuid16 == gBleSig_Humidity_d))
            {
                /* Found Humidity Char */
                mPeerInformation.customInfo.humClientConfig.hHumidity = pService->aCharacteristics[i].value.handle;
            }
        }
    }
}

 

BleApp_StoreCharHandles() handles the descriptors.

static void BleApp_StoreCharHandles
(
    gattCharacteristic_t   *pChar
)
{
    uint8_t i;
    
    if ((pChar->value.uuidType == gBleUuidType16_c) &&
        (pChar->value.uuid.uuid16 == gBleSig_Humidity_d))
    {    
        for (i = 0; i < pChar->cNumDescriptors; i++)
        {
            if (pChar->aDescriptors[i].uuidType == gBleUuidType16_c)
            {
                switch (pChar->aDescriptors[i].uuid.uuid16)
                {
                    case gBleSig_CharPresFormatDescriptor_d:
                    {
                        mPeerInformation.customInfo.humClientConfig.hHumDesc = pChar->aDescriptors[i].handle;
                        break;
                    }
                    case gBleSig_CCCD_d:
                    {
                        mPeerInformation.customInfo.humClientConfig.hHumCccd = pChar->aDescriptors[i].handle;
                        break;
                    }
                    default:
                        break;
                }
            }
        }
    }
}

 

BleApp_StoreDescValues() stores the format of the value.

static void BleApp_StoreDescValues
(
    gattAttribute_t     *pDesc
)
{
    if (pDesc->handle == mPeerInformation.customInfo.humClientConfig.hHumDesc)
    {
        /* Store Humidity format*/
        FLib_MemCpy(&mPeerInformation.customInfo.humClientConfig.humFormat,
                    pDesc->paValue,
                    pDesc->valueLength);
    }
  
}

 

     After we store all the data of the Humidity Service, we need to check the notification callback. Every time the Client receives a notification with the BleApp_GattNotificationCallback(),  call the BleApp_PrintHumidity() function and check the Format Value; in this case is 0x27AD  that mean percentage and also have to be the same on the GATT server.

static void BleApp_GattNotificationCallback
(
    deviceId_t serverDeviceId, 
    uint16_t characteristicValueHandle,
    uint8_t* aValue,
    uint16_t valueLength
)
{
/*Compare if the characteristics handle Server is the same
of the GATT Server*/ 
    if (characteristicValueHandle == mPeerInformation.customInfo.humClientConfig.hHumidity)
    {
           BleApp_PrintTemperature(*(uint16_t*)aValue);
    }  
}


BleApp_PrintHumidity() print the value of the Humidity, but first check if the format value is the same.

static void BleApp_PrintHumidity
(
    uint16_t humidity
)
{
    shell_write("Humidity: ");
    shell_writeDec(humidity);
     /*If the format value is the same, print the value*/
    if (mPeerInformation.customInfo.humClientConfig.humFormat.unitUuid16 == 0x27AD)
    {
        shell_write(" %\r\n");
    }
    else
    {
        shell_write("\r\n");
    }
}

 

Step to include the file to the demo.

1. Create a clone of the Temperature_Collector with the name of Humidity_Collector

2. Unzip the Humidity_Collector.zip file attached to this post.

3. Save the humidity folder in the fallowing path: <kw40zConnSoft_install_dir>\ConnSw\bluetooth\profiles .

4. Replaces the common folder in the next path: <kw40zConnSoft_install_dir>\ConnSw\examples\bluetooth\humidity_sensor\common .

 

Once you already save the folders in the corresponding path you must to indicate in the demo where they are and drag the file in the humidity folder to the workspace.

 

For test the demo fallow the next steps:

  1. Compile the project and run.
  2. Press SW1 for the advertising/scanning mode, and
    wait to connect it.
  3. Once the connection finish, press the SW1 of the
    Humidity Sensor board to get and print the data.

 

Enjoy the demo!


NOTE:

This demo works with the Humidity Sensor demo. This means that you need one board programmed with the Humidity Sensor application and a second board with the Humidity Collector explained in this post.

Figure 3. Example of the Humidity Collector using the Humidity Sensor.

1 person found this helpful

Attachments

Outcomes