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 connect to Bluetooth Low Energy peripheral devices using the Bluetooth Framework.

Table Of Contents

Generic Attributes

GATT profile The Generic Attributes (GATT) defines 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 the 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 or configuration flags relating to the owning characteristic.

GATT defines a client (BLE Central) and a server (BLE Peripheral) role. 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.

The Bluetooth Framework supports the GATT client role with BlueSoleil, Microsoft Bluetooth drivers, and the BLED112 USB Bluetooth dongle. BlueSoleil Bluetooth drivers and the BLED112 dongle allow the use of Bluetooth LE GATT features on any Windows platform starting from Windows XP. The Microsoft Bluetooth driver support the 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.

The 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: the public and the random device address.

  • The 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.
  • The 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.
  • The 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.
  • The Resolvable Private Address 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. A non-resolvable private address is the address that is random and cannot be "expected". A possible use case: a device that already communicated a non-resolvable address to a peer for a reconnection.

The Bluetooth Framework supports any type of Bluetooth LE device address. However, there is no way to resolve the Resolvable Private Address to a real device MAC at the moment.

Bluetooth Radio Object

To be able to communicate with a Bluetooth LE device, an application must get the currently available Bluetooth Radio object. The Bluetooth radio object represents a Bluetooth hardware device connected to your computer. It can be a built-in or external (USB, serial) Bluetooth adapter that works with BlueSoleil and Microsoft Bluetooth drivers or with the Silicon Labs BLED112 USB Bluetooth dongle ( the 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 a 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: The Bluetooth Framework always detects Microsoft Bluetooth radio even if there are 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;
                            }
                          

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 is available with BlueSoleil Bluetooth drivers and with the BLED112 dongle on any Windows platform. With the Microsoft Bluetooth driver:

  • On Windows 8 you must discover and pair with a Bluetooth LE device through Windows UI. After the device is paired, you can discover it using in-app discovering methods.
  • On Windows 10 1607 and below you also have to pair with a Bluetooth LE device through Windows UI. After the device is 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 devices using in-app discovering.

To discover nearby Bluetooth LE devices, call the Discover method of the wclBluetoothRadio class.


                            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 the discovery is completed. The OnDiscoveringCompleted event handler is a good place to do that (as it is shown in the code above).

Connecting To And Disconnect From BLE Devices

After discovering Bluetooth Low Energy devices, you can connect to them. 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: The connection procedure is asynchronous. The call to the Connect method of the wclGattClient class simply starts the connection procedure. After the connection is established (with or without success), the OnConnect event is called with the real connection result code. In case the connection was established with success (the Error parameter passed to the OnConnect event handler is WCL_E_SUCCESS) the wclGattClient stays in csConnected state, and you can read the device's GATT services, characteristics, and other attributes. If the connection was not successful, the wclGattClient switches back to the csDisconnected state, and your application can try to connect to it once again or try to connect to another BLE device. You can also change the Bluetooth LE connection parameters.

To disconnect from the connected BLE device, call the Disconnect method of the wclGattClient class. After disconnecting, the OnDisconnect event is called. The event is also called 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));
                            }
                          

Reading Services

Once a connection to the Bluetooth Low Energy device has been established, you can read the device's 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 a UUID that uniquely identifies the services. To read GATT services, the Bluetooth Framework provides the ReadServices method of the wclGattClient class. NOTE: services reading is a synchronous 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));
                                  }
                            }
                          

Reading Characteristics

Once you read the services, you can read the 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 is described via a pre-defined 16-bit or 128-bit UUID.

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 characteristics.

Each characteristic, in addition to the UUID, has a few properties that describe the characteristic's "features". The Bluetooth Framework provides all the characteristic's information through the wclGattCharacteristic structure. Below are 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));
                                  }
                            }
                          

Reading And Writing Characteristic Value

If a characteristic has the IsReadable property set to True, an application can read the characteristic's value. The 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 a characteristic has either the 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));
                            }
                          

Reading Descriptors

In addition to the properties, a 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 a 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;
                                        }
                                  }
                            }
                          

Characteristic Value Change Notifications

If a characteristic has either the IsNotifiable or IsIndicatable property set to True, the characteristic can send notifications to the application when the characteristic's value changes. The Bluetooth Framework supports both notification types.

What Are Notifications And Indications

Indications and notifications are commands that could be sent 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 is also known as Central.
  • Server devices have the GATT database, access control methods, and provide resources to the remote client. This is also known as Peripheral.

BLE standard defines two ways to transfer data from 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 to be acknowledged, so they are faster. Hence, the server does not know if the message reaches the client. Indications need to be acknowledged to be communicated. The client sent a confirmation message back to the server; this way, the server knows that the 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, the 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 to 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 notifications, an application must write the Client Configuration Descriptor for the selected characteristic. To make it simple, the Bluetooth Framework includes the WriteClientConfiguration method that does all the things to write the descriptor. Once the notification has been received, the OnCharacteristicChanged event is called. 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 the characteristic's Handle that is also passed to the event handler. The code snippets below show how to subscribe to the characteristic change notifications, handle the OnCharacteristicChanged event, and unsubscribe from the characteristic change notifications.

The Bluetooth Framework also provides methods for fast characteristic changes notification subscribing (SubscribeForNotifications) and unsubscribing (UnsubscribeFromNotifications). These methods internally call the Subscribe and WriteClientConfiguration methods when needed.

NOTE: DFRobot Bluno boards do not implement Client Configuration Descriptor, and the 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.


                            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 0x000510B6 Error Code

For some characteristics, calling the Subscribe, Unsubscribe, and WriteClientConfiguration methods may return the 0x000510B6 (WCL_E_BLUETOOTH_LE_INVALID_CHARACTERISTIC_CONFIGURATION) 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 the other to False.

If you are interested in receiving notifications, then set the IsIndicatable property to False. If you are interested in receiving indications, then set the IsNotifiable property to False. Below you can find the same code as above but with characteristic properties checked.


                            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);
                                  ...
                            }