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
- Universally Unique ID
- Bluetooth LE Device Address
- Bluetooth Radio Object
- Discovering Bluetooth LE Devices
- Connecting To And Disconnect From BLE Devices
- Reading Services
- Reading Characteristics
- Reading And Writing Characteristic Value
- Reading Descriptors
- Characteristic Value Change Notifications
- What Are Notifications And Indications
- How To Subsribe To And Unsubscribe From Value Change Notifications
- Why Do I Get Error 0x000510B6 (WCL_E_BLUETOOTH_LE_INVALID_CHARACTERISTIC_CONFIGURATION)
- Sample Application
Generic Attributes
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);
...
}