This example of custom profile uses the Temperature Sensor and Temperature Collector examples as a base, so it can be easily modified. Both examples are in the SDK, so this document explains how to add the Humidity profile, and how to modify the code to get the Humidity Sensor and Collector working.
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.
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 KW38 SDK.
First, we need to use the Temperature Sensor project as a base, to create our Humidity Custom Profile Server (Sensor).
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 (${workspace_loc:/${ProjName}/bluetooth/host/interface) 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 Sensor is going to have the GATT Server, because is going to be the device that has all the information for the GATT Client.
On the Temperature Sensor demo have the Battery Service and Device Information, so you only have to change the Temperature Service to Humidity Service
In order to create the demo we need to define or develop a service that has to be the same as in the GATT Client, 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 structure in SDK have a common template which helps the application to act accordingly. 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)
All the Service and Characteristics is declared in gattdb.h. Descriptors are declared after the Characteristic Value declaration but before the next Characteristic declaration. In this case the permission is the CharPresFormatDescriptor that have specific description by the standard. The Units of the Humidity Characteristic is on Percentage that is 0x27AD.
Client Characteristic Configuration Descriptor (CCCD) is a descriptor where clients write some of the bits to activate Server notifications and/or indications.
PRIMARY_SERVICE_UUID128(service_humidity, uuid_service_humidity)
CHARACTERISTIC(char_humidity, gBleSig_Humidity_d, (gGattCharPropNotify_c))
VALUE(value_humidity, gBleSig_Humidity_d, (gPermissionNone_c), 2, 0x00, 0x25)
DESCRIPTOR(desc_humidity, gBleSig_CharPresFormatDescriptor_d, (gPermissionFlagReadable_c), 7, 0x0E, 0x00, 0xAD, 0x27, 0x00, 0x00, 0x00)
CCCD(cccd_humidity)
After that, create a folder humidity in the next path ${workspace_loc:/${ProjName}/bluetooth/profiles. Found the temperature folder, copy the temperature_service.c and paste inside of the humidity folder with another name (humidity_service.c). Then go back and look for the interface folder, copy temperature_interface.h and change the name (humidity_interface.h) in the same path.
You need to include the path of the created folder. Project properties>C/C+ Build>Settings>Tool Settings>MCU C Compiler>Includes:
The humidity_interface.h file should have the following code.
The Service structure has the service handle, and the initialization value.
/*! Humidity Service - Configuration */
typedef struct humsConfig_tag {
uint16_t serviceHandle;
int16_t initialHumidity;
} humsConfig_t;
/*! Humidity Client - Configuration */
typedef struct humcConfig_tag {
uint16_t hService;
uint16_t hHumidity;
uint16_t hHumCccd;
uint16_t hHumDesc;
gattDbCharPresFormat_t humFormat;
} humcConfig_t;
At minimum on humidity_service.c file, should have the following code.
The service stores the device identification for the connected client. This value is changed on subscription and non-subscription events.
/*! Humidity Service - Subscribed Client*/
static deviceId_t mHums_SubscribedClientId;
The initialization of the service is made by calling the start procedure. This function is usually called when the application is initialized. In this case is on the BleApp_Config().
bleResult_t Hums_Start(humsConfig_t *pServiceConfig)
{
mHums_SubscribedClientId = gInvalidDeviceId_c;
/* Set the initial value of the humidity characteristic */
return Hums_RecordHumidityMeasurement(pServiceConfig->serviceHandle,
pServiceConfig->initialHumidity);
}
On stop function, the unsubscribe function is called.
bleResult_t Hums_Stop(humsConfig_t *pServiceConfig)
{
/* Stop functionality by unsubscribing */
return Hums_Unsubscribe();
}
bleResult_t Hums_Unsubscribe(void)
{
/* Unsubscribe by invalidating the client ID */
mHums_SubscribedClientId = gInvalidDeviceId_c;
return gBleSuccess_c;
}
The subscribe function will be used in the main file, to subscribe the GATT client to the Humidity service.
bleResult_t Hums_Subscribe(deviceId_t clientDeviceId)
{
/* Subscribe by saving the client ID */
mHums_SubscribedClientId = clientDeviceId;
return gBleSuccess_c;
}
Depending on the complexity of the service, the API will implement additional functions. For the Humidity Sensor only have a one characteristic.
The measurement will be saving on the GATT database and send the notification to the client. This function will need the service handle and the new value as input parameters.
bleResult_t Hums_RecordHumidityMeasurement(uint16_t serviceHandle, int16_t humidity)
{
uint16_t handle;
bleResult_t result;
bleUuid_t uuid = Uuid16(gBleSig_Humidity_d);
/* Get handle of Humidity characteristic */
result = GattDb_FindCharValueHandleInService(serviceHandle,
gBleUuidType16_c, &uuid, &handle);
if (result != gBleSuccess_c)
return result;
/* Update characteristic value */
result = GattDb_WriteAttribute(handle, sizeof(uint16_t), (uint8_t*) &humidity);
if (result != gBleSuccess_c)
return result;
Hts_SendHumidityMeasurementNotification(handle);
return gBleSuccess_c;
}
After save the measurement on the GATT database with GattDb_WriteAttribute function we send the notification.
To send the notification, first have to get the CCCD and after check if the notification is active, if is active send the notification.
static void Hts_SendHumidityMeasurementNotification
(
uint16_t handle
)
{
uint16_t hCccd;
bool_t isNotificationActive;
/* Get handle of CCCD */
if (GattDb_FindCccdHandleForCharValueHandle(handle, &hCccd)
!= gBleSuccess_c)
return;
if (gBleSuccess_c == Gap_CheckNotificationStatus
(mHums_SubscribedClientId, hCccd, &isNotificationActive) &&
TRUE == isNotificationActive)
{
GattServer_SendNotification(mHums_SubscribedClientId, handle);
}
}
There are some modifications that have to be done, to use the new Humidity profile in our sensor example.
First, we need to declare the humidity service:
static humsConfig_t humsServiceConfig = {(uint16_t)service_humidity, 0};
Then, we need to add or modify the following functions:
You need to modify this line:
/* Device is connected, send humidity value */
BleApp_SendHumidity();
You need to start the Humidity Service, and to modify the PrintString line:
humsServiceConfig.initialHumidity = 0;
(void)Hums_Start(&humsServiceConfig);
AppPrintString("\r\nHumidity sensor -> Press switch to start advertising.\r\n");
There are some modifications required in two Connection Events.
(void)Hums_Subscribe(peerDeviceId);
gConnEvtDisconnected_c
(void)Hums_Unsubscribe();
/* Notify the humidity value when CCCD is written */
BleApp_SendHumidity()
And, we need to add this function:
static void BleApp_SendHumidity(void)
{
(void)TMR_StopTimer(appTimerId);
/* Update with initial humidity */
(void)Hums_RecordHumidityMeasurement((uint16_t)service_humidity,
(int16_t)(BOARD_GetTemperature()));
#if defined(cPWR_UsePowerDownMode) && (cPWR_UsePowerDownMode)
/* Start Sleep After Data timer */
(void)TMR_StartLowPowerTimer(appTimerId,
gTmrLowPowerSecondTimer_c,
TmrSeconds(gGoToSleepAfterDataTime_c),
DisconnectTimerCallback, NULL);
#endif
}
In this example, the Record Humidity uses the BOARD_GetTemperature, to use the example without any external sensor and to be able to see a change in the collector, but, in this section would be a GetHumidity function.
First, we need to use the Temperature Collector project as a base, to create our Humidity Custom Profile Client (Collector).
The same applies for the Client. 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 (${workspace_loc:/${ProjName}/bluetooth/host/interface) 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 structure in SDK have a common template which helps the application to act accordingly. 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)
After that, copy the humidity profile folder from the Sensor project, to the Collector project ${workspace_loc:/${ProjName}/bluetooth/profiles. And also for this project, include the path of the new folder. Project properties>C/C+ Build>Settings>Tool Settings>MCU C Compiler>Includes:
In the Collector source file, we need to do also some modifications, to use the Humidity Profile.
First, we need to modify the Custom Information of the Peer device:
humcConfig_t humsClientConfig;
static void BleApp_StoreServiceHandles
(
gattService_t *pService
)
{
uint8_t i,j;
if ((pService->uuidType == gBleUuidType128_c) &&
FLib_MemCmp(pService->uuid.uuid128, uuid_service_humidity, 16))
{
/* Found Humidity Service */
mPeerInformation.customInfo.humsClientConfig.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 Humudity Char */
mPeerInformation.customInfo.humsClientConfig.hHumidity = pService->aCharacteristics[i].value.handle;
for (j = 0; j < pService->aCharacteristics[i].cNumDescriptors; j++)
{
if (pService->aCharacteristics[i].aDescriptors[j].uuidType == gBleUuidType16_c)
{
switch (pService->aCharacteristics[i].aDescriptors[j].uuid.uuid16)
{
/* Found Humidity Char Presentation Format Descriptor */
case gBleSig_CharPresFormatDescriptor_d:
{
mPeerInformation.customInfo.humsClientConfig.hHumDesc = pService->aCharacteristics[i].aDescriptors[j].handle;
break;
}
/* Found Humidity Char CCCD */
case gBleSig_CCCD_d:
{
mPeerInformation.customInfo.humsClientConfig.hHumCccd = pService->aCharacteristics[i].aDescriptors[j].handle;
break;
}
default:
; /* No action required */
break;
}
}
}
}
}
}
}
if (pDesc->handle == mPeerInformation.customInfo.humsClientConfig.hHumDesc)
{
/* Store Humidity format*/
FLib_MemCpy(&mPeerInformation.customInfo.humsClientConfig.humFormat,
pDesc->paValue,
pDesc->valueLength);
}
/*www.bluetooth.com/specifications/assigned-numbers/units */
if (mPeerInformation.customInfo.humsClientConfig.humFormat.unitUuid16 == 0x27ADU)
{
AppPrintString(" %\r\n");
}
else
{
AppPrintString("\r\n");
}
if (characteristicValueHandle == mPeerInformation.customInfo.humsClientConfig.hHumidity)
{
BleApp_PrintHumidity(Utils_ExtractTwoByteValue(aValue));
}
foundMatch = MatchDataInAdvElementList(&adElement, &uuid_service_humidity, 16);
if (mPeerInformation.customInfo.humsClientConfig.hHumidity != gGattDbInvalidHandle_d)
if (mPeerInformation.customInfo.humsClientConfig.hHumDesc != 0U)
mpCharProcBuffer->handle = mPeerInformation.customInfo.humsClientConfig.hHumDesc;
if (mPeerInformation.customInfo.humsClientConfig.hHumCccd != 0U)
mpCharProcBuffer->handle = mPeerInformation.customInfo.humsClientConfig.hHumCccd;
Now, after connection, every time that you press the SW3 on KW38 Humidity Sensor is going to send the value to KW38 Humidity Collector.