Bluetooth Low Energy (Bluetooth LE, BLE), also known as Bluetooth Smart is a wireless personal area network technology aimed at applications in the healthcare, fitness, beacons, security, and home entertainment industries. Compared to Classic Bluetooth, Bluetooth LE is intended to provide considerably reduced power consumption and cost while maintaining a similar communication range.

This article describes how to use Bluetooth Framework with Bluetooth LE GATT Profiles. To learn how to use Bluetooth Framework with Bluetooth LE Beacons refer to this article.

Table Of Contents

Generic Attributes

GATT profile The Generic Attributes (GATT) define a hierarchical data structure that is exposed to connected Bluetooth Low Energy (LE) devices.

The GATT profiles describe a use case, roles and general behaviors based on the GATT functionality. Services are collections of characteristics and relationships to other services that encapsulate the behavior of part of a device. This also includes hierarchy of services, characteristics and attributes used in the attribute server.

On top of the GATT hierarchy is a profile, which is composed of one or more services necessary to fulfill a use case. A service is composed of characteristics or references to other services. A characteristic consists of a type (represented by a UUID), a value, a set of properties indicating the operations the characteristic supports and a set of permissions relating to security. It may also include one or more descriptors—metadata or configuration flags relating to the owning characteristic.

GATT defines client (BLE Central) and server (BLE Peripheral) roles. The GATT server stores the data transported over the air to the GATT client and accepts requests, commands and confirmations from the GATT client. The GATT server sends responses to requests and sends indications and notifications asynchronously to the GATT client when specified events occur on the GATT server. GATT also specifies the format of data contained on the GATT server.

Current version of Bluetooth Framework supports GATT Client (Central) role with BlueSoleil, Microsoft Bluetooth drivers and with BLED112 USB Bluetooth dongle. BlueSoleil Bluetooth drivers and BLED112 Bluetooth dongle allow to use Bluetooth LE GATT features on any Windows platform starting from Windows XP. Microsoft Bluetooth drivers supports BLE GATT features starting from Windows 8.

Universally Unique ID

A Universally Unique ID (UUID) is an abbreviation you will see a lot in the BLE world. It is a unique number used to identify services, characteristics and descriptors, also known as attributes. These IDs are transmitted over the air so that e.g. a peripheral can inform a central what services it provides. To save transmitting air time and memory space there are two kinds of UUIDs:

  • The first type is a short 16-bit UUID. The predefined Heart rate service, e.g., has the UUID 0x180D and one of its enclosed characteristics, the Heart Rate Measurement characteristic, has the UUID 0x2A37. The 16-bit UUID is energy and memory efficient, but since it only provides a relatively limited number of unique IDs there is a rule; a device can only transmit the predefined Bluetooth SIG UUIDs directly over the air. Hence there is a need for a second type of UUID so vendors can transmit their own custom UUIDs as well.
  • The second type is a long 128-bit UUID, sometimes referred to as a vendor specific UUID. This is the type of UUID vendors need to use when they are making their own custom services and characteristics.

Bluetooth Framework supports both types of the UUIDs through wclGattUuid structure.

Bluetooth LE Device Address

To communicate with a Bluetooth LE device your application must know the Bluetooth Device Address, the 48-bit (6-byte) number that uniquely identifies a device among peers. There are two types of device addresses: Public and Random device address.

Public Device Address is the standard, IEEE-assigned 48-bit universal LAN MAC address which must be obtained from the IEEE Registration Authority. It is divided into two fields: IEEE-assigned company ID held in the 24 most-significant bits; Company-assigned device ID held in the 24 least significant bits.

Static Random Device Address is a 48-bit randomly generated address. A new value is generated after each power cycle. If the static address of a device is changed, then the address stored in peer devices will not be valid and the ability to reconnect using the old address will be lost.

Private Random Device Address is used when a device wants to remain private. These are addresses that can be periodically changed so that the device can not be tracked. These may be resolvable or not. Resolvable Private Addresse is an address that can be resolved through a pre-shared hash key: Only the trusted entities that have your pre-shared key can identify you. For all other entities, the address seems to be randomly changing and untrackable. These addresses are generated by a mathematical algorithm using the Identity Resolving Key (IRK) - this is one of the keys exchanged during pairing. Non-Resolvable Private Addresse is an address that is random and can not be "expected". A possible use case: a device that already communicated a non-resolvable address to a peer for a reconnection.

Bluetooth Framework supports any type of the Bluetooth LE Device Addresses. However there is no way to resolve the Resolvable Private Addresse to real Device MAC at the moment.

Bluetooth Radio Object

To be able to communicate with a Bluetooth LE device an application must get currently available Bluetooth Radio object. The Bluetooth Radio object represents a Bluetooth hardware connected to your computer. It can be built-in or external (USB, Serial) Bluetooth adapter that works with BlueSoleil, Microsoft Bluetooth drivers or with Silicon Labs BLED112 USB Bluetooth dongle (Bluetooth Framework does not support Bluetooth LE communication with Toshiba Bluetooth drivers).

To get the active Bluetooth Radio object an application should use the wclBluetoothManager class. First it must be opened by calling the Open method. It is good idea to place the code into your application initialization.


                            wclBluetoothManager: TwclBluetoothManager;
                            
                            procedure TfmMain.FormCreate(Sender: TObject);
                            begin
                                  wclBluetoothManager.Open;
                            end;
                          

                            TwclBluetoothManager *wclBluetoothManager;
                            
                            void __fastcall TfmMain::FormCreate(TObject *Sender)
                            {
                                  wclBluetoothManager->Open();
                            }
                          

                            private wclBluetoothManager Manager;
                            
                            private void fmMain_Load(object sender, EventArgs e)
                            {
                                  Manager = new wclBluetoothManager();
                                  Manager.Open();
                            }
                          

                            Private WithEvents Manager As wclBluetoothManager
                            
                            Private Sub main_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
                                  Manager = New wclBluetoothManager()
                                  Manager.Open()
                            End Sub
                          

                            CwclBluetoothManager wclBluetoothManager;
                            
                            BOOL CGattClientDlg::OnInitDialog()
                            {
                                  CDialog::OnInitDialog();
                                  wclBluetoothManager.Open();
                            }
                          


Once the wclBluetoothManager opened you can try to find the working Bluetooth Radio object. NOTE: Bluetooth Framework always detects Microsoft Bluetooth Radio even there is other drivers installed. Your application must not destroy the Bluetooth Radio Object returned by the wclBluetoothManager class.


                            function TfmMain.GetRadio: TwclBluetoothRadio;
                            var
                                  Res: Integer;
                                  Radio: TwclBluetoothRadio;
                            begin
                                  Res := wclBluetoothManager.GetLeRadio(Radio);
                                  if Res <> WCL_E_SUCCESS then begin
                                        MessageDlg('Get working radio failed: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                                        Result := nil;
                                  end else
                                        Result := Radio;
                            end;
                          

                            TwclBluetoothRadio* __fastcall TfmMain::GetRadio()
                            {
                                  TwclBluetoothRadio* Radio;
                                  int Res = wclBluetoothManager->GetLeRadio(Radio);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        MessageDlg("Get working radio failed: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                                        return NULL;
                                  }
                                  return Radio;
                            }
                          

                            private wclBluetoothRadio GetRadio()
                            {
                                  wclBluetoothRadio Radio;
                                  Int32 Res = Manager.GetLeRadio(out Radio);
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                  {
                                        MessageBox.Show("Get working radio failed: 0x" + Res.ToString("X8"));
                                        return null;
                                  }
                                  return Radio;
                            }
                          

                            Private Function GetRadio() As wclBluetoothRadio
                                  Dim Radio As wclBluetoothRadio = Nothing
                                  Dim Res As Int32 = Manager.GetLeRadio(Radio)
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Get working radio failed: 0x" + Res.ToString("X8"))
                                        Return Nothing
                                  End If
                                  Return Radio
                            End Function
                          

                            CwclBluetoothRadio* CGattClientDlg::GetRadio()
                            {
                                  CwclBluetoothRadio* Radio;
                                  int Res = wclBluetoothManager.GetLeRadio(Radio);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        AfxMessageBox(_T("Get working radio failed"));
                                        return NULL;
                                  }
                                  return Radio;
                            }
                          


To find more detailed information and code example about working with the wclBluetoothManager and how to get the working Bluetooth Radio Object refer to the GattDemo application from the Bluetooth Framework package.

Discovering Bluetooth LE Devices

Once you get the working Bluetooth Radio Object you need to discover nearby Bluetooth LE devices. Depending on the used Bluetooth drivers in-app discovering can be different.

In-app Bluetooth LE discovering available with BlueSoleil Bluetooth drivers and with BLED112 Bluetooth dongle on any Windows platform. With Microsoft Bluetooth drivers:

  • On Windows 8 you must discover and pair with Bluetooth LE device through Windows UI. After device paired you can discover it using in-app discovering methods.
  • On Windows 10 1607 and below you also have to pair with Bluetooth LE device through Windows UI. After device paired you can discover it using in-app discovering methods.
  • On Windows 10 1703 and above you do not need to pair with your device manually. You can discover nearby Bluetooth LE device using in-app discovering.

To discover nearby Bluetooth LE device call the Discover method of the wclBluetoothRadio object.


                            procedure TfmMain.btDiscoverClick(Sender: TObject);
                            var
                                  Radio: TwclBluetoothRadio;
                                  Res: Integer;
                            begin
                                  Radio := GetRadio;
                                  if Radio <> nil then begin
                                        Res := Radio.Discover(10, dkBle);
                                        if Res <> WCL_E_SUCCESS then
                                              MessageDlg('Error starting discovering: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                                  end;
                            end;
                            
                            procedure TfmMain.wclBluetoothManagerDiscoveringStarted(Sender: TObject;const Radio: TwclBluetoothRadio);
                            begin
                                  lvDevices.Items.Clear;
                            end;
                            
                            procedure TfmMain.wclBluetoothManagerDeviceFound(Sender: TObject; const Radio: TwclBluetoothRadio; const Address: Int64);
                            var
                                  Item: TListItem;
                                  DevType: TwclBluetoothDeviceType;
                                  Res: Integer;
                            begin
                                  DevType := dtMixed;
                                  Res := Radio.GetRemoteDeviceType(Address, DevType);
                                  
                                  Item := lvDevices.Items.Add;
                                  Item.Caption := IntToHex(Address, 12);
                                  Item.SubItems.Add(''); // We can not read a device's name here.
                                  Item.Data := Radio; // To use it later.
                                  if Res <> WCL_E_SUCCESS then
                                        Item.SubItems.Add('Error: 0x' + IntToHex(Res, 8))
                                  else begin
                                        case DevType of
                                              dtClassic:
                                                    Item.SubItems.Add('Classic');
                                              dtBle:
                                                    Item.SubItems.Add('BLE');
                                              dtMixed:
                                                    Item.SubItems.Add('Mixed');
                                              else
                                                    Item.SubItems.Add('Unknown');
                                        end;
                                  end;
                            end;
                            
                            procedure TfmMain.wclBluetoothManagerDiscoveringCompleted(Sender: TObject; const Radio: TwclBluetoothRadio; const Error: Integer);
                            var
                                  i: Integer;
                                  Item: TListItem;
                                  Address: Int64;
                                  Res: Integer;
                                  DevName: string;
                            begin
                                  if lvDevices.Items.Count = 0 then
                                        MessageDlg('No BLE devices were found.', mtInformation, [mbOK], 0)
                                  else begin
                                        // Here we can update found devices names.
                                        for i := 0 to lvDevices.Items.Count - 1 do begin
                                              Item := lvDevices.Items[i];
                                              
                                              Address := StrToInt64('$' + Item.Caption);
                                              Res := Radio.GetRemoteName(Address, DevName);
                                              if Res <> WCL_E_SUCCESS then
                                                    Item.SubItems[0] := 'Error: 0x' + IntToHex(Res, 8)
                                              else
                                                    Item.SubItems[0] := DevName;
                                        end;
                                  end;
                            end;
                          

                            void __fastcall TfmMain::btDiscoverClick(TObject *Sender)
                            {
                                  TwclBluetoothRadio* Radio = GetRadio();
                                  if (Radio != NULL)
                                  {
                                        int Res = Radio->Discover(10, dkBle);
                                        if (Res != WCL_E_SUCCESS)
                                              MessageDlg("Error starting discovering: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                                  }
                            }
                            
                            void __fastcall TfmMain::wclBluetoothManagerDiscoveringStarted(TObject *Sender, const TwclBluetoothRadio *Radio)
                            {
                                  lvDevices->Items->Clear();
                            }
                            
                            void __fastcall TfmMain::wclBluetoothManagerDeviceFound(TObject *Sender, const TwclBluetoothRadio *Radio, const __int64 Address)
                            {
                                  TwclBluetoothDeviceType DevType = dtMixed;
                                  int Res = ((TwclBluetoothRadio*)Radio)->GetRemoteDeviceType(Address, DevType);
                                  
                                  TListItem* Item = lvDevices->Items->Add();
                                  Item->Caption = IntToHex(Address, 12);
                                  Item->SubItems->Add(""); // We can not read a device's name here.
                                  Item->Data = (void*)Radio; // To use it later.
                                  if (Res != WCL_E_SUCCESS)
                                        Item->SubItems->Add("Error: 0x" + IntToHex(Res, 8));
                                  else
                                  {
                                        switch (DevType)
                                        {
                                              case dtClassic:
                                                    Item->SubItems->Add("Classic");
                                                    break;
                                              case dtBle:
                                                    Item->SubItems->Add("BLE");
                                                    break;
                                              case dtMixed:
                                                    Item->SubItems->Add("Mixed");
                                                    break;
                                              default:
                                                    Item->SubItems->Add("Unknown");
                                                    break;
                                        }
                                  }
                            }
                            
                            void __fastcall TfmMain::wclBluetoothManagerDiscoveringCompleted( TObject *Sender, const TwclBluetoothRadio *Radio, const int Error)
                            {
                                  if (lvDevices->Items->Count == 0)
                                        MessageDlg("No BLE devices were found.", mtInformation, TMsgDlgButtons() << mbOK, 0);
                                  else
                                  {
                                        // Here we can update found devices names.
                                        for (int i = 0; i < lvDevices->Items->Count; i++)
                                        {
                                              TListItem* Item = lvDevices->Items->Item[i];
                                              
                                              __int64 Address = StrToInt64("$" + Item->Caption);
                                              String DevName = "";
                                              int Res = ((TwclBluetoothRadio*)Radio)->GetRemoteName(Address, DevName);
                                              if (Res != WCL_E_SUCCESS)
                                                    Item->SubItems->Strings[0] = "Error: 0x" + IntToHex(Res, 8);
                                              else
                                                    Item->SubItems->Strings[0] = DevName;
                                        }
                                  }
                            }
                          

                            private void btDiscover_Click(object sender, EventArgs e)
                            {
                                  wclBluetoothRadio Radio = GetRadio();
                                  if (Radio != null)
                                  {
                                        Int32 Res = Radio.Discover(10, wclBluetoothDiscoverKind.dkBle);
                                        if (Res != wclErrors.WCL_E_SUCCESS)
                                              MessageBox.Show("Error starting discovering: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                                  }
                            }
                            
                            void Manager_OnDiscoveringStarted(object Sender, wclBluetoothRadio Radio)
                            {
                                  lvDevices.Items.Clear();
                            }
                            
                            void Manager_OnDeviceFound(object Sender, wclBluetoothRadio Radio, long Address)
                            {
                                  wclBluetoothDeviceType DevType = wclBluetoothDeviceType.dtMixed;
                                  Int32 Res = Radio.GetRemoteDeviceType(Address, out DevType);
                                  
                                  ListViewItem Item = lvDevices.Items.Add(Address.ToString("X12"));
                                  Item.SubItems.Add(""); // We can not read a device's name here.
                                  Item.Tag = Radio; // To use it later.
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                        Item.SubItems.Add("Error: 0x" + Res.ToString("X8"));
                                  else
                                  {
                                        switch (DevType)
                                        {
                                              case wclBluetoothDeviceType.dtClassic:
                                                    Item.SubItems.Add("Classic");
                                                    break;
                                              case wclBluetoothDeviceType.dtBle:
                                                    Item.SubItems.Add("BLE");
                                                    break;
                                              case wclBluetoothDeviceType.dtMixed:
                                                    Item.SubItems.Add("Mixed");
                                                    break;
                                              default:
                                                    Item.SubItems.Add("Unknown");
                                                    break;
                                        }
                                  }
                            }
                            
                            void Manager_OnDiscoveringCompleted(object Sender, wclBluetoothRadio Radio, int Error)
                            {
                                  if (lvDevices.Items.Count == 0)
                                        MessageBox.Show("No BLE devices were found.", "Discovering for BLE devices", MessageBoxButtons.OK, MessageBoxIcon.Information);
                                  else
                                  {
                                        // Here we can update found devices names.
                                        for (Int32 i = 0; i i <  lvDevices.Items.Count; i++)
                                        {
                                              ListViewItem Item = lvDevices.Items[i];
                                              
                                              Int64 Address = Convert.ToInt64(Item.Text, 16);
                                              String DevName;
                                              Int32 Res = Radio.GetRemoteName(Address, out DevName);
                                              if (Res != wclErrors.WCL_E_SUCCESS)
                                                    Item.SubItems[1].Text = "Error: 0x" + Res.ToString("X8");
                                              else
                                                    Item.SubItems[1].Text = DevName;
                                        }
                                  }
                            }
                          

                            Private Sub btDiscover_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btDiscover.Click
                                  Dim Radio As wclBluetoothRadio = GetRadio()
                                  If Radio IsNot Nothing Then
                                        Dim Res As Int32 = Radio.Discover(10, wclBluetoothDiscoverKind.dkBle)
                                        If Res <> wclErrors.WCL_E_SUCCESS Then
                                              MessageBox.Show("Error starting discovering: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                        End If
                                  End If
                            End Sub
                            
                            Private Sub Manager_OnDiscoveringStarted(ByVal Sender As Object, ByVal Radio As wclBluetooth.wclBluetoothRadio) Handles Manager.OnDiscoveringStarted
                                  lvDevices.Items.Clear()
                            End Sub
                            
                            Private Sub Manager_OnDeviceFound(ByVal Sender As Object, ByVal Radio As wclBluetooth.wclBluetoothRadio, ByVal Address As Long) Handles Manager.OnDeviceFound
                                  Dim DevType As wclBluetoothDeviceType = wclBluetoothDeviceType.dtMixed
                                  Dim Res As Int32 = Radio.GetRemoteDeviceType(Address, DevType)
                                  
                                  Dim Item As ListViewItem = lvDevices.Items.Add(Address.ToString("X12"))
                                  Item.SubItems.Add("") ' We can not read a device's name here.
                                  Item.Tag = Radio 'To use it later.
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        Item.SubItems.Add("Error: 0x" + Res.ToString("X8"))
                                  Else
                                        Select Case DevType
                                              Case wclBluetoothDeviceType.dtClassic
                                                    Item.SubItems.Add("Classic")
                                              Case wclBluetoothDeviceType.dtBle
                                                    Item.SubItems.Add("BLE")
                                              Case wclBluetoothDeviceType.dtMixed
                                                    Item.SubItems.Add("Mixed")
                                              Case Else
                                                    Item.SubItems.Add("Unknown")
                                        End Select
                                  End If
                            End Sub
                            
                            Private Sub Manager_OnDiscoveringCompleted(ByVal Sender As Object, ByVal Radio As wclBluetooth.wclBluetoothRadio, ByVal [Error] As Integer) Handles Manager.OnDiscoveringCompleted
                                  If lvDevices.Items.Count = 0 Then
                                        MessageBox.Show("No BLE devices were found.", "Discovering for BLE devices", MessageBoxButtons.OK, MessageBoxIcon.Information)
                                  Else
                                        ' Here we can update found devices names.
                                        Dim i As Int32
                                        For i = 0 To lvDevices.Items.Count - 1
                                              Dim Item As ListViewItem = lvDevices.Items(i)
                                              
                                              Dim Address As Int64 = Convert.ToInt64(Item.Text, 16)
                                              Dim DevName As String = ""
                                              Dim Res As Int32 = Radio.GetRemoteName(Address, DevName)
                                              If Res <> wclErrors.WCL_E_SUCCESS Then
                                                    Item.SubItems(1).Text = "Error: 0x" + Res.ToString("X8")
                                              Else
                                                    Item.SubItems(1).Text = DevName
                                              End If
                                        Next i
                                  End If
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonDiscover()
                            {
                                  CwclBluetoothRadio* Radio = GetRadio();
                                  if (Radio != NULL)
                                  {
                                        int Res = Radio->Discover(10, dkBle);
                                        if (Res != WCL_E_SUCCESS)
                                              AfxMessageBox(_T("Error starting discovering: 0x") + IntToHex(Res));
                                  }
                            }
                            
                            void CGattClientDlg::wclBluetoothManagerDiscoveringStarted(void* Sender, CwclBluetoothRadio* Radio)
                            {
                                  lvDevices.DeleteAllItems();
                            }
                            
                            void CGattClientDlg::wclBluetoothManagerDeviceFound(void* Sender, CwclBluetoothRadio* Radio, __int64 Address)
                            {
                                  wclBluetoothDeviceType DevType = dtMixed;
                                  int Res = Radio->GetRemoteDeviceType(Address, DevType);
                                  
                                  int Item = lvDevices.GetItemCount();
                                  lvDevices.InsertItem(Item, IntToHex(Address));
                                  lvDevices.SetItemText(Item, 1, _T("")); // We can not read a device's name here.
                                  lvDevices.SetItemData(Item, (DWORD_PTR)Radio); // To use it later.
                                  if (Res != WCL_E_SUCCESS)
                                        lvDevices.SetItemText(Item, 2, _T("Error: 0x") + IntToHex(Res));
                                  else
                                  {
                                        switch (DevType)
                                        {
                                              case dtClassic:
                                                    lvDevices.SetItemText(Item, 2, _T("Classic"));
                                                    break;
                                              case dtBle:
                                                    lvDevices.SetItemText(Item, 2, _T("BLE"));
                                                    break;
                                              case dtMixed:
                                                    lvDevices.SetItemText(Item, 2, _T("Mixed"));
                                                    break;
                                              default:
                                                    lvDevices.SetItemText(Item, 2, _T("Unknown"));
                                                    break;
                                        }
                                  }
                            }
                            
                            void CGattClientDlg::wclBluetoothManagerDiscoveringCompleted(void* Sender, CwclBluetoothRadio* Radio, int Error)
                            {
                                  if (lvDevices.GetItemCount() == 0)
                                        AfxMessageBox(_T("No BLE devices were found."));
                                  else
                                  {
                                        // Here we can update found devices names.
                                        for (int i = 0; i < lvDevices.GetItemCount(); i++)
                                        {
                                              __int64 Address = StrToInt64(lvDevices.GetItemText(i, 0));
                                              tstring DevName;
                                              int Res = Radio->GetRemoteName(Address, DevName);
                                              if (Res != WCL_E_SUCCESS)
                                                    lvDevices.SetItemText(i, 1, _T("Error: 0x") + IntToHex(Res));
                                              else
                                                    lvDevices.SetItemText(i, 1, DevName.c_str());
                                        }
                                  }
                            }
                          


If you need to know the Bluetooth LE device name or other information you should read it only after discovering completed. The OnDiscoveringCompleted event handler is good place to do that (as it is shown in the code above).

To find more detailed information and code example about discovering nearby Bluetooth LE devices refer to the GattDemo application for the Bluetooth Framework package.

Connecting To And Disconnect From BLE Devices

After discovering Bluetooth Low Energy devices you can connect to it. To connect to a remote Bluetooth LE device you need to know its Address (which is discovered on the previous step) and the working Bluetooth Radio Object. NOTE: Connection procedure is asynchronouse. The call to the Connect method of the wclGattClient class simple starts the connection procedure. After connection is established (with or without success) the OnConnect event fires with the real connection result code. In case case if connection was established with success (the Error parameter passed into the OnConnect event handler is WCL_E_SUCCESS) the wclGattClient stays in csConnected state and you can read device's GATT services, characteristics and other attributes. If the connection was not success the wclGattClient switches back to the csDisconnected state and your application can try to connect to it once again or try to connect to other BLE device.

Starting from version 7.14.0 Bluetooth Framework allows to change Bluetooth LE connection parameters. For more details refer to this article.

To disconnect from the connected BLE device call the Disconnect method of the wclGattClient class. After disconnecting the OnDisconnect event fires. The event also fires if the device terminates the connection from its side.


                            procedure TfmMain.btConnectClick(Sender: TObject);
                            var
                                  Res: Integer;
                                  Item: TListItem;
                            begin
                                  if lvDevices.Selected = nil then
                                        MessageDlg('Select device', mtWarning, [mbOK], 0)
                                  else begin
                                        try
                                              Item := lvDevices.Selected;
                                              wclGattClient.Address := StrToInt64('$' + Item.Caption);
                                              wclGattClient.ConnectOnRead := cbConnectOnRead.Checked;
                                              Res := wclGattClient.Connect(TwclBluetoothRadio(Item.Data));
                                              if Res <> WCL_E_SUCCESS then
                                                    MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                                        except
                                              on E: Exception do
                                                    MessageDlg(E.Message, mtError, [mbOK], 0);
                                        end;
                                  end;
                            end;
                            
                            procedure TfmMain.wclGattClientConnect(Sender: TObject; const Error: Integer);
                            begin
                                  // Connection property is valid here.
                                  TraceEvent(TwclGattClient(Sender).Address, 'Connected', 'Error', '0x' + IntToHex(Error, 8));
                            end;
                            
                            procedure TfmMain.btDisconnectClick(Sender: TObject);
                            var
                                  Res: Integer;
                            begin
                                  Res := wclGattClient.Disconnect;
                                  if Res <> WCL_E_SUCCESS then
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                            end;
                            
                            procedure TfmMain.wclGattClientDisconnect(Sender: TObject; const Reason: Integer);
                            begin
                                  // Connection property is valid here.
                                  TraceEvent(TwclGattClient(Sender).Address, 'Disconnected', 'Reason', '0x' + IntToHex(Reason, 8));
                            end;
                          

                            void __fastcall TfmMain::btConnectClick(TObject *Sender)
                            {
                                  if (lvDevices->Selected == NULL)
                                        MessageDlg("Select device", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                  else
                                  {
                                        TListItem* Item = lvDevices->Selected;
                                        try
                                        {
                                              wclGattClient->Address = StrToInt64("$" + Item->Caption);
                                              wclGattClient->ConnectOnRead = cbConnectOnRead->Checked;
                                              int Res = wclGattClient->Connect((TwclBluetoothRadio*)Item->Data);
                                              if (Res != WCL_E_SUCCESS)
                                                    MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                                        }
                                        catch(const Exception& e)
                                        {
                                              MessageDlg(e.Message, mtError, TMsgDlgButtons() << mbOK, 0);
                                        }
                                  }
                            }
                            
                            void __fastcall TfmMain::wclGattClientConnect(TObject *Sender, const int Error)
                            {
                                  // Connection property is valid here.
                                  TraceEvent(((TwclGattClient*)Sender)->Address, "Connected", "Error", "0x" + IntToHex(Error, 8));
                            }
                            
                            void __fastcall TfmMain::btDisconnectClick(TObject *Sender)
                            {
                                  int Res = wclGattClient->Disconnect();
                                  if (Res != WCL_E_SUCCESS)
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                            }
                            
                            void __fastcall TfmMain::wclGattClientDisconnect(TObject *Sender, const int Reason)
                            {
                                  // Connection property is valid here.
                                  TraceEvent(((TwclGattClient*)Sender)->Address, "Disconnected", "Reason", "0x" + IntToHex(Reason, 8));
                            }
                          

                            private void btConnect_Click(object sender, EventArgs e)
                            {
                                  if (lvDevices.SelectedItems.Count == 0)
                                        MessageBox.Show("Select device", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                  else
                                  {
                                        ListViewItem Item = lvDevices.SelectedItems[0];
                                        try
                                        {
                                              Client.Address = Convert.ToInt64(Item.Text, 16);
                                              Client.ConnectOnRead = cbConnectOnRead.Checked;
                                              Int32 Res = Client.Connect((wclBluetoothRadio)Item.Tag);
                                              if (Res != wclErrors.WCL_E_SUCCESS)
                                                    MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                                        }
                                        catch (Exception ex)
                                        {
                                              MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                                        }
                                  }
                            }
                            
                            void Client_OnConnect(object Sender, int Error)
                            {
                                  // Connection property is valid here.
                                  TraceEvent(((wclGattClient)Sender).Address, "Connected", "Error", "0x" + Error.ToString("X8"));
                            }
                            
                            private void btDisconnect_Click(object sender, EventArgs e)
                            {
                                  Int32 Res = Client.Disconnect();
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                            
                            void Client_OnDisconnect(object Sender, int Reason)
                            {
                                  // Connection property is valid here.
                                  TraceEvent(((wclGattClient)Sender).Address, "Disconnected", "Reason", "0x" + Reason.ToString("X8"));
                            }
                          

                            Private Sub btConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btConnect.Click
                                  If lvDevices.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select device", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                  Else
                                        Dim Item As ListViewItem = lvDevices.SelectedItems(0)
                                        Try
                                              Client.Address = Convert.ToInt64(Item.Text, 16)
                                              Client.ConnectOnRead = cbConnectOnRead.Checked
                                              Dim Res As Int32 = Client.Connect(CType(Item.Tag, wclBluetoothRadio))
                                              If Res <> wclErrors.WCL_E_SUCCESS Then
                                                    MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                              End If
                                        Catch ex As Exception
                                              MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                        End Try
                                  End If
                            End Sub
                            
                            Private Sub Client_OnConnect(ByVal Sender As Object, ByVal [Error] As Integer) Handles Client.OnConnect
                                  ' Connection property is valid here.
                                  TraceEvent(CType(Sender, wclGattClient).Address, "Connected", "Error", "0x" + [Error].ToString("X8"))
                            End Sub
                            
                            Private Sub btDisconnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btDisconnect.Click
                                  Dim Res As Int32 = Client.Disconnect()
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                  End If
                            End Sub
                            
                            Private Sub Client_OnDisconnect(ByVal Sender As Object, ByVal Reason As Integer) Handles Client.OnDisconnect
                                  ' Connection property is valid here.
                                  TraceEvent(CType(Sender, wclGattClient).Address, "Disconnected", "Reason", "0x" + Reason.ToString("X8"))
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonConnect()
                            {
                                  POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                        AfxMessageBox(_T("Select device"));
                                  else
                                  {
                                        int Item = lvDevices.GetNextSelectedItem(Pos);
                                        try
                                        {
                                              wclGattClient.SetAddress(StrToInt64(lvDevices.GetItemText(Item, 0)));
                                              wclGattClient.SetConnectOnRead(cbConnectOnRead.GetCheck() != FALSE);
                                              int Res = wclGattClient.Connect((CwclBluetoothRadio*)lvDevices.GetItemData(Item));
                                              if (Res != WCL_E_SUCCESS)
                                                    AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                                        }
                                        catch(wclException* ex)
                                        {
                                              MessageBoxA(NULL, ex->what(), "ERROR", 0);
                                        }
                                  }
                            }
                            
                            void CGattClientDlg::wclGattClientConnect(void* Sender, int Error)
                            {
                                  // Connection property is valid here.
                                  TraceEvent(((CwclGattClient*)Sender)->GetAddress(), _T("Connected"), _T("Error"), _T("0x") + IntToHex(Error));
                            }
                            
                            void CGattClientDlg::OnBnClickedButtonDisconnect()
                            {
                                  int Res = wclGattClient.Disconnect();
                                  if (Res != WCL_E_SUCCESS)
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                            }
                            
                            void CGattClientDlg::wclGattClientDisconnect(void* Sender, int Reason)
                            {
                                  // Connection property is valid here.
                                  TraceEvent(((CwclGattClient*)Sender)->GetAddress(), _T("Disconnected"), _T("Reason"), _T("0x") + IntToHex(Reason));
                            }
                          


To find more detailed information and code example about connecting to the selected Bluetooth LE devices refer to the GattDemo application from the Bluetooth Framework package.

Reading Services

Once connection to the Bluetooth Low Energy device has been established you can read the devices services. The GATT services are collections of characteristics and relationships to other services that encapsulate the behavior of part of a device. Each service has UUID that unique identifies the services. Bluetooth SIG defines the List Of the Service Assigned Numbers. To read GATT services Bluetooth Framework provides ReadServices method of the wclGattClient class. NOTE: services reading is synchronouse procedure.


                            procedure TfmMain.btGetServicesClick(Sender: TObject);
                            var
                                  Res: Integer;
                                  Item: TListItem;
                                  i: Integer;
                                  Service: TwclGattService;
                            begin
                                  lvServices.Items.Clear;
                                  FServices := nil;
                                  
                                  Res := wclGattClient.ReadServices(OpFlag, FServices);
                                  if Res <> WCL_E_SUCCESS then begin
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  if FServices = nil then
                                        Exit;
                                  
                                  for i := 0 to Length(FServices) - 1 do begin
                                        Service := FServices[i];
                                        
                                        Item := lvServices.Items.Add;
                                        if Service.Uuid.IsShortUuid then
                                              Item.Caption := IntToHex(Service.Uuid.ShortUuid, 4)
                                        else
                                              Item.Caption := GUIDToString(Service.Uuid.LongUuid);
                                        Item.SubItems.Add(BoolToStr(Service.Uuid.IsShortUuid, True));
                                        Item.SubItems.Add(IntToHex(Service.Handle, 4));
                                  end;
                            end;
                          

                            void __fastcall TfmMain::btGetServicesClick(TObject *Sender)
                            {
                                  lvServices->Items->Clear();
                                  FServices.Length = 0;
                                  
                                  int Res = wclGattClient->ReadServices(OpFlag(), FServices);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  if (FServices.Length == 0)
                                        return;
                                  
                                  for (int i = 0;  i <  FServices.Length; i++)
                                  {
                                        TwclGattService Service = FServices[i];
                                        
                                        TListItem* Item = lvServices->Items->Add();
                                        if (Service.Uuid.IsShortUuid)
                                              Item->Caption = IntToHex(Service.Uuid.ShortUuid, 4);
                                        else
                                              Item->Caption = Sysutils::GUIDToString(Service.Uuid.LongUuid);
                                        Item->SubItems->Add(BoolToStr(Service.Uuid.IsShortUuid, true));
                                        Item->SubItems->Add(IntToHex(Service.Handle, 4));
                                  }
                            }
                          

                            private void btGetServices_Click(object sender, EventArgs e)
                            {
                                  lvServices.Items.Clear();
                                  FServices = null;
                                  
                                  Int32 Res = Client.ReadServices(OpFlag(), out FServices);
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                  {
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                                        return;
                                  }
                                  
                                  if (FServices == null)
                                        return;
                                  
                                  foreach(wclGattService Service in FServices)
                                  {
                                        String s;
                                        if (Service.Uuid.IsShortUuid)
                                              s = Service.Uuid.ShortUuid.ToString("X4");
                                        else
                                              s = Service.Uuid.LongUuid.ToString();
                                        ListViewItem Item = lvServices.Items.Add(s);
                                        Item.SubItems.Add(Service.Uuid.IsShortUuid.ToString());
                                        Item.SubItems.Add(Service.Handle.ToString("X4"));
                                  }
                            }
                          

                            Private Sub btGetServices_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btGetServices.Click
                                  lvServices.Items.Clear()
                                  FServices = Nothing
                                  
                                  Dim Res As Int32 = Client.ReadServices(OpFlag(), FServices)
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                        Return
                                  End If
                                  
                                  If FServices Is Nothing Then
                                        Return
                                  End If
                                  
                                  For Each Service As wclGattService In FServices
                                        Dim s As String
                                        If Service.Uuid.IsShortUuid Then
                                              s = Service.Uuid.ShortUuid.ToString("X4")
                                        Else
                                              s = Service.Uuid.LongUuid.ToString()
                                        End If
                                        Dim Item As ListViewItem = lvServices.Items.Add(s)
                                        Item.SubItems.Add(Service.Uuid.IsShortUuid.ToString())
                                        Item.SubItems.Add(Service.Handle.ToString("X4"))
                                  Next Service
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonGetServices()
                            {
                                  lvServices.DeleteAllItems();
                                  FServices.clear();
                                  
                                  int Res = wclGattClient.ReadServices(OpFlag(), FServices);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                                        return;
                                  }
                                  
                                  if (FServices.size() == 0)
                                        return;
                                  
                                  for (wclGattServices::iterator i = FServices.begin(); i != FServices.end(); i++)
                                  {
                                        wclGattService Service = (*i);
                                        
                                        int Item = lvServices.GetItemCount();
                                        if (Service.Uuid.IsShortUuid)
                                              lvServices.InsertItem(Item, IntToHex(Service.Uuid.ShortUuid));
                                        else
                                              lvServices.InsertItem(Item, GUIDToString(Service.Uuid.LongUuid));
                                        lvServices.SetItemText(Item, 1, BoolToStr(Service.Uuid.IsShortUuid));
                                        lvServices.SetItemText(Item, 2, IntToHex(Service.Handle));
                                  }
                            }
                          


To find more detailed information and code example about reading Bluetooth LE GATT services refer to the GattDemo application from the Bluetooth Framework package.

Reading Characteristics

Once you read services you can read Characteristics of the selected service. The Characteristic encapsulates a single data point (though it may contain an array of related data, such as X/Y/Z values from a 3-axis accelerometer, etc.). Each Characteristic described via a pre-defined 16-bit or 128-bit UUID. Here you can find the standard characteristics defined by the Bluetooth SIG.

Characteristics are the main point that you will interact with your BLE peripheral, so it is important to understand the concept. They are also used to send data back to the BLE peripheral, since you are also able to write to characteristic.

Each Characteristic in addition to the UUID has few properties that describes the Characteristic's "features". Bluetooth Framework provides all the Characteristic's information through the wclGattCharacteristic structure. Below is the descriptions of the important Characteristic's properties:

  • IsBroadcastable - True indicates that the Characteristic can be broadcast.
  • IsReadable - True indicates that the Characteristic can be read.
  • IsWritable - True indicates that the Characteristic can be written to.
  • IsWritableWithoutResponse - True indicates that the Characteristic can be written to without requiring a response.
  • IsSignedWritable - True indicates that the Characteristic can be signed writable.
  • IsNotifiable - True indicates that the Characteristic can be updated by the device through Handle Value Notifications, and the new value will be returned through the event.
  • IsIndicatable - True indicates that the Characteristic can be updated by the device through Handle Value Indications, and the new value will be returned through the event.
  • HasExtendedProperties - True indicates that the Characteristic has extended properties, which will be presented through a Characteristic Extended Properties descriptor.

                            procedure TfmMain.btGetCharacteristicsClick(Sender: TObject);
                            var
                                  Service: TwclGattService;
                                  Res: Integer;
                                  Item: TListItem;
                                  i: Integer;
                                  Character: TwclGattCharacteristic;
                            begin
                                  FCharacteristics := nil;
                                  lvCharacteristics.Items.Clear;
                                  
                                  if lvServices.Selected = nil then begin
                                        MessageDlg('Select service', mtWarning, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  Service := FServices[lvServices.Selected.Index];
                                  Res := wclGattClient.ReadCharacteristics(Service, OpFlag, FCharacteristics);
                                  if Res <> WCL_E_SUCCESS then begin
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  if FCharacteristics = nil then
                                        Exit;
                                  
                                  for i := 0 to Length(FCharacteristics) - 1 do begin
                                        Character := FCharacteristics[i];
                                        
                                        Item := lvCharacteristics.Items.Add;
                                        if Character.Uuid.IsShortUuid then
                                              Item.Caption := IntToHex(Character.Uuid.ShortUuid, 4)
                                        else
                                              Item.Caption := GUIDToString(Character.Uuid.LongUuid);
                                        Item.SubItems.Add(BoolToStr(Character.Uuid.IsShortUuid, True));
                                        Item.SubItems.Add(IntToHex(Character.ServiceHandle, 4));
                                        Item.SubItems.Add(IntToHex(Character.Handle, 4));
                                        Item.SubItems.Add(IntToHex(Character.ValueHandle, 4));
                                        Item.SubItems.Add(BoolToStr(Character.IsBroadcastable, True));
                                        Item.SubItems.Add(BoolToStr(Character.IsReadable, True));
                                        Item.SubItems.Add(BoolToStr(Character.IsWritable, True));
                                        Item.SubItems.Add(BoolToStr(Character.IsWritableWithoutResponse, True));
                                        Item.SubItems.Add(BoolToStr(Character.IsSignedWritable, True));
                                        Item.SubItems.Add(BoolToStr(Character.IsNotifiable, True));
                                        Item.SubItems.Add(BoolToStr(Character.IsIndicatable, True));
                                        Item.SubItems.Add(BoolToStr(Character.HasExtendedProperties, True));
                                  end;
                            end;
                          

                            void __fastcall TfmMain::btGetCharacteristicsClick(TObject *Sender)
                            {
                                  FCharacteristics.Length = 0;
                                  lvCharacteristics->Items->Clear();
                                  
                                  if (lvServices->Selected == NULL)
                                  {
                                        MessageDlg("Select service", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  TwclGattService Service = FServices[lvServices->Selected->Index];
                                  int Res = wclGattClient->ReadCharacteristics(Service, OpFlag(), FCharacteristics);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  if (FCharacteristics.Length == 0)
                                        return;
                                  
                                  for (int i = 0;  i <  FCharacteristics.Length; i++)
                                  {
                                        TwclGattCharacteristic Character = FCharacteristics[i];
                                        
                                        TListItem* Item = lvCharacteristics->Items->Add();
                                        if (Character.Uuid.IsShortUuid)
                                              Item->Caption = IntToHex(Character.Uuid.ShortUuid, 4);
                                        else
                                              Item->Caption = Sysutils::GUIDToString(Character.Uuid.LongUuid);
                                        Item->SubItems->Add(BoolToStr(Character.Uuid.IsShortUuid, true));
                                        Item->SubItems->Add(IntToHex(Character.ServiceHandle, 4));
                                        Item->SubItems->Add(IntToHex(Character.Handle, 4));
                                        Item->SubItems->Add(IntToHex(Character.ValueHandle, 4));
                                        Item->SubItems->Add(BoolToStr(Character.IsBroadcastable, true));
                                        Item->SubItems->Add(BoolToStr(Character.IsReadable, true));
                                        Item->SubItems->Add(BoolToStr(Character.IsWritable, true));
                                        Item->SubItems->Add(BoolToStr(Character.IsWritableWithoutResponse, true));
                                        Item->SubItems->Add(BoolToStr(Character.IsSignedWritable, true));
                                        Item->SubItems->Add(BoolToStr(Character.IsNotifiable, true));
                                        Item->SubItems->Add(BoolToStr(Character.IsIndicatable, true));
                                        Item->SubItems->Add(BoolToStr(Character.HasExtendedProperties, true));
                                  }
                            }
                          

                            private void btGetCharacteristics_Click(object sender, EventArgs e)
                            {
                                  FCharacteristics = null;
                                  lvCharacteristics.Items.Clear();
                                  
                                  if (lvServices.SelectedItems.Count == 0)
                                  {
                                        MessageBox.Show("Select service", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                        return;
                                  }
                                  
                                  wclGattService Service = FServices[lvServices.SelectedItems[0].Index];
                                  Int32 Res = Client.ReadCharacteristics(Service, OpFlag(), out FCharacteristics);
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                  {
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                                        return;
                                  }
                                  
                                  if (FCharacteristics == null)
                                        return;
                                  
                                  foreach(wclGattCharacteristic Character in FCharacteristics)
                                  {
                                        String s;
                                        if (Character.Uuid.IsShortUuid)
                                              s = Character.Uuid.ShortUuid.ToString("X4");
                                        else
                                              s = Character.Uuid.LongUuid.ToString();
                                        ListViewItem Item = lvCharacteristics.Items.Add(s);
                                        
                                        Item.SubItems.Add(Character.Uuid.IsShortUuid.ToString());
                                        Item.SubItems.Add(Character.ServiceHandle.ToString("X4"));
                                        Item.SubItems.Add(Character.Handle.ToString("X4"));
                                        Item.SubItems.Add(Character.ValueHandle.ToString("X4"));
                                        Item.SubItems.Add(Character.IsBroadcastable.ToString());
                                        Item.SubItems.Add(Character.IsReadable.ToString());
                                        Item.SubItems.Add(Character.IsWritable.ToString());
                                        Item.SubItems.Add(Character.IsWritableWithoutResponse.ToString());
                                        Item.SubItems.Add(Character.IsSignedWritable.ToString());
                                        Item.SubItems.Add(Character.IsNotifiable.ToString());
                                        Item.SubItems.Add(Character.IsIndicatable.ToString());
                                        Item.SubItems.Add(Character.HasExtendedProperties.ToString());
                                  }
                            }
                          

                            Private Sub btGetCharacteristics_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btGetCharacteristics.Click
                                  FCharacteristics = Nothing
                                  lvCharacteristics.Items.Clear()
                                  
                                  If lvServices.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select service", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                        Return
                                  End If
                                  
                                  Dim Service As wclGattService = FServices(lvServices.SelectedItems(0).Index)
                                  Dim Res As Int32 = Client.ReadCharacteristics(Service, OpFlag(), FCharacteristics)
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                        Return
                                  End If
                                  
                                  If FCharacteristics Is Nothing Then
                                        Return
                                  End If
                                  
                                  For Each Character As wclGattCharacteristic In FCharacteristics
                                        Dim s As String
                                        If Character.Uuid.IsShortUuid Then
                                              s = Character.Uuid.ShortUuid.ToString("X4")
                                        Else
                                              s = Character.Uuid.LongUuid.ToString()
                                        End If
                                        Dim Item As ListViewItem = lvCharacteristics.Items.Add(s)
                                        
                                        Item.SubItems.Add(Character.Uuid.IsShortUuid.ToString())
                                        Item.SubItems.Add(Character.ServiceHandle.ToString("X4"))
                                        Item.SubItems.Add(Character.Handle.ToString("X4"))
                                        Item.SubItems.Add(Character.ValueHandle.ToString("X4"))
                                        Item.SubItems.Add(Character.IsBroadcastable.ToString())
                                        Item.SubItems.Add(Character.IsReadable.ToString())
                                        Item.SubItems.Add(Character.IsWritable.ToString())
                                        Item.SubItems.Add(Character.IsWritableWithoutResponse.ToString())
                                        Item.SubItems.Add(Character.IsSignedWritable.ToString())
                                        Item.SubItems.Add(Character.IsNotifiable.ToString())
                                        Item.SubItems.Add(Character.IsIndicatable.ToString())
                                        Item.SubItems.Add(Character.HasExtendedProperties.ToString())
                                  Next Character
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonGetCharacteristics()
                            {
                                  FCharacteristics.clear();
                                  lvCharacteristics.DeleteAllItems();
                                  
                                  POSITION Pos = lvServices.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                  {
                                        AfxMessageBox(_T("Select service"));
                                        return;
                                  }
                                  
                                  wclGattServices::iterator it = FServices.begin();
                                  std::advance(it, lvServices.GetNextSelectedItem(Pos));
                                  wclGattService Service = (*it);
                                  
                                  int Res = wclGattClient.ReadCharacteristics(Service, OpFlag(), FCharacteristics);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                                        return;
                                  }
                                  
                                  if (FCharacteristics.size() == 0)
                                        return;
                                  
                                  for (wclGattCharacteristics::iterator i = FCharacteristics.begin(); i != FCharacteristics.end(); i++)
                                  {
                                        wclGattCharacteristic Character = (*i);
                                        
                                        int Item = lvCharacteristics.GetItemCount();
                                        if (Character.Uuid.IsShortUuid)
                                              lvCharacteristics.InsertItem(Item, IntToHex(Character.Uuid.ShortUuid));
                                        else
                                              lvCharacteristics.InsertItem(Item, GUIDToString(Character.Uuid.LongUuid));
                                        lvCharacteristics.SetItemText(Item, 1, BoolToStr(Character.Uuid.IsShortUuid));
                                        lvCharacteristics.SetItemText(Item, 2, IntToHex(Character.ServiceHandle));
                                        lvCharacteristics.SetItemText(Item, 3, IntToHex(Character.Handle));
                                        lvCharacteristics.SetItemText(Item, 4, IntToHex(Character.ValueHandle));
                                        lvCharacteristics.SetItemText(Item, 5, BoolToStr(Character.IsBroadcastable));
                                        lvCharacteristics.SetItemText(Item, 6, BoolToStr(Character.IsReadable));
                                        lvCharacteristics.SetItemText(Item, 7, BoolToStr(Character.IsWritable));
                                        lvCharacteristics.SetItemText(Item, 8, BoolToStr(Character.IsWritableWithoutResponse));
                                        lvCharacteristics.SetItemText(Item, 9, BoolToStr(Character.IsSignedWritable));
                                        lvCharacteristics.SetItemText(Item, 10, BoolToStr(Character.IsNotifiable));
                                        lvCharacteristics.SetItemText(Item, 11, BoolToStr(Character.IsIndicatable));
                                        lvCharacteristics.SetItemText(Item, 12, BoolToStr(Character.HasExtendedProperties));
                                  }
                            }
                          


To find more detailed information and code example about reading Bluetooth LE GATT characteristics refer to the GattDemo application from the Bluetooth Framework package.

Reading And Writing Characteristic Value

If the Characteristic has the IsReadable property set to True an application can read the Characteristic's value. Bluetooth Framework represents the Characteristic value as a raw bytes array. The meaning of the value depends on the characteristic's UUID. Bluetooth SIG defines the meaning for the Characteristics Assigned Numbers. The meaning of the value of the vendor defined characteristics can be different and defined by the vendor.


                            procedure TfmMain.btGetCharValueClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                                  Value: TwclGattCharacteristicValue;
                                  Str: string;
                                  i: Integer;
                            begin
                                  if lvCharacteristics.Selected = nil then begin
                                        MessageDlg('Select characteristic', mtWarning, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  Res := wclGattClient.ReadCharacteristicValue(Characteristic, OpFlag, Value);
                                  if Res <> WCL_E_SUCCESS then begin
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  if Value = nil then
                                        Exit;
                                  
                                  try
                                        if Length(Value) = 0 then
                                              MessageDlg('Value is empty', mtInformation, [mbOK], 0)
                                        else begin
                                              Str := '';
                                              
                                              for i := Low(Value) to High(Value) do
                                                    Str := Str + IntToHex(Value[i], 2);
                                              
                                              MessageDlg('Value: ' + Str, mtInformation, [mbOK], 0);
                                        end;
                                  finally
                                        Value := nil;
                                  end;
                            end;
                          

                            void __fastcall TfmMain::btGetCharValueClick(TObject *Sender)
                            {
                                  if (lvCharacteristics->Selected == NULL)
                                  {
                                        MessageDlg("Select characteristic", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  TwclGattCharacteristicValue Value;
                                  int Res = wclGattClient->ReadCharacteristicValue(Characteristic, OpFlag(), Value);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  if (Value.Length == 0)
                                  {
                                        MessageDlg("Value is empty", mtInformation, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  try
                                  {
                                        String Str = "";
                                        
                                        for (int i = 0;  i <  Value.Length; i++)
                                              Str = Str + IntToHex(Value[i], 2);
                                        
                                        MessageDlg("Value: " + Str, mtInformation, TMsgDlgButtons() << mbOK, 0);
                                  }
                                  __finally
                                  {
                                        Value.Length = 0;
                                  }
                            }
                          

                            private void btGetCharValue_Click(object sender, EventArgs e)
                            {
                                  if (lvCharacteristics.SelectedItems.Count == 0)
                                  {
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                        return;
                                  }
                                  
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  Byte[] Value;
                                  Int32 Res = Client.ReadCharacteristicValue(Characteristic, OpFlag(), out Value);
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                  {
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                                        return;
                                  }
                                  
                                  if (Value == null)
                                        return;
                                  
                                  try
                                  {
                                        if (Value.Length == 0)
                                              MessageBox.Show("Value is empty", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                                        else
                                        {
                                              String Str = "";
                                              for (Int32 i = 0;  i <  Value.Length; i++)
                                                    Str = Str + Value[i].ToString("X2");
                                              
                                              MessageBox.Show("Value: " + Str, "Characterist value", MessageBoxButtons.OK, MessageBoxIcon.Information);
                                        }
                                  }
                                  finally
                                  {
                                        Value = null;
                                  }
                            }
                          

                            Private Sub btGetCharValue_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btGetCharValue.Click
                                  If lvCharacteristics.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                        Return
                                  End If
                                  
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  Dim Value As Byte() = Nothing
                                  Dim Res As Int32 = Client.ReadCharacteristicValue(Characteristic, OpFlag(), Value)
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                        Return
                                  End If
                                  
                                  If Value Is Nothing Then
                                        Return
                                  End If
                                  
                                  Try
                                        If Value.Length = 0 Then
                                              MessageBox.Show("Value is empty", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information)
                                        Else
                                              Dim Str As String = ""
                                              For i As Int32 = 0 To Value.Length - 1
                                                    Str = Str + Value(i).ToString("X2")
                                              Next i
                                              MessageBox.Show("Value: " + Str, "Characterist value", MessageBoxButtons.OK, MessageBoxIcon.Information)
                                        End If
                                  Finally
                                        Value = Nothing
                                  End Try
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonGetCharValue()
                            {
                                  POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                  {
                                        AfxMessageBox(_T("Select characteristic"));
                                        return;
                                  }
                                  
                                  wclGattCharacteristics::iterator it = FCharacteristics.begin();
                                  std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
                                  wclGattCharacteristic Characteristic = (*it);
                                  
                                  wclGattCharacteristicValue Value;
                                  int Res = wclGattClient.ReadCharacteristicValue(Characteristic, OpFlag(), Value);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                                        return;
                                  }
                                  
                                  if (Value.size() == 0)
                                  {
                                        AfxMessageBox(_T("Value is empty"));
                                        return;
                                  }
                                  
                                  try
                                  {
                                       CString Str = _T("");
                                       
                                       for (wclGattCharacteristicValue::iterator i = Value.begin(); i != Value.end(); i++)
                                             Str = Str + IntToHex((*i));
                                       
                                       AfxMessageBox(_T("Value: ") + Str);
                                  }
                                  finally(
                                       Value.clear();
                                  )
                            }
                          


If the Characteristic has either IsWritable or IsWritableWithoutResponse or IsSignedWritable property set to True an application can Write to the Characteristic.


                            procedure TfmMain.btSetValueClick(Sender: TObject);
                            var
                                  Str: string;
                                  Characteristic: TwclGattCharacteristic;
                                  Val: TwclGattCharacteristicValue;
                                  i: Integer;
                                  j: Integer;
                                  Res: Integer;
                            begin
                                  if lvCharacteristics.Selected = nil then begin
                                        MessageDlg('Select characteristic', mtWarning, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  
                                  Str := edCharVal.Text;
                                  if Length(Str) mod 2 <> 0 then
                                        Str := '0' + Str;
                                  SetLength(Val, Length(Str) div 2);
                                  i := 1;
                                  j := 0;
                                  while i < Length(Str) do begin
                                        Val[j] := StrToInt('$' + Copy(Str, i, 2));
                                        Inc(j);
                                        Inc(i, 2);
                                  end;
                                  
                                  Res := wclGattClient.WriteCharacteristicValue(Characteristic, Val);
                                  if Res <> WCL_E_SUCCESS then
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                            end;
                          

                            void __fastcall TfmMain::btSetValueClick(TObject *Sender)
                            {
                                  if (lvCharacteristics->Selected == NULL)
                                  {
                                        MessageDlg("Select characteristic", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  
                                  String Str = edCharVal->Text;
                                  if (Str.Length() % 2 != 0)
                                        Str = "0" + Str;
                                  TwclGattCharacteristicValue Val;
                                  Val.Length = Str.Length() / 2;
                                  int i = 0;
                                  int j = 0;
                                  while (i < Str.Length())
                                  {
                                        Val[j] = StrToInt("$" + Str.SubString(i, 2));
                                        j++;
                                        i += 2;
                                  }
                                  
                                  int Res = wclGattClient->WriteCharacteristicValue(Characteristic, Val);
                                  if (Res != WCL_E_SUCCESS)
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                            }
                          

                            private void btSetValue_Click(object sender, EventArgs e)
                            {
                                  if (lvCharacteristics.SelectedItems.Count == 0)
                                  {
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                        return;
                                  }
                                  
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  
                                  String Str = edCharVal.Text;
                                  if (Str.Length % 2 != 0)
                                        Str = "0" + Str;
                                  
                                  Byte[] Val = new Byte[Str.Length / 2];
                                  for (Int32 i = 0;  i <  Val.Length; i++)
                                  {
                                        String b = Str.Substring(i * 2, 2);
                                        Val[i] = Convert.ToByte(b, 16);
                                  }
                                  
                                  Int32 Res = Client.WriteCharacteristicValue(Characteristic, Val);
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                          

                            Private Sub btSetValue_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btSetValue.Click
                                  If lvCharacteristics.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                        Return
                                  End If
                                  
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  
                                  Dim Str As String = edCharVal.Text
                                  If Str.Length Mod 2 <> 0 Then
                                        Str = "0" + Str
                                  End If
                                  
                                  Dim Val(Str.Length \ 2 - 1) As Byte
                                  For i As Int32 = 0 To Val.Length - 1
                                        Dim b As String = Str.Substring(i * 2, 2)
                                        Val(i) = Convert.ToByte(b, 16)
                                  Next i
                                  
                                  Dim Res As Int32 = Client.WriteCharacteristicValue(Characteristic, Val)
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                  End If
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonSetCharValue()
                            {
                                  POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                  {
                                        AfxMessageBox(_T("Select characteristic"));
                                        return;
                                  }
                                  
                                  wclGattCharacteristics::iterator it = FCharacteristics.begin();
                                  std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
                                  wclGattCharacteristic Characteristic = (*it);
                                  
                                  CString Str;
                                  edCharValue.GetWindowText(Str);
                                  if (Str.GetLength() % 2 != 0)
                                        Str = _T("0") + Str;
                                  
                                  wclGattCharacteristicValue Val;
                                  int i = 0;
                                  while (i < Str.GetLength())
                                  {
                                        unsigned char b = LOBYTE(LOWORD(_tcstol(Str.Mid(i, 2), NULL, 16)));
                                        Val.push_back(b);
                                        i+=2;
                                  }
                                  
                                  int Res = wclGattClient.WriteCharacteristicValue(Characteristic, Val);
                                  if (Res != WCL_E_SUCCESS)
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                            }
                          


To find more detailed information and code example about reading and writing Bluetooth LE GATT characteristics refer to the GattDemo application from the Bluetooth Framework package.

Reading Descriptors

In addition to the Properties the Characteristic may have Descriptors that are defined attributes that describe a characteristic value. The Descriptor is described by the UUID. Bluetooth SIG defines Descriptors Assigned Numbers for predefined descriptors. The vendors can use custom UUID for the vendor specified Descriptors.


                            procedure TfmMain.btGetDescriptorsClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                                  i: Integer;
                                  Item: TListItem;
                                  Descriptor: TwclGattDescriptor;
                            begin
                                  FDescriptors := nil;
                                  lvDescriptors.Items.Clear;
                                  
                                  if lvCharacteristics.Selected = nil then begin
                                        MessageDlg('Select characteristic', mtWarning, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  Res := wclGattClient.ReadDescriptors(Characteristic, OpFlag, FDescriptors);
                                  if Res <> WCL_E_SUCCESS then begin
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  if FDescriptors = nil then
                                        Exit;
                                  
                                  for i := 0 to Length(FDescriptors) - 1 do begin
                                        Descriptor := FDescriptors[i];
                                        
                                        Item := lvDescriptors.Items.Add;
                                        if Descriptor.Uuid.IsShortUuid then
                                              Item.Caption := IntToHex(Descriptor.Uuid.ShortUuid, 4)
                                        else
                                              Item.Caption := GUIDToString(Descriptor.Uuid.LongUuid);
                                        Item.SubItems.Add(BoolToStr(Descriptor.Uuid.IsShortUuid, True));
                                        Item.SubItems.Add(IntToHex(Descriptor.ServiceHandle, 4));
                                        Item.SubItems.Add(IntToHex(Descriptor.CharacteristicHandle, 4));
                                        Item.SubItems.Add(IntToHex(Descriptor.Handle, 4));
                                        case Descriptor.DescriptorType of
                                              dtCharacteristicExtendedProperties:
                                                    Item.SubItems.Add('dtCharacteristicExtendedProperties');
                                              dtCharacteristicUserDescription:
                                                    Item.SubItems.Add('dtCharacteristicUserDescription');
                                              dtClientCharacteristicConfiguration:
                                                    Item.SubItems.Add('dtClientCharacteristicConfiguration');
                                              dtServerCharacteristicConfiguration:
                                                    Item.SubItems.Add('dtServerCharacteristicConfiguration');
                                              dtCharacteristicFormat:
                                                    Item.SubItems.Add('dtCharacteristicFormat');
                                              dtCharacteristicAggregateFormat:
                                                    Item.SubItems.Add('dtCharacteristicAggregateFormat');
                                              else
                                                    Item.SubItems.Add('dtCustomDescriptor');
                                        end;
                                  end;
                            end;
                          

                            void __fastcall TfmMain::btGetDescriptorsClick(TObject *Sender)
                            {
                                  FDescriptors.Length = 0;
                                  lvDescriptors->Items->Clear();
                                  
                                  if (lvCharacteristics->Selected == NULL)
                                  {
                                        MessageDlg("Select characteristic", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  int Res = wclGattClient->ReadDescriptors(Characteristic, OpFlag(), FDescriptors);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  if (FDescriptors.Length == 0)
                                        return;
                                  
                                  for (int i = 0;  i <  FDescriptors.Length; i++)
                                  {
                                        TwclGattDescriptor Descriptor = FDescriptors[i];
                                        
                                        TListItem* Item = lvDescriptors->Items->Add();
                                        if (Descriptor.Uuid.IsShortUuid)
                                              Item->Caption = IntToHex(Descriptor.Uuid.ShortUuid, 4);
                                        else
                                              Item->Caption = Sysutils::GUIDToString(Descriptor.Uuid.LongUuid);
                                        Item->SubItems->Add(BoolToStr(Descriptor.Uuid.IsShortUuid, true));
                                        Item->SubItems->Add(IntToHex(Descriptor.ServiceHandle, 4));
                                        Item->SubItems->Add(IntToHex(Descriptor.CharacteristicHandle, 4));
                                        Item->SubItems->Add(IntToHex(Descriptor.Handle, 4));
                                        switch (Descriptor.DescriptorType)
                                        {
                                              case dtCharacteristicExtendedProperties:
                                                    Item->SubItems->Add("dtCharacteristicExtendedProperties");
                                                    break;
                                              case dtCharacteristicUserDescription:
                                                    Item->SubItems->Add("dtCharacteristicUserDescription");
                                                    break;
                                              case dtClientCharacteristicConfiguration:
                                                    Item->SubItems->Add("dtClientCharacteristicConfiguration");
                                                    break;
                                              case dtServerCharacteristicConfiguration:
                                                    Item->SubItems->Add("dtServerCharacteristicConfiguration");
                                                    break;
                                              case dtCharacteristicFormat:
                                                    Item->SubItems->Add("dtCharacteristicFormat");
                                                    break;
                                              case dtCharacteristicAggregateFormat:
                                                    Item->SubItems->Add("dtCharacteristicAggregateFormat");
                                                    break;
                                              default:
                                                    Item->SubItems->Add("dtCustomDescriptor");
                                                    break;
                                        }
                                  }
                            }
                          

                            private void btGetDesc_Click(object sender, EventArgs e)
                            {
                                  FDescriptors = null;
                                  lvDescriptors.Items.Clear();
                                  
                                  if (lvCharacteristics.SelectedItems.Count == 0)
                                  {
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                        return;
                                  }
                                  
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  Int32 Res = Client.ReadDescriptors(Characteristic, OpFlag(), out FDescriptors);
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                  {
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                                        return;
                                  }
                                  
                                  if (FDescriptors == null)
                                        return;
                                  
                                  foreach (wclGattDescriptor Descriptor in FDescriptors)
                                  {
                                        String s;
                                        if (Descriptor.Uuid.IsShortUuid)
                                              s = Descriptor.Uuid.ShortUuid.ToString("X4");
                                        else
                                              s = Descriptor.Uuid.LongUuid.ToString();
                                        ListViewItem Item = lvDescriptors.Items.Add(s);
                                        
                                        Item.SubItems.Add(Descriptor.Uuid.IsShortUuid.ToString());
                                        Item.SubItems.Add(Descriptor.ServiceHandle.ToString("X4"));
                                        Item.SubItems.Add(Descriptor.CharacteristicHandle.ToString("X4"));
                                        Item.SubItems.Add(Descriptor.Handle.ToString("X4"));
                                        switch (Descriptor.DescriptorType)
                                        {
                                              case wclGattDescriptorType.dtCharacteristicExtendedProperties:
                                                    Item.SubItems.Add("dtCharacteristicExtendedProperties");
                                                    break;
                                              case wclGattDescriptorType.dtCharacteristicUserDescription:
                                                    Item.SubItems.Add("dtCharacteristicUserDescription");
                                                    break;
                                              case wclGattDescriptorType.dtClientCharacteristicConfiguration:
                                                    Item.SubItems.Add("dtClientCharacteristicConfiguration");
                                                    break;
                                              case wclGattDescriptorType.dtServerCharacteristicConfiguration:
                                                    Item.SubItems.Add("dtServerCharacteristicConfiguration");
                                                    break;
                                              case wclGattDescriptorType.dtCharacteristicFormat:
                                                    Item.SubItems.Add("dtCharacteristicFormat");
                                                    break;
                                              case wclGattDescriptorType.dtCharacteristicAggregateFormat:
                                                    Item.SubItems.Add("dtCharacteristicAggregateFormat");
                                                    break;
                                              default:
                                                    Item.SubItems.Add("dtCustomDescriptor");
                                                    break;
                                        }
                                  }
                            }
                          

                            Private Sub btGetDesc_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btGetDesc.Click
                                  FDescriptors = Nothing
                                  lvDescriptors.Items.Clear()
                                  
                                  If lvCharacteristics.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                        Return
                                  End If
                                  
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  Dim Res As Int32 = Client.ReadDescriptors(Characteristic, OpFlag(), FDescriptors)
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                        Return
                                  End If
                                  
                                  If FDescriptors Is Nothing Then
                                        Return
                                  End If
                                  
                                  For Each Descriptor As wclGattDescriptor In FDescriptors
                                        Dim s As String
                                        If Descriptor.Uuid.IsShortUuid Then
                                              s = Descriptor.Uuid.ShortUuid.ToString("X4")
                                        Else
                                              s = Descriptor.Uuid.LongUuid.ToString()
                                        End If
                                        Dim Item As ListViewItem = lvDescriptors.Items.Add(s)
                                        
                                        Item.SubItems.Add(Descriptor.Uuid.IsShortUuid.ToString())
                                        Item.SubItems.Add(Descriptor.ServiceHandle.ToString("X4"))
                                        Item.SubItems.Add(Descriptor.CharacteristicHandle.ToString("X4"))
                                        Item.SubItems.Add(Descriptor.Handle.ToString("X4"))
                                        Select Case Descriptor.DescriptorType
                                              Case wclGattDescriptorType.dtCharacteristicExtendedProperties
                                                    Item.SubItems.Add("dtCharacteristicExtendedProperties")
                                              Case wclGattDescriptorType.dtCharacteristicUserDescription
                                                    Item.SubItems.Add("dtCharacteristicUserDescription")
                                              Case wclGattDescriptorType.dtClientCharacteristicConfiguration
                                                    Item.SubItems.Add("dtClientCharacteristicConfiguration")
                                              Case wclGattDescriptorType.dtServerCharacteristicConfiguration
                                                    Item.SubItems.Add("dtServerCharacteristicConfiguration")
                                              Case wclGattDescriptorType.dtCharacteristicFormat
                                                    Item.SubItems.Add("dtCharacteristicFormat")
                                              Case wclGattDescriptorType.dtCharacteristicAggregateFormat
                                                    Item.SubItems.Add("dtCharacteristicAggregateFormat")
                                              Case Else
                                                    Item.SubItems.Add("dtCustomDescriptor")
                                        End Select
                                  Next Descriptor
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonGetDescriptors()
                            {
                                  FDescriptors.clear();
                                  lvDescriptors.DeleteAllItems();
                                  
                                  POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                  {
                                        AfxMessageBox(_T("Select characteristic"));
                                        return;
                                  }
                                  
                                  wclGattCharacteristics::iterator it = FCharacteristics.begin();
                                  std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
                                  wclGattCharacteristic Characteristic = (*it);
                                  
                                  int Res = wclGattClient.ReadDescriptors(Characteristic, OpFlag(), FDescriptors);
                                  if (Res != WCL_E_SUCCESS)
                                  {
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                                        return;
                                  }
                                  
                                  if (FDescriptors.size() == 0)
                                        return;
                                  
                                  for (wclGattDescriptors::iterator i = FDescriptors.begin(); i != FDescriptors.end(); i++)
                                  {
                                        wclGattDescriptor Descriptor = (*i);
                                        
                                        int Item = lvDescriptors.GetItemCount();
                                        if (Descriptor.Uuid.IsShortUuid)
                                              lvDescriptors.InsertItem(Item, IntToHex(Descriptor.Uuid.ShortUuid));
                                        else
                                              lvDescriptors.InsertItem(Item, GUIDToString(Descriptor.Uuid.LongUuid));
                                        lvDescriptors.SetItemText(Item, 1, BoolToStr(Descriptor.Uuid.IsShortUuid));
                                        lvDescriptors.SetItemText(Item, 2, IntToHex(Descriptor.ServiceHandle));
                                        lvDescriptors.SetItemText(Item, 3, IntToHex(Descriptor.CharacteristicHandle));
                                        lvDescriptors.SetItemText(Item, 4, IntToHex(Descriptor.Handle));
                                        switch (Descriptor.DescriptorType)
                                        {
                                              case dtCharacteristicExtendedProperties:
                                                    lvDescriptors.SetItemText(Item, 5, _T("dtCharacteristicExtendedProperties"));
                                                    break;
                                              case dtCharacteristicUserDescription:
                                                    lvDescriptors.SetItemText(Item, 5, _T("dtCharacteristicUserDescription"));
                                                    break;
                                              case dtClientCharacteristicConfiguration:
                                                    lvDescriptors.SetItemText(Item, 5, _T("dtClientCharacteristicConfiguration"));
                                                    break;
                                              case dtServerCharacteristicConfiguration:
                                                    lvDescriptors.SetItemText(Item, 5, _T("dtServerCharacteristicConfiguration"));
                                                    break;
                                              case dtCharacteristicFormat:
                                                    lvDescriptors.SetItemText(Item, 5, _T("dtCharacteristicFormat"));
                                                    break;
                                              case dtCharacteristicAggregateFormat:
                                                    lvDescriptors.SetItemText(Item, 5, _T("dtCharacteristicAggregateFormat"));
                                                    break;
                                              default:
                                                    lvDescriptors.SetItemText(Item, 5, _T("dtCustomDescriptor"));
                                                    break;
                                        }
                                  }
                            }
                          


To find more detailed information and code example about reading Bluetooth LE GATT descriptors refer to the GattDemo application from the Bluetooth Framework package.

Characteristic Value Change Notifications

If the Characteristic has either IsNotifiable or IsIndicatable property set to True the Characteristic can send notifications to the application when the Characteristic's value changed. Bluetooth Framework supports both notification types.

What Are Notifications And Indications

Indication and notifications are commands that could be send through the attribute (ATT) protocol. So, there are two roles defined at the ATT layer:

  • Client devices access remote resources over a BLE link using the GATT protocol. It also known as Central.
  • Server devices have the GATT database, access control methods, and provide resources to the remote client. This also known as Peripheral.

BLE standard defines two ways to transfer data for the server to the client: notification and indication. Notifications and indications are initiated by the Server but enabled by the Client.

Notifications do not need acknowledged, so they are faster. Hence, server does not know if the message reach to the client.
Indications need acknowledged to communicated. The client sent a confirmation message back to the server, this way server knows that message reached the client. One interesting thing defined by the ATT protocol is that a Server can't send two consecutive indications if the confirmation was not received. In other words, server has to wait for the confirmation of each indication in order to send the next indication. That is why indications are slower than notifications.

How To Subsribe To And Unsubscribe From Value Change Notifications

To subscribe for the Characteristic's changes the application must call the Subscribe method first (that should be done for each Characteristic the application is interested in). After that to start receiving notification an application must write the Client Configuration Descriptor for the selected characteristic. To make it simple Bluetooth Framework includes the WriteClientConfiguration method that does all the things to write the descriptor. Once the notification has been received the OnCharacteristicChanged even fires. The new value of the Characteristic is passed to the event handler as a Raw Bytes Array. The application can detect which Characteristic has been changed (if it is subscribed to more than one Characteristic) by Characteristic's Handle that is also passed to the event handler. The code snippets below show how to subscribe to the characterstic change notifications, handle the OnCharacteristicChanged event and unsubscribe from the characterstic change notifications.

Bluetooth Framework also provides methods for fast characteristic changes notification subscribing (SubscribeForNotifications) and unsubscribing (UnsubscribeFromNotifications). For more details about how to use this new methods refer to the GattClient demo application.

NOTE: DFRobot Bluno boards do not have Client Configuration Descriptor and common subscribing method will not work with such boards. To be able to receive notifications from such boards an application must use the ForceNotifications flag. Please refer to this article to find more details about using the ForceNotifications flag.


                            procedure TfmMain.btSubscribeClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                            begin
                                  if lvCharacteristics.Selected = nil then begin
                                        MessageDlg('Select characteristic', mtWarning, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  
                                  Res := wclGattClient.Subscribe(Characteristic);
                                  if Res <> WCL_E_SUCCESS then
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                            end;
                            
                            procedure TfmMain.btCCCDSubscribeClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                            begin
                                  if lvCharacteristics.Selected = nil then begin
                                        MessageDlg('Select characteristic', mtWarning, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  
                                  Res := wclGattClient.WriteClientConfiguration(Characteristic, True, OpFlag);
                                  if Res <> WCL_E_SUCCESS then
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                            end;
                            
                            procedure TfmMain.wclGattClientCharacteristicChanged(Sender: TObject; const Handle: Word; const Value: TwclGattCharacteristicValue);
                            var
                                  Str: string;
                                  i: Integer;
                            begin
                                  TraceEvent(TwclGattClient(Sender).Address, 'ValueChanged', 'Handle', IntToHex(Handle, 4));
                                  if Value = nil then
                                        TraceEvent(0, '', 'Value', '')
                                  else begin
                                        if Length(Value) = 0 then
                                              TraceEvent(0, '', 'Value', '')
                                        else begin
                                              Str := '';
                                              
                                              for i := Low(Value) to High(Value) do
                                                    Str := Str + IntToHex(Value[i], 2);
                                              
                                              TraceEvent(0, '', 'Value', Str);
                                        end;
                                  end;
                            end;
                            
                            procedure TfmMain.btCCCDUnsubscribeClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                            begin
                                  if lvCharacteristics.Selected = nil then begin
                                        MessageDlg('Select characteristic', mtWarning, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  
                                  Res := wclGattClient.WriteClientConfiguration(Characteristic, False, OpFlag);
                                  if Res <> WCL_E_SUCCESS then
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                            end;
                            
                            procedure TfmMain.btUnsubscribeClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                            begin
                                  if lvCharacteristics.Selected = nil then begin
                                        MessageDlg('Select characteristic', mtWarning, [mbOK], 0);
                                        Exit;
                                  end;
                                  
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  
                                  Res := wclGattClient.Unsubscribe(Characteristic);
                                  if Res <> WCL_E_SUCCESS then
                                        MessageDlg('Error: 0x' + IntToHex(Res, 8), mtError, [mbOK], 0);
                            end;
                          

                            void __fastcall TfmMain::btSubscribeClick(TObject *Sender)
                            {
                                  if (lvCharacteristics->Selected == NULL)
                                  {
                                        MessageDlg("Select characteristic", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  
                                  int Res = wclGattClient->Subscribe(Characteristic);
                                  if (Res != WCL_E_SUCCESS)
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                            }
                            
                            void __fastcall TfmMain::btCCCDSubscribeClick(TObject *Sender)
                            {
                                  if (lvCharacteristics->Selected == NULL)
                                  {
                                        MessageDlg("Select characteristic", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  
                                  int Res = wclGattClient->WriteClientConfiguration(Characteristic, true, OpFlag());
                                  if (Res != WCL_E_SUCCESS)
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                            }
                            
                            void __fastcall TfmMain::wclGattClientCharacteristicChanged(TObject *Sender, const WORD Handle, const TwclGattCharacteristicValue Value)
                            {
                                  TraceEvent(((TwclGattClient*)Sender)->Address, "ValueChanged", "Handle", IntToStr(Handle));
                                  if (Value.Length == 0)
                                        TraceEvent(0, "", "Value", "");
                                  else
                                  {
                                        String Str = "";
                                        
                                        for (int i = 0;  i <  Value.Length; i++)
                                              Str = Str + IntToHex(Value[i], 2);
                                        
                                        TraceEvent(0, "", "Value", Str);
                                  }
                            }
                            
                            void __fastcall TfmMain::btCCCDUnsubscribeClick(TObject *Sender)
                            {
                                  if (lvCharacteristics->Selected == NULL)
                                  {
                                        MessageDlg("Select characteristic", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  
                                  int Res = wclGattClient->WriteClientConfiguration(Characteristic, false, OpFlag());
                                  if (Res != WCL_E_SUCCESS)
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                            }
                            
                            void __fastcall TfmMain::btUnsubscribeClick(TObject *Sender)
                            {
                                  if (lvCharacteristics->Selected == NULL)
                                  {
                                        MessageDlg("Select characteristic", mtWarning, TMsgDlgButtons() << mbOK, 0);
                                        return;
                                  }
                                  
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  
                                  int Res = wclGattClient->Unsubscribe(Characteristic);
                                  if (Res != WCL_E_SUCCESS)
                                        MessageDlg("Error: 0x" + IntToHex(Res, 8), mtError, TMsgDlgButtons() << mbOK, 0);
                            }
                          

                            private void btSubscribe_Click(object sender, EventArgs e)
                            {
                                  if (lvCharacteristics.SelectedItems.Count == 0)
                                  {
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                        return;
                                  }
                                  
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  
                                  Int32 Res = Client.Subscribe(Characteristic);
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                            
                            private void btWriteSubsc_Click(object sender, EventArgs e)
                            {
                                  if (lvCharacteristics.SelectedItems.Count == 0)
                                  {
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                        return;
                                  }
                                  
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  
                                  Int32 Res = Client.WriteClientConfiguration(Characteristic, true, OpFlag());
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                            
                            void Client_OnCharacteristicChanged(object Sender, ushort Handle, byte[] Value)
                            {
                                  TraceEvent(((wclGattClient)Sender).Address, "ValueChanged", "Handle", Handle.ToString("X4"));
                                  if (Value == null)
                                        TraceEvent(0, "", "Value", "");
                                  else
                                  {
                                        if (Value.Length == 0)
                                              TraceEvent(0, "", "Value", "");
                                        else
                                        {
                                              String Str = "";
                                              
                                              for (Int32 i = 0;  i <  Value.Length; i++)
                                                    Str = Str + Value[i].ToString("X2");
                                              
                                              TraceEvent(0, "", "Value", Str);
                                        }
                                  }
                            }
                            
                            private void btWriteUnsubsc_Click(object sender, EventArgs e)
                            {
                                  if (lvCharacteristics.SelectedItems.Count == 0)
                                  {
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                        return;
                                  }
                                  
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  
                                  Int32 Res = Client.WriteClientConfiguration(Characteristic, false, OpFlag());
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                            
                            private void btUnsubscribe_Click(object sender, EventArgs e)
                            {
                                  if (lvCharacteristics.SelectedItems.Count == 0)
                                  {
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                        return;
                                  }
                                  
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  
                                  Int32 Res = Client.Unsubscribe(Characteristic);
                                  if (Res != wclErrors.WCL_E_SUCCESS)
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                          

                            Private Sub btSubscribe_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btSubscribe.Click
                                  If lvCharacteristics.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                        Return
                                  End If
                                  
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  
                                  Dim Res As Int32 = Client.Subscribe(Characteristic)
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                  End If
                            End Sub
                            
                            Private Sub btWriteSubsc_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btWriteSubsc.Click
                                  If lvCharacteristics.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                        Return
                                  End If
                                  
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  
                                  Dim Res As Int32 = Client.WriteClientConfiguration(Characteristic, True, OpFlag())
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                  End If
                            End Sub
                            
                            Private Sub Client_OnCharacteristicChanged(ByVal Sender As Object, ByVal Handle As UShort, ByVal Value() As Byte) Handles Client.OnCharacteristicChanged
                                  TraceEvent(CType(Sender, wclGattClient).Address, "ValueChanged", "Handle", Handle.ToString("X4"))
                                  If Value Is Nothing Then
                                        TraceEvent(0, "", "Value", "")
                                  Else
                                        If Value.Length = 0 Then
                                              TraceEvent(0, "", "Value", "")
                                        Else
                                              Dim Str As String = ""
                                              
                                              Dim i As Int32
                                              For i = 0 To Value.Length - 1
                                                    Str = Str + Value(i).ToString("X2")
                                              Next i
                                              
                                              TraceEvent(0, "", "Value", Str)
                                        End If
                                  End If
                            End Sub
                            
                            Private Sub btWriteUnsubsc_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btWriteUnsubsc.Click
                                  If lvCharacteristics.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                        Return
                                  End If
                                  
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  
                                  Dim Res As Int32 = Client.WriteClientConfiguration(Characteristic, False, OpFlag())
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                  End If
                            End Sub
                            
                            Private Sub btUnsubscribe_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btUnsubscribe.Click
                                  If lvCharacteristics.SelectedItems.Count = 0 Then
                                        MessageBox.Show("Select characteristic", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                                        Return
                                  End If
                                  
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  
                                  Dim Res As Int32 = Client.Unsubscribe(Characteristic)
                                  If Res <> wclErrors.WCL_E_SUCCESS Then
                                        MessageBox.Show("Error: 0x" + Res.ToString("X8"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                                  End If
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonSubscribe()
                            {
                                  POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                  {
                                        AfxMessageBox(_T("Select characteristic"));
                                        return;
                                  }
                                  
                                  wclGattCharacteristics::iterator it = FCharacteristics.begin();
                                  std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
                                  wclGattCharacteristic Characteristic = (*it);
                                  
                                  int Res = wclGattClient.Subscribe(Characteristic);
                                  if (Res != WCL_E_SUCCESS)
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                            }
                            
                            void CGattClientDlg::OnBnClickedButtonWriteCccdSubscribe()
                            {
                                  POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                  {
                                        AfxMessageBox(_T("Select characteristic"));
                                        return;
                                  }
                                  
                                  wclGattCharacteristics::iterator it = FCharacteristics.begin();
                                  std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
                                  wclGattCharacteristic Characteristic = (*it);
                                  
                                  int Res = wclGattClient.WriteClientConfiguration(Characteristic, true, OpFlag());
                                  if (Res != WCL_E_SUCCESS)
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                            }
                            
                            void CGattClientDlg::wclGattClientCharacteristicChanged(void* Sender, unsigned short Handle, wclGattCharacteristicValue& Value)
                            {
                                  TraceEvent(((CwclGattClient*)Sender)->GetAddress(), _T("ValueChanged"), _T("Handle"), IntToHex(Handle));
                                  if (Value.size() == 0)
                                        TraceEvent(0, _T(""), _T("Value"), _T(""));
                                  else
                                  {
                                        CString Str = _T("");
                                        for (wclGattCharacteristicValue::iterator i = Value.begin(); i != Value.end(); i++)
                                              Str = Str + IntToHex((*i));
                                        
                                        TraceEvent(0, _T(""), _T("Value"), Str);
                                  }
                            }
                            
                            void CGattClientDlg::OnBnClickedButtonWriteCccdUnsubscribe()
                            {
                                  POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                  {
                                        AfxMessageBox(_T("Select characteristic"));
                                        return;
                                  }
                                  
                                  wclGattCharacteristics::iterator it = FCharacteristics.begin();
                                  std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
                                  wclGattCharacteristic Characteristic = (*it);
                                  
                                  int Res = wclGattClient.WriteClientConfiguration(Characteristic, false, OpFlag());
                                  if (Res != WCL_E_SUCCESS)
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                            }
                            
                            void CGattClientDlg::OnBnClickedButtonUnsubscribe()
                            {
                                  POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
                                  if (Pos == NULL)
                                  {
                                        AfxMessageBox(_T("Select characteristic"));
                                        return;
                                  }
                                  
                                  wclGattCharacteristics::iterator it = FCharacteristics.begin();
                                  std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
                                  wclGattCharacteristic Characteristic = (*it);
                                  
                                  int Res = wclGattClient.Unsubscribe(Characteristic);
                                  if (Res != WCL_E_SUCCESS)
                                        AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
                            }
                          

Why Do I Get Error 0x000510B6 (WCL_E_BLUETOOTH_LE_INVALID_CHARACTERISTIC_CONFIGURATION)

For some characteristics call to the Subscribe, Unsubscribe WriteClientConfiguration methods may return WCL_E_BLUETOOTH_LE_INVALID_CHARACTERISTIC_CONFIGURATION (0x000510B6) error code. The error means that the characteristic has both IsIndicatable and IsNotifiable properties set to True. You must select one of them by setting other to False.

If you are interested in receiving Notifications then set IsIndicatable to False. If you are interested in receiving Indications then set IsNotifiable to False. Below you can find the same code as above but with characteristic properties check.


                            procedure TfmMain.btSubscribeClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                            begin
                                  ...
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if Characteristic.IsIndicatable and Characteristic.IsNotifiable then
                                        Characteristic.IsIndicatable := False;
                                  Res := wclGattClient.Subscribe(Characteristic);
                                  ...
                            end;
                            
                            procedure TfmMain.btCCCDSubscribeClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                            begin
                                  ...
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if Characteristic.IsIndicatable and Characteristic.IsNotifiable then
                                        Characteristic.IsIndicatable := False;
                                  Res := wclGattClient.WriteClientConfiguration(Characteristic, True, OpFlag);
                                  ...
                            end;
                            
                            procedure TfmMain.btCCCDUnsubscribeClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                            begin
                                  ...
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if Characteristic.IsIndicatable and Characteristic.IsNotifiable then
                                        Characteristic.IsIndicatable := False;
                                  Res := wclGattClient.WriteClientConfiguration(Characteristic, False, OpFlag);
                                  ...
                            end;
                            
                            procedure TfmMain.btUnsubscribeClick(Sender: TObject);
                            var
                                  Characteristic: TwclGattCharacteristic;
                                  Res: Integer;
                            begin
                                  ...
                                  Characteristic := FCharacteristics[lvCharacteristics.Selected.Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if Characteristic.IsIndicatable and Characteristic.IsNotifiable then
                                        Characteristic.IsIndicatable := False;
                                  Res := wclGattClient.Unsubscribe(Characteristic);
                                  ...
                            end;
                          

                            void __fastcall TfmMain::btSubscribeClick(TObject *Sender)
                            {
                                  ...
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  int Res = wclGattClient->Subscribe(Characteristic);
                                  ...
                            }
                            
                            void __fastcall TfmMain::btCCCDSubscribeClick(TObject *Sender)
                            {
                                  ...
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  int Res = wclGattClient->WriteClientConfiguration(Characteristic, true, OpFlag());
                                  ...
                            }
                            
                            void __fastcall TfmMain::btCCCDUnsubscribeClick(TObject *Sender)
                            {
                                  ...
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  int Res = wclGattClient->WriteClientConfiguration(Characteristic, false, OpFlag());
                                  ...
                            }
                            
                            void __fastcall TfmMain::btUnsubscribeClick(TObject *Sender)
                            {
                                  ...
                                  TwclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics->Selected->Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  int Res = wclGattClient->Unsubscribe(Characteristic);
                                  ...
                            }
                          

                            private void btSubscribe_Click(object sender, EventArgs e)
                            {
                                  ...
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  Int32 Res = Client.Subscribe(Characteristic);
                                  ...
                            }
                            
                            private void btWriteSubsc_Click(object sender, EventArgs e)
                            {
                                  ...
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  Int32 Res = Client.WriteClientConfiguration(Characteristic, true, OpFlag());
                                  ...
                            }
                            
                            private void btWriteUnsubsc_Click(object sender, EventArgs e)
                            {
                                  ...
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  Int32 Res = Client.WriteClientConfiguration(Characteristic, false, OpFlag());
                                  ...
                            }
                            
                            private void btUnsubscribe_Click(object sender, EventArgs e)
                            {
                                  ...
                                  wclGattCharacteristic Characteristic = FCharacteristics[lvCharacteristics.SelectedItems[0].Index];
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  Int32 Res = Client.Unsubscribe(Characteristic);
                                  ...
                            }
                          

                            Private Sub btSubscribe_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btSubscribe.Click
                                  ...
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  ' Let say we prefer notifications in case if both properties set to True.
                                  If Characteristic.IsIndicatable And Characteristic.IsNotifiable Then
                                        Characteristic.IsIndicatable = False
                                  End If
                                  Dim Res As Int32 = Client.Subscribe(Characteristic)
                                  ...
                            End Sub
                            
                            Private Sub btWriteSubsc_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btWriteSubsc.Click
                                  ...
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  ' Let say we prefer notifications in case if both properties set to True.
                                  If Characteristic.IsIndicatable And Characteristic.IsNotifiable Then
                                        Characteristic.IsIndicatable = False
                                  End If
                                  Dim Res As Int32 = Client.WriteClientConfiguration(Characteristic, True, OpFlag())
                                  ...
                            End Sub
                            
                            Private Sub btWriteUnsubsc_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btWriteUnsubsc.Click
                                  ...
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  ' Let say we prefer notifications in case if both properties set to True.
                                  If Characteristic.IsIndicatable And Characteristic.IsNotifiable Then
                                        Characteristic.IsIndicatable = False
                                  End If
                                  Dim Res As Int32 = Client.WriteClientConfiguration(Characteristic, False, OpFlag())
                                  ...
                            End Sub
                            
                            Private Sub btUnsubscribe_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btUnsubscribe.Click
                                  ...
                                  Dim Characteristic As wclGattCharacteristic = FCharacteristics(lvCharacteristics.SelectedItems(0).Index)
                                  ' Let say we prefer notifications in case if both properties set to True.
                                  If Characteristic.IsIndicatable And Characteristic.IsNotifiable Then
                                        Characteristic.IsIndicatable = False
                                  End If
                                  Dim Res As Int32 = Client.Unsubscribe(Characteristic)
                                  ...
                            End Sub
                          

                            void CGattClientDlg::OnBnClickedButtonSubscribe()
                            {
                                  ...
                                  wclGattCharacteristic Characteristic = (*it);
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  int Res = wclGattClient.Subscribe(Characteristic);
                                  ...
                            }
                            
                            void CGattClientDlg::OnBnClickedButtonWriteCccdSubscribe()
                            {
                                  ...
                                  wclGattCharacteristic Characteristic = (*it);
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  int Res = wclGattClient.WriteClientConfiguration(Characteristic, true, OpFlag());
                                  ...
                            }
                            
                            void CGattClientDlg::OnBnClickedButtonWriteCccdUnsubscribe()
                            {
                                  ...
                                  wclGattCharacteristic Characteristic = (*it);
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  int Res = wclGattClient.WriteClientConfiguration(Characteristic, false, OpFlag());
                                  ...
                            }
                            
                            void CGattClientDlg::OnBnClickedButtonUnsubscribe()
                            {
                                  ...
                                  wclGattCharacteristic Characteristic = (*it);
                                  // Let say we prefer notifications in case if both properties set to True.
                                  if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                        Characteristic.IsIndicatable = false;
                                  int Res = wclGattClient.Unsubscribe(Characteristic);
                                  ...
                            }
                          


To find more detailed information and code example about subscribing to the Characteristic change notifications refer to the GattDemo application from the Bluetooth Framework package.

Download Demo Application

The Bluetooth Framework package includes the GattClient sample application that shows how to use the wclGattClient class. To download Bluetooth Framework package visit the Bluetooth Framework page.