How To Add Custom Profiles In Bluetooth – Part 2
Audio : Listen to This Blog.
Overview
This article is a continuation of the previous article, and gives a brief on adding a custom Bluetooth profile for sharing the temperature and humidity data over Bluetooth. This article also gives a brief description of the Bluetooth GATT profiles in Bluetooth.
GATT Profiles in Bluetooth
GATT is an acronym for the Generic Attribute Profile, it is a specification regarding an aspect of Bluetooth-based wireless communication between devices and defines the way two Bluetooth Low Energy devices transfer data back and forth using concepts called Services and Characteristics. The GATT profile describes a use case, roles and general behaviours based on the GATT functionality. Services are collections of characteristics and relationships to other services that encapsulate the behaviour of part of a device. This also includes hierarchy of services, characteristics and attributes used in the attribute server.
GATT uses ATT as the transport mechanism for discovering attributes of the remote devices and ATT provides services to the Generic Attribute Profile. An attribute is something that represents data. ATT protocol is designed to push or pull that data to or from a remote device. ATT protocol also supports setting notifications and indications so that the remote devices can be alerted when that data changes. The figure below shows GATT protocol stack.
GATT profile as with any other profile follows the Client Server model. The GATT client corresponds to the ATT client, and sends requests to a server and receives responses from it. The GATT client does not know anything in advance about the server’s attributes, so it must first inquire about the presence and nature of those attributes by performing service discovery. After completing service discovery, it can then start reading and writing attributes found in the server. The GATT server corresponds to the ATT server, and it receives requests from a client and sends responses back. The GATT server is responsible for storing and making the user data available to the client, organized in attributes. Every BLE device must include at least a basic GATT server that can respond to client requests, even if only to return an error response. This article explains how to implement a custom profile in the server side.
GATT follows a strict hierarchy to organize attributes in a manner to allow access and retrieval of information between client and server to follow a concise set of rules that together constitute the framework used by all GATT-based profiles. The following figure shows the data hierarchy introduced by GATT.
Adding Custom Profile
Custom Bluetooth profile includes custom services and characteristics and can also include standard services and characteristics. In the following sections there is brief description on defining the service, characteristics, descriptors and UUIDs.
In our case, we read temperature/humidity data from the DTH11 sensor device using the raspberry pi device (as show in the figure below). The sensor data read is transmitted over Bluetooth to the other system for processing with another device.
We create a GATT based profile implementation in our use case, which consists of roles and which services each role implements. A service is comprised of one or more characteristics, and follows client server design pattern. We define a GATT based profile which will contain a description of the use case, the roles required to support the use case, the one or more services exposed which enable the use case as well as the definition of the server required to implement the use case.
Defining Use Case – Temperature/Humidity sensor data
We need to define what will be enabled and what won’t.
- The DTH11 sensor device will get the temperature and humidity sensor data, which will be processed by another device (client system). The format of the temperature data returned is a signed 8 bit integer.
Defining Services
A Service is a collection of one or more characteristics intended to work together to perform a function. When describing the characteristics within a service, the way in which those characteristics are utilized is defined. This includes defining properties as well as descriptors for each characteristic. The property metadata defines how the characteristic can be accessed and includes such capabilities as readable, writeable, broadcast and if authentication is required. The descriptors define metadata such as description and presentation information. A service may contain both mandatory and optional characteristics.
The service definition for reading the room temperature and humidity is very simple. It only needs to use the two characteristic defined (Temperature and Humidity) above as mandatory and add the metadata describing how that characteristic can be utilized in the service. The service description is given below. The function gatt_service_add is used for adding the GATT based service.
static gboolean register_temp_sensor_service(struct btd_adapter *adapter) { bt_uuid_t uuid; bt_uuid16_create(&uuid, TEMP_SENSOR_STATE_SVC_UUID); return gatt_service_add(adapter, GATT_PRIM_SVC_UUID, &uuid, /* temperature sensor state characteristic */ GATT_OPT_CHR_UUID16, TEMP_SENSOR_STATE_UUID, GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ | GATT_CHR_PROP_NOTIFY, GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, temp_sensor_state_read, adapter, GATT_OPT_INVALID); }
Defining Characteristics
After defining the use case, we will define the characteristics, which are required for enabling this use case. A characteristic is a piece of data, a value, with metadata to describe the data of GATT based profile. Each GATT based profile exposes at least one service that exposes at least one characteristic; some will have many more. To define a characteristic for each service, we need to define:
- Data Value: The data value describes the type and the length of the data transferred. Supported data types include unsigned byte, signed byte, word, character string, and array.
- Property: The property describes how the data value is accessed. Available choices are Broadcast, Read, Write, WriteWithoutResponse, Notify, Indicate, SignedWrite, and WritableAuxiliaries.
- Permissions: Permissions describe the access permissions for the data. Permission settings are provided for Encryption, Authentication, and Authorization.
- UUID: The UUID value (128-bit) uniquely identifies the characteristic
In our case, the Data Value, Property, Permissions are passed as part of the gatt_service_add function call. The UUID is defined locally to identify the characteristic (for our case we are using 32-bit value to define the UUID)
#define TEMP_SENSOR_STATE_SVC_UUID 0xB002 #define TEMP_SENSOR_STATE_UUID 0xB003 #define MANUFACTURER_SVC_UUID 0xB005 #define TEMPERATURE_UUID 0xB006
Once the GATT based profile is defined, it can be compiled into the structure required by the device implementing the profile. A Characteristic is a fundamental building block used in the creation of GATT based profiles.
Within the Value section itself, one or more fields are defined. Each field consists of a name which must be unique within that characteristic as well as the type of the data contained in the field and optional exponent definition.
Since a suitable temperature and humidity characteristic has already been defined, these characteristics are included in the service.
Defining Profiles
A Profile is a collection of one or more services intended to work together to enable a use case. The profile may contain both mandatory and optional services. However, a characteristic which is not part of a service may not be included in a profile.
In both the “Obtaining the temperature in a room” and “Obtaining the humidity of the room” examples, the use case is fully defined by a single Service.
The code implementation for the server side role for this profile is as below using BlueZ package.
/* * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann <[email protected]> * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "lib/bluetooth.h" #include "lib/sdp.h" #include "lib/uuid.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/shared/util.h" #include "src/log.h" #include "attrib/gattrib.h" #include "attrib/gatt-service.h" #include "attrib/att.h" #include "attrib/gatt.h" #include "attrib/att-database.h" #include "src/attrib-server.h" /* Not defined as UUID128 */ #define OPCODES_SUPPORTED_UUID 0xB001 #define TEMP_SENSOR_STATE_SVC_UUID 0xB002 #define TEMP_SENSOR_STATE_UUID 0xB003 #define MANUFACTURER_SVC_UUID 0xB005 #define TEMPERATURE_UUID 0xB006 #define BLUETOOTH_SIG_UUID 0xB00B #define MANUFACTURER_NAME_UUID 0xB00C #define MANUFACTURER_SERIAL_UUID 0xB00D #define VENDOR_SPECIFIC_SVC_UUID 0xB00E #define VENDOR_SPECIFIC_TYPE_UUID 0xB00F struct temp_sensor_adapter { struct btd_adapter *adapter; GSList *sdp_handles; }; static GSList *adapters = NULL; static void temp_sensor_adapter_free(struct temp_sensor_adapter *gadapter) { while (gadapter->sdp_handles != NULL) { uint32_t handle = GPOINTER_TO_UINT(gadapter->sdp_handles->data); attrib_free_sdp(gadapter->adapter, handle); gadapter->sdp_handles = g_slist_remove(gadapter->sdp_handles, gadapter->sdp_handles->data); } if (gadapter->adapter != NULL) btd_adapter_unref(gadapter->adapter); g_free(gadapter); } static int adapter_cmp(gconstpointer a, gconstpointer b) { const struct temp_sensor_adapter *gatt_adapter = a; const struct btd_adapter *adapter = b; if (gatt_adapter->adapter == adapter) return 0; return -1; } static uint8_t temp_sensor_state_read(struct attribute *a, struct btd_device *device, gpointer user_data) { struct btd_adapter *adapter = user_data; uint8_t value; value = 0x04; attrib_db_update(adapter, a->handle, NULL, &value, sizeof(value), NULL); return 0; } static gboolean register_temp_sensor_service(struct btd_adapter *adapter) { bt_uuid_t uuid; bt_uuid16_create(&uuid, TEMP_SENSOR_STATE_SVC_UUID); return gatt_service_add(adapter, GATT_PRIM_SVC_UUID, &uuid, /* TEMP_SENSOR state characteristic */ GATT_OPT_CHR_UUID16, TEMP_SENSOR_STATE_UUID, GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ | GATT_CHR_PROP_NOTIFY, GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, temp_sensor_state_read, adapter, GATT_OPT_INVALID); } static void register_manuf1_service(struct temp_sensor_adapter *adapter, uint16_t range[2]) { const char *manufacturer_name1 = "DTH11 Temperature Sensor"; const char *serial1 = "11111-111-B"; uint16_t start_handle, h; const int svc_size = 5; uint8_t atval[256]; bt_uuid_t uuid; int len; bt_uuid16_create(&uuid, MANUFACTURER_SVC_UUID); start_handle = attrib_db_find_avail(adapter->adapter, &uuid, svc_size); if (start_handle == 0) { error("Not enough free handles to register service"); return; } DBG("start_handle=0x%04x", start_handle); h = start_handle; /* Secondary Service: Manufacturer Service */ bt_uuid16_create(&uuid, GATT_SND_SVC_UUID); put_le16(MANUFACTURER_SVC_UUID, &atval[0]); attrib_db_add(adapter->adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); /* Manufacturer name characteristic definition */ bt_uuid16_create(&uuid, GATT_CHARAC_UUID); atval[0] = GATT_CHR_PROP_READ; put_le16(h + 1, &atval[1]); put_le16(MANUFACTURER_NAME_UUID, &atval[3]); attrib_db_add(adapter->adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); /* Manufacturer name characteristic value */ bt_uuid16_create(&uuid, MANUFACTURER_NAME_UUID); len = strlen(manufacturer_name1); strncpy((char *) atval, manufacturer_name1, len); attrib_db_add(adapter->adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); /* Manufacturer serial number characteristic */ bt_uuid16_create(&uuid, GATT_CHARAC_UUID); atval[0] = GATT_CHR_PROP_READ; put_le16(h + 1, &atval[1]); put_le16(MANUFACTURER_SERIAL_UUID, &atval[3]); attrib_db_add(adapter->adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); /* Manufacturer serial number characteristic value */ bt_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID); len = strlen(serial1); strncpy((char *) atval, serial1, len); attrib_db_add(adapter->adapter, h, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len); g_assert(h - start_handle + 1 == svc_size); range[0] = start_handle; range[1] = start_handle + svc_size - 1; } static void register_vendor_service(struct temp_sensor_adapter *adapter, uint16_t range[2]) { uint16_t start_handle, h; const int svc_size = 3; uint8_t atval[256]; bt_uuid_t uuid; bt_uuid16_create(&uuid, VENDOR_SPECIFIC_SVC_UUID); start_handle = attrib_db_find_avail(adapter->adapter, &uuid, svc_size); if (start_handle == 0) { error("Not enough free handles to register service"); return; } DBG("start_handle=0x%04x", start_handle); h = start_handle; /* Secondary Service: Vendor Specific Service */ bt_uuid16_create(&uuid, GATT_SND_SVC_UUID); put_le16(VENDOR_SPECIFIC_SVC_UUID, &atval[0]); attrib_db_add(adapter->adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); /* Vendor Specific Type characteristic definition */ bt_uuid16_create(&uuid, GATT_CHARAC_UUID); atval[0] = GATT_CHR_PROP_READ; put_le16(h + 1, &atval[1]); put_le16(VENDOR_SPECIFIC_TYPE_UUID, &atval[3]); attrib_db_add(adapter->adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); /* Vendor Specific Type characteristic value */ bt_uuid16_create(&uuid, VENDOR_SPECIFIC_TYPE_UUID); atval[0] = 0x56; atval[1] = 0x65; atval[2] = 0x6E; atval[3] = 0x64; atval[4] = 0x6F; atval[5] = 0x72; attrib_db_add(adapter->adapter, h, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6); g_assert(h - start_handle + 1 == svc_size); range[0] = start_handle; range[1] = start_handle + svc_size - 1; } static int temp_sensor_adapter_probe(struct btd_adapter *adapter) { uint16_t manuf1_range[2] = {0, 0}, manuf2_range[2] = {0, 0}; uint16_t vendor_range[2] = {0, 0}; struct temp_sensor_adapter *gadapter; gadapter = g_new0(struct temp_sensor_adapter, 1); gadapter->adapter = btd_adapter_ref(adapter); if (!register_temp_sensor_service(adapter)) { DBG("Temperature sensor service could not be registered"); temp_sensor_adapter_free(gadapter); return -EIO; } register_manuf1_service(gadapter, manuf1_range); register_vendor_service(gadapter, vendor_range); adapters = g_slist_append(adapters, gadapter); return 0; } static void temp_sensor_adapter_remove(struct btd_adapter *adapter) { struct temp_sensor_adapter *gadapter; GSList *l; l = g_slist_find_custom(adapters, adapter, adapter_cmp); if (l == NULL) return; gadapter = l->data; adapters = g_slist_remove(adapters, gadapter); temp_sensor_adapter_free(gadapter); } static struct btd_adapter_driver temp_sensor_adapter_driver = { .name = "temperature-sensor-adapter-driver", .probe = temp_sensor_adapter_probe, .remove = temp_sensor_adapter_remove, }; static int temp_sensor_init(void) { return btd_register_adapter_driver(&temp_sensor_adapter_driver); } static void temp_sensor_exit(void) { btd_unregister_adapter_driver(&temp_sensor_adapter_driver); } BLUETOOTH_PLUGIN_DEFINE(temp_sensor, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, temp_sensor_init, temp_sensor_exit)
References:
1 Comment
Hi Sudheer,
Thanks for this useful blog. It has all the information that is needed for adding bluetooth profiles.
Can you please shared the bluez version which you are using ?
Thanks,
Kasiviswanathan.V