A BLE transmitter broadcasting advertisement packets to multiple receivers

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

Beacon watcher receiving BLE advertisement frames and decoding them into structured data

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

BLE advertiser broadcasting custom advertisement packets to nearby receivers

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:

TypeConstantDescription
00LE_GAP_AD_TYPE_NONEEmpty flags
01LE_GAP_AD_TYPE_FLAGSFlags
08LE_GAP_AD_TYPE_LOCAL_NAME_SHORTShortened Local Name
09LE_GAP_AD_TYPE_LOCAL_NAME_COMPLETEComplete Local Name
1BLE_GAP_AD_TYPE_ADDRESSLE Bluetooth Device Address
1CLE_GAP_AD_TYPE_ROLELE Role

The following types are additionally reserved by the Microsoft Bluetooth driver and not allowed when the advertiser runs on top of it:

TypeConstantDescription
02LE_GAP_AD_TYPE_SERVICES_16_MOREIncomplete List of 16-bit Service UUIDs
03LE_GAP_AD_TYPE_SERVICES_16_ALLComplete List of 16-bit Service UUIDs
04LE_GAP_AD_TYPE_SERVICES_32_MOREIncomplete List of 32-bit Service UUIDs
05LE_GAP_AD_TYPE_SERVICES_32_ALLComplete List of 32-bit Service UUIDs
06LE_GAP_AD_TYPE_SERVICES_128_MOREIncomplete List of 128-bit Service UUIDs
07LE_GAP_AD_TYPE_SERVICES_128_ALLComplete List of 128-bit Service UUIDs
0ALE_GAP_AD_TYPE_TXPOWERTx Power Level
0DLE_GAP_AD_TYPE_CODClass of Device
0ELE_GAP_AD_TYPE_PAIRING_HASH_CSimple Pairing Hash C192
0FLE_GAP_AD_TYPE_PAIRING_RAND_RSimple Pairing Randomizer R192
10LE_GAP_AD_TYPE_DEVICE_IDSecurity Manager TK Values
11LE_GAP_AD_TYPE_SM_OOB_FLAGSSecurity Manager OOB Flags
12LE_GAP_AD_TYPE_CON_INTERVALSSlave Connection Interval Range
17LE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESSPublic Target Address
18LE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESSRandom Target Address
19LE_GAP_AD_TYPE_APPEARANCEAppearance
1ALE_GAP_AD_TYPE_ADV_INTERVALAdvertising Interval
1DLE_GAP_AD_TYPE_PAIRING_HASH_C_256Simple Pairing Hash C256
1ELE_GAP_AD_TYPE_PAIRING_RAND_R_256Simple Pairing Randomizer R256
3DLE_GAP_AD_TYPE_3D_INFO3D 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 to true, the device address is not included in the advertisement header.
  • IncludeTxRssi — when set to true, 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 wclBluetoothLeBeaconWatcher class. It decodes dozens of standard packet types, including Eddystone, Apple Continuity, and Drone Remote ID.
Can I transmit custom BLE advertising packets?
Yes. The wclBluetoothLeAdvertiser class allows transmitting both predefined and fully custom advertisement packets, subject to driver restrictions.