Bluetooth Low Energy allows devices to broadcast information without establishing a connection. Such broadcasts are known as Bluetooth LE Advertisement. They are used by BLE beacons, Apple Continuity, Drone Remote ID, and many other applications. Advertising is also part of the discovery process and carries information about nearby Bluetooth-enabled devices.
The common use of BLE advertising is Bluetooth Beacons — hardware transmitters that broadcast an identifier to nearby devices. Apple Continuity and Drone Remote ID are two other protocols built on top of BLE advertisement.
The Bluetooth Framework provides methods to receive and decode BLE advertisement packets, also known
as frames, as well as to build and broadcast custom advertisements. The library package includes the Beacons sample application
that shows both sides of this workflow.
Receive BLE Advertisements
To receive Bluetooth LE advertisements with the Bluetooth Framework, use the
wclBluetoothLeBeaconWatcher class. It monitors nearby BLE devices and decodes their advertisement frames. The class can
decode the following advertisement types:
- Appearance
- Basic Information
- Extended Information
- 16-bit, 32-bit, and 128-bit Service Data
- 16-bit, 32-bit, and 128-bit Solicitation Service
- TX Power
- 128-bit UUID
- AltBeacon
- Eddystone TLM, UID, and URL
- Apple Continuity
- Microsoft CDP Beacon
- Drone Remote ID
- Manufacturer Raw
Unknown frames are passed to the application as a raw byte stream. The stream can be decoded by the application if needed.
Starting the Beacon Watcher
Before capturing advertisements, obtain a working radio from the wclBluetoothManager, configure the watcher's scanning
mode, and hook the frame events. The snippet below shows the minimal start and stop logic along with an example event handler.
procedure TfmMain.btWatcherStartClick(Sender: TObject);
var
Radio: TwclBluetoothRadio;
Res: Integer;
begin
Radio := GetRadio;
if Radio <> nil then begin
BeaconWatcher.ScanningMode := smActive;
BeaconWatcher.AllowExtendedAdvertisements := True;
Res := BeaconWatcher.Start(Radio);
if Res <> WCL_E_SUCCESS then
Trace('Start watcher failed', Res);
end;
end;
procedure TfmMain.btWatcherStopClick(Sender: TObject);
var
Res: Integer;
begin
Res := BeaconWatcher.Stop;
if Res <> WCL_E_SUCCESS then
Trace('Stop watcher failed', Res);
end;
procedure TfmMain.BeaconWatcherAdvertisementFrameInformation(
Sender: TObject; const Address: Int64; const Timestamp: Int64;
const Rssi: SByte; const Name: string;
const PacketType: TwclBluetoothLeAdvertisementType;
const Flags: TwclBluetoothLeAdvertisementFlags);
begin
Log('Frame from ' + IntToHex(Address, 12) +
' Name: ' + Name + ' RSSI: ' + IntToStr(Rssi));
end;
void __fastcall TfmMain::btWatcherStartClick(TObject *Sender)
{
TwclBluetoothRadio* Radio = GetRadio();
if (Radio != NULL)
{
BeaconWatcher->ScanningMode = smActive;
BeaconWatcher->AllowExtendedAdvertisements = true;
int Res = BeaconWatcher->Start(Radio);
if (Res != WCL_E_SUCCESS)
Trace("Start watcher failed", Res);
}
}
void __fastcall TfmMain::btWatcherStopClick(TObject *Sender)
{
int Res = BeaconWatcher->Stop();
if (Res != WCL_E_SUCCESS)
Trace("Stop watcher failed", Res);
}
void __fastcall TfmMain::BeaconWatcherAdvertisementFrameInformation(
TObject *Sender, const __int64 Address, const __int64 Timestamp,
const SByte Rssi, const String Name,
const TwclBluetoothLeAdvertisementType PacketType,
const TwclBluetoothLeAdvertisementFlags Flags)
{
Log("Frame from " + IntToHex(Address, 12) +
" Name: " + Name + " RSSI: " + IntToStr(Rssi));
}
private void btWatcherStart_Click(Object sender, EventArgs e)
{
wclBluetoothRadio Radio = GetRadio();
if (Radio != null)
{
BeaconWatcher.ScanningMode = wclBluetoothLeScanningMode.smActive;
BeaconWatcher.AllowExtendedAdvertisements = true;
Int32 Res = BeaconWatcher.Start(Radio);
if (Res != wclErrors.WCL_E_SUCCESS)
Trace("Start watcher failed", Res);
}
}
private void btWatcherStop_Click(Object sender, EventArgs e)
{
Int32 Res = BeaconWatcher.Stop();
if (Res != wclErrors.WCL_E_SUCCESS)
Trace("Stop watcher failed", Res);
}
void BeaconWatcherAdvertisementFrameInformation(Object Sender,
long Address, long Timestamp, sbyte Rssi, string Name,
wclBluetoothLeAdvertisementType PacketType,
wclBluetoothLeAdvertisementFlag Flags)
{
Log("Frame from " + Address.ToString("X12") +
" Name: " + Name + " RSSI: " + Rssi.ToString());
}
Private Sub btWatcherStart_Click(sender As Object, e As EventArgs)
Handles btWatcherStart.Click
Dim Radio As wclBluetoothRadio = GetRadio()
If Radio IsNot Nothing Then
BeaconWatcher.ScanningMode = wclBluetoothLeScanningMode.smActive
BeaconWatcher.AllowExtendedAdvertisements = True
Dim Res As Int32 = BeaconWatcher.Start(Radio)
If Res <> wclErrors.WCL_E_SUCCESS Then
Trace("Start watcher failed", Res)
End If
End If
End Sub
Private Sub btWatcherStop_Click(sender As Object, e As EventArgs)
Handles btWatcherStop.Click
Dim Res As Int32 = BeaconWatcher.Stop()
If Res <> wclErrors.WCL_E_SUCCESS Then
Trace("Stop watcher failed", Res)
End If
End Sub
Private Sub BeaconWatcherAdvertisementFrameInformation(
Sender As Object, Address As Long, Timestamp As Long,
Rssi As SByte, Name As String,
PacketType As wclBluetoothLeAdvertisementType,
Flags As wclBluetoothLeAdvertisementFlag)
Handles BeaconWatcher.OnAdvertisementFrameInformation
Log("Frame from " & Address.ToString("X12") &
" Name: " & Name & " RSSI: " & Rssi.ToString())
End Sub
void CBeaconsDlg::OnBnClickedButtonWatcherStart()
{
CwclBluetoothRadio* Radio = GetRadio();
if (Radio != NULL)
{
BeaconWatcher.ScanningMode = smActive;
BeaconWatcher.AllowExtendedAdvertisements = true;
int Res = BeaconWatcher.Start(Radio);
if (Res != WCL_E_SUCCESS)
Trace(_T("Start watcher failed"), Res);
}
}
void CBeaconsDlg::OnBnClickedButtonWatcherStop()
{
int Res = BeaconWatcher.Stop();
if (Res != WCL_E_SUCCESS)
Trace(_T("Stop watcher failed"), Res);
}
CwclBluetoothRadio* CBeaconsDlg::GetRadio()
{
if (!BluetoothManager.Active)
BluetoothManager.Open();
CwclBluetoothRadio* Radio = NULL;
BluetoothManager.GetLeRadio(Radio);
return Radio;
}
void CBeaconsDlg::BeaconWatcherAdvertisementFrameInformation(
void* Sender, const __int64 Address, const __int64 Timestamp,
const char Rssi, const tstring& Name,
const wclBluetoothLeAdvertisementType PacketType,
const wclBluetoothLeAdvertisementFlags& Flags)
{
UNREFERENCED_PARAMETER(Sender);
// Store or display the frame data
}
Transmit BLE Advertising
The Bluetooth Framework also allows transmitting
custom BLE advertisements. The wclBluetoothLeAdvertiser class switches the application to
Bluetooth LE Advertiser mode. It supports predefined advertisements as well as fully custom packets, subject to the restrictions
described below.
Supported Advertisement Types
The following advertisement types are reserved and not allowed:
| Type | Constant | Description |
|---|---|---|
00 | LE_GAP_AD_TYPE_NONE | Empty flags |
01 | LE_GAP_AD_TYPE_FLAGS | Flags |
08 | LE_GAP_AD_TYPE_LOCAL_NAME_SHORT | Shortened Local Name |
09 | LE_GAP_AD_TYPE_LOCAL_NAME_COMPLETE | Complete Local Name |
1B | LE_GAP_AD_TYPE_ADDRESS | LE Bluetooth Device Address |
1C | LE_GAP_AD_TYPE_ROLE | LE Role |
The following types are additionally reserved by the Microsoft Bluetooth driver and not allowed when the advertiser runs on top of it:
| Type | Constant | Description |
|---|---|---|
02 | LE_GAP_AD_TYPE_SERVICES_16_MORE | Incomplete List of 16-bit Service UUIDs |
03 | LE_GAP_AD_TYPE_SERVICES_16_ALL | Complete List of 16-bit Service UUIDs |
04 | LE_GAP_AD_TYPE_SERVICES_32_MORE | Incomplete List of 32-bit Service UUIDs |
05 | LE_GAP_AD_TYPE_SERVICES_32_ALL | Complete List of 32-bit Service UUIDs |
06 | LE_GAP_AD_TYPE_SERVICES_128_MORE | Incomplete List of 128-bit Service UUIDs |
07 | LE_GAP_AD_TYPE_SERVICES_128_ALL | Complete List of 128-bit Service UUIDs |
0A | LE_GAP_AD_TYPE_TXPOWER | Tx Power Level |
0D | LE_GAP_AD_TYPE_COD | Class of Device |
0E | LE_GAP_AD_TYPE_PAIRING_HASH_C | Simple Pairing Hash C192 |
0F | LE_GAP_AD_TYPE_PAIRING_RAND_R | Simple Pairing Randomizer R192 |
10 | LE_GAP_AD_TYPE_DEVICE_ID | Security Manager TK Values |
11 | LE_GAP_AD_TYPE_SM_OOB_FLAGS | Security Manager OOB Flags |
12 | LE_GAP_AD_TYPE_CON_INTERVALS | Slave Connection Interval Range |
17 | LE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS | Public Target Address |
18 | LE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS | Random Target Address |
19 | LE_GAP_AD_TYPE_APPEARANCE | Appearance |
1A | LE_GAP_AD_TYPE_ADV_INTERVAL | Advertising Interval |
1D | LE_GAP_AD_TYPE_PAIRING_HASH_C_256 | Simple Pairing Hash C256 |
1E | LE_GAP_AD_TYPE_PAIRING_RAND_R_256 | Simple Pairing Randomizer R256 |
3D | LE_GAP_AD_TYPE_3D_INFO | 3D Information Data |
Extended Advertisement
If the hardware supports Bluetooth 5 and above, the Bluetooth Framework can
use the Extended Advertisement Format when running with the Microsoft Bluetooth driver on Windows 10 and
above. Enable it by setting the UseExtendedAdvertisement property of the wclBluetoothLeAdvertiser class.
When the property is set to true, three additional properties control the advertisement:
Anonymous— when set totrue, the device address is not included in the advertisement header.IncludeTxRssi— when set totrue, the transmit power level is included in the advertisement header.PreferredTxRssi— requests the radio to use the indicated transmit power level. To disable, set the value to-127.
If the application enables Extended Advertisement on hardware or an OS that does not support it, the
WCL_E_BLUETOOTH_LE_EXT_ADV_NOT_SUPPORTED error code is returned when the advertisement is started.
Starting the LE Advertiser
The wclBluetoothLeAdvertiser class allows broadcasting standard beacon frames such as iBeacon, Eddystone, and AltBeacon,
as well as fully custom raw advertisements. The example below creates an iBeacon and a manufacturer-specific advertisement, enables
extended advertising, and starts the transmission.
const
BEACON_UUID: TGUID = '{09039835-4A80-443B-87AA-DC565D09EA61}';
procedure TfmMain.btAdvertiserStartClick(Sender: TObject);
var
Radio: TwclBluetoothRadio;
Adv: TwclBluetoothLeAdvertisement;
Data: TwclBluetoothLeAdvertisementFrameRawData;
Res: Integer;
begin
Radio := GetRadio;
if Radio = nil then
Exit;
LeAdvertiser.Clear;
// iBeacon
Adv := TwclBluetoothLeiBeaconAdvertisement.Create(
-5, $0101, $0202, BEACON_UUID);
LeAdvertiser.Add(Adv);
// Manufacturer specific
SetLength(Data, 2);
Data[0] := $12;
Data[1] := $34;
Adv := TwclBluetoothLeManufacturerAdvertisement.Create($010E, Data);
LeAdvertiser.Add(Adv);
// Extended advertisement (Bluetooth 5)
LeAdvertiser.UseExtendedAdvertisement := True;
LeAdvertiser.Anonymous := False;
LeAdvertiser.IncludeTxRssi := True;
LeAdvertiser.PreferredTxRssi := 10;
LeAdvertiser.Interval := 100;
Res := LeAdvertiser.Start(Radio);
if Res <> WCL_E_SUCCESS then
Trace('Start advertiser failed', Res);
end;
procedure TfmMain.btAdvertiserStopClick(Sender: TObject);
var
Res: Integer;
begin
Res := LeAdvertiser.Stop;
if Res <> WCL_E_SUCCESS then
Trace('Stop advertiser failed', Res);
end;
const GUID BEACON_UUID = {
0x09039835, 0x4A80, 0x443B,
{ 0x87, 0xAA, 0xDC, 0x56, 0x5D, 0x09, 0xEA, 0x61 }
};
void __fastcall TfmMain::btAdvertiserStartClick(TObject *Sender)
{
TwclBluetoothRadio* Radio = GetRadio();
if (Radio == NULL)
return;
LeAdvertiser->Clear();
// iBeacon
TwclBluetoothLeiBeaconAdvertisement* Adv;
Adv = new TwclBluetoothLeiBeaconAdvertisement(
-5, 0x0101, 0x0202, BEACON_UUID);
LeAdvertiser->Add(Adv);
// Manufacturer specific
TwclBluetoothLeAdvertisementFrameRawData Data;
Data.Length = 2;
Data[0] = 0x12;
Data[1] = 0x34;
Adv = new TwclBluetoothLeManufacturerAdvertisement(0x010E, Data);
LeAdvertiser->Add(Adv);
// Extended advertisement
LeAdvertiser->UseExtendedAdvertisement = true;
LeAdvertiser->Anonymous = false;
LeAdvertiser->IncludeTxRssi = true;
LeAdvertiser->PreferredTxRssi = 10;
LeAdvertiser->Interval = 100;
int Res = LeAdvertiser->Start(Radio);
if (Res != WCL_E_SUCCESS)
Trace("Start advertiser failed", Res);
}
void __fastcall TfmMain::btAdvertiserStopClick(TObject *Sender)
{
int Res = LeAdvertiser->Stop();
if (Res != WCL_E_SUCCESS)
Trace("Stop advertiser failed", Res);
}
private static readonly Guid BEACON_UUID =
new Guid("{09039835-4A80-443B-87AA-DC565D09EA61}");
private void btAdvertiserStart_Click(Object sender, EventArgs e)
{
wclBluetoothRadio Radio = GetRadio();
if (Radio == null)
return;
LeAdvertiser.Clear();
// iBeacon
wclBluetoothLeAdvertisement Adv =
new wclBluetoothLeiBeaconAdvertisement(
-5, 0x0101, 0x0202, BEACON_UUID);
LeAdvertiser.Add(Adv);
// Manufacturer specific
byte[] Data = new byte[2] { 0x12, 0x34 };
Adv = new wclBluetoothLeManufacturerAdvertisement(0x010E, Data);
LeAdvertiser.Add(Adv);
// Extended advertisement
LeAdvertiser.UseExtendedAdvertisement = true;
LeAdvertiser.Anonymous = false;
LeAdvertiser.IncludeTxRssi = true;
LeAdvertiser.PreferredTxRssi = 10;
LeAdvertiser.Interval = 100;
Int32 Res = LeAdvertiser.Start(Radio);
if (Res != wclErrors.WCL_E_SUCCESS)
Trace("Start advertiser failed", Res);
}
private void btAdvertiserStop_Click(Object sender, EventArgs e)
{
Int32 Res = LeAdvertiser.Stop();
if (Res != wclErrors.WCL_E_SUCCESS)
Trace("Stop advertiser failed", Res);
}
Private Shared ReadOnly BEACON_UUID As New Guid(
"{09039835-4A80-443B-87AA-DC565D09EA61}")
Private Sub btAdvertiserStart_Click(sender As Object, e As EventArgs)
Handles btAdvertiserStart.Click
Dim Radio As wclBluetoothRadio = GetRadio()
If Radio Is Nothing Then Return
LeAdvertiser.Clear()
' iBeacon
Dim Adv As wclBluetoothLeAdvertisement =
New wclBluetoothLeiBeaconAdvertisement(
-5, &H101, &H202, BEACON_UUID)
LeAdvertiser.Add(Adv)
' Manufacturer specific
Dim Data As Byte() = New Byte() {&H12, &H34}
Adv = New wclBluetoothLeManufacturerAdvertisement(&H10E, Data)
LeAdvertiser.Add(Adv)
' Extended advertisement
LeAdvertiser.UseExtendedAdvertisement = True
LeAdvertiser.Anonymous = False
LeAdvertiser.IncludeTxRssi = True
LeAdvertiser.PreferredTxRssi = 10
LeAdvertiser.Interval = 100
Dim Res As Int32 = LeAdvertiser.Start(Radio)
If Res <> wclErrors.WCL_E_SUCCESS Then
Trace("Start advertiser failed", Res)
End If
End Sub
Private Sub btAdvertiserStop_Click(sender As Object, e As EventArgs)
Handles btAdvertiserStop.Click
Dim Res As Int32 = LeAdvertiser.Stop()
If Res <> wclErrors.WCL_E_SUCCESS Then
Trace("Stop advertiser failed", Res)
End If
End Sub
static const GUID BEACON_UUID = {
0x09039835, 0x4A80, 0x443B,
{ 0x87, 0xAA, 0xDC, 0x56, 0x5D, 0x09, 0xEA, 0x61 }
};
void CBeaconsDlg::OnBnClickedButtonAdvertiserStart()
{
CwclBluetoothRadio* Radio = GetRadio();
if (Radio == NULL)
return;
LeAdvertiser.Clear();
// iBeacon
CwclBluetoothLeAdvertisement* Adv;
Adv = new CwclBluetoothLeiBeaconAdvertisement(
-5, 0x0101, 0x0202, BEACON_UUID);
LeAdvertiser.Add(Adv);
// Manufacturer specific
wclBluetoothLeAdvertisementFrameRawData Data;
Data.resize(2);
Data[0] = 0x12;
Data[1] = 0x34;
Adv = new CwclBluetoothLeManufacturerAdvertisement(0x010E, Data);
LeAdvertiser.Add(Adv);
// Extended advertisement
LeAdvertiser.UseExtendedAdvertisement = true;
LeAdvertiser.Anonymous = false;
LeAdvertiser.IncludeTxRssi = true;
LeAdvertiser.PreferredTxRssi = 10;
LeAdvertiser.Interval = 100;
int Res = LeAdvertiser.Start(Radio);
if (Res != WCL_E_SUCCESS)
Trace(_T("Start advertiser failed"), Res);
}
void CBeaconsDlg::OnBnClickedButtonAdvertiserStop()
{
int Res = LeAdvertiser.Stop();
if (Res != WCL_E_SUCCESS)
Trace(_T("Stop advertiser failed"), Res);
}
Frequently asked questions
- What is Bluetooth LE Advertisement?
- It is the broadcast transmission of data by BLE devices without establishing a connection, used for beacons, identification, and telemetry.
- How to receive BLE packets with Bluetooth Framework?
- Use the
wclBluetoothLeBeaconWatcherclass. It decodes dozens of standard packet types, including Eddystone, Apple Continuity, and Drone Remote ID. - Can I transmit custom BLE advertising packets?
- Yes. The
wclBluetoothLeAdvertiserclass allows transmitting both predefined and fully custom advertisement packets, subject to driver restrictions.