Bluetooth Low Energy allows devices to broadcast information without establishing a connection. Such broadcasts are known as Bluetooth LE Advertisement. They are used by Bluetooth 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 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.
Advertisement and connection
Bluetooth LE advertising is a connectionless broadcast. A transmitter sends advertisement packets at a fixed interval, and any receiver in radio range can pick them up without pairing, bonding, or establishing a connection. This is the fundamental difference between advertising and a GATT connection: advertising is one-to-many, GATT is one-to-one and requires an established link.
Not every advertisement is the same. The Bluetooth specification defines several advertisement packet types,
and the Bluetooth Framework reports them through the PacketType parameter of the information
frame that the watcher delivers (see Beacon Watcher events below):
- Connectable undirected — any receiver may initiate a connection. Used by devices that want to be discovered and paired, not by typical beacons.
- Connectable directed — only a specific receiver is allowed to connect. Used during reconnection to a known peer.
- Scannable undirected — not connectable, but a receiver may send a scan request and receive a scan response that carries additional data. The scan response gives the advertiser a second packet's worth of payload.
- Non-connectable undirected — pure broadcast, no connection, no scan response. This is what a typical beacon uses.
- Scan response — the reply to a scan request. It is delivered as a separate frame and is not an advertisement on its own.
- Extended — an extended advertisement (Bluetooth 5). See Bluetooth 5 Extended Advertising.
Why does this matter for beacons? Because a beacon is usually non-connectable by design. Its job is to broadcast an identifier or a payload, not to accept connections. A connectable advertisement ties up the radio in a way that prevents it from rotating through multiple formats, and it exposes the device to unsolicited connection attempts. The Bluetooth Framework's advertiser always transmits non-connectable advertising — the packet types above are informational, delivered to the watcher for observers that want to distinguish a connectable device from a beacon.
Because advertising is connectionless, the receiver never acknowledges a packet. There is no retransmission, no flow control, and no guarantee that any given advertisement was received. A beacon that needs reliability has to broadcast repeatedly and accept that some fraction of packets will be lost to interference, distance, or channel conditions.
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.
Preparing the manager and radio
Every operation in the Bluetooth Framework starts with a working radio. The wclBluetoothManager
class enumerates the radios available on the system, opens the Bluetooth driver, and hands out a wclBluetoothRadio object. Both the
beacon watcher and the LE advertiser take a radio as the argument to their Start method — without one, nothing happens.
The standard pattern is to open the manager lazily, on first use, and then ask it for a working LE radio. The GetLeRadio
method returns the first radio that supports Bluetooth Low Energy, which is what beacons need. If the system has no LE-capable radio, the
method returns an error and the application should report it rather than silently failing.
The snippet below is the complete implementation from the Beacons sample.
function TfmMain.GetRadio: TwclBluetoothRadio;
var
Res: Integer;
begin
Result := nil;
if not BluetoothManager.Active then
Res := BluetoothManager.Open
else
Res := WCL_E_SUCCESS;
if Res <> WCL_E_SUCCESS then
Trace('Bluetooth Manager open', Res)
else begin
Res := BluetoothManager.GetLeRadio(Result);
if Res <> WCL_E_SUCCESS then
Trace('Get working radio', Res)
else
Trace('Use ' + Result.ApiName + ' radio');
end;
end;
TwclBluetoothRadio* __fastcall TfmMain::GetRadio()
{
int Res = WCL_E_SUCCESS;
if (!BluetoothManager->Active)
Res = BluetoothManager->Open();
if (Res != WCL_E_SUCCESS)
{
Trace("Bluetooth Manager open", Res);
return NULL;
}
TwclBluetoothRadio* Radio;
Res = BluetoothManager->GetLeRadio(Radio);
if (Res != WCL_E_SUCCESS)
{
Trace("Get working radio", Res);
return NULL;
}
Trace("Use " + Radio->ApiName + " radio");
return Radio;
}
private wclBluetoothRadio GetRadio()
{
Int32 Res = wclErrors.WCL_E_SUCCESS;
if (!BluetoothManager.Active)
Res = BluetoothManager.Open();
if (Res != wclErrors.WCL_E_SUCCESS)
{
Trace("Bluetooth Manager open", Res);
return null;
}
wclBluetoothRadio Radio;
Res = BluetoothManager.GetLeRadio(out Radio);
if (Res != wclErrors.WCL_E_SUCCESS)
{
Trace("Get working radio", Res);
return null;
}
Trace("Use " + Radio.ApiName + " radio");
return Radio;
}
Private Function GetRadio() As wclBluetoothRadio
Dim Res As Int32 = wclErrors.WCL_E_SUCCESS
If Not BluetoothManager.Active Then Res = BluetoothManager.Open()
If Res <> wclErrors.WCL_E_SUCCESS Then
Trace("Bluetooth Manager open", Res)
Return Nothing
End If
Dim Radio As wclBluetoothRadio = Nothing
Res = BluetoothManager.GetLeRadio(Radio)
If Res <> wclErrors.WCL_E_SUCCESS Then
Trace("Get working radio", Res)
Return Nothing
End If
Trace("Use " + Radio.ApiName + " radio")
Return Radio
End Function
CwclBluetoothRadio* CBeaconsDlg::GetRadio()
{
int Res = WCL_E_SUCCESS;
if (!BluetoothManager.Active)
Res = BluetoothManager.Open();
if (Res != WCL_E_SUCCESS)
{
Trace(_T("Bluetooth Manager open"), Res);
return NULL;
}
CwclBluetoothRadio* Radio;
Res = BluetoothManager.GetLeRadio(Radio);
if (Res != WCL_E_SUCCESS)
{
Trace(_T("Get working radio"), Res);
return NULL;
}
Trace(_T("Use ") + CString(Radio->ApiName.c_str()) + _T(" radio"));
return Radio;
}
Three things to note about this function:
- The manager is opened only once. The
Activeproperty reports whether it is already open, so the second and subsequent calls skip theOpenstep. TheBluetoothManagerAfterOpenevent fires on the first call only. - The returned radio is owned by the manager. The application must not free it; it becomes invalid when the manager is closed.
- The manager must be closed when the application exits. In the
Beaconssample this happens in the form's destruction handler —FormDestroyin Delphi and C++ Builder,fmMainFormClosedin C# and VB.NET,OnDestroyin the native C++ sample.
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
}
Scanning modes
Two scanning modes are available: active and passive. The difference is what the watcher does after it receives a connectable or scannable advertisement.
- Active (
smActive) — after receiving an advertisement, the watcher sends a scan request. The advertiser may reply with a scan response that carries additional data. Legacy advertising uses the scan response to deliver payload that does not fit in the main packet, so most beacon receivers use active mode. - Passive (
smPassive) — the watcher only listens. It never sends a scan request and therefore never receives a scan response. This is slightly cheaper on the radio and is the right choice when the receiver is itself battery-powered, or when the application only cares about the main advertisement.
For beacon reception, active mode is almost always the correct choice — most beacon formats carry their full payload in the advertisement packet itself, so passive scanning works too, but active mode is safer because it also picks up the scan response if the transmitter sends one. Passive mode is a deliberate optimisation for receivers that must preserve their own battery.
A related set of scan parameters controls the scan window and scan interval. The
framework exposes them as the ScanInterval and ScanWindow properties, with ScanParametersType
selecting a preset: ptDefault, ptCoexistenceOptimized, ptLowLatency, or ptCustom.
The Beacons sample uses the presets and only enables the two edit fields when the type is ptCustom.
Beacon Watcher events
The watcher does not deliver every frame through a single event. Instead, it exposes a small map of events, and the application subscribes only to the ones it cares about. The events fall into four groups:
Generic information. OnAdvertisementFrameInformation fires once per advertisement and delivers the
address, timestamp, RSSI, local name, packet type, and advertisement flags. It is the event most applications start with, because it
is enough to log every nearby device even when none of the specialised decoders apply.
Per-format events. Each supported beacon format has its own event: OnAppleiBeaconFrame,
OnAltBeaconFrame, OnEddystoneUidFrame, OnEddystoneUrlFrame, OnEddystoneTlmFrame,
OnManufacturerRawFrame, OnMicrosoftCdpBeaconFrame, and a set of Apple Continuity events
(OnAppleAirdropFrame, OnAppleAirplaySourceFrame, OnAppleAirplayTargetFrame,
OnAppleAirprintFrame, OnAppleHomeKitFrame, OnAppleNearbyActionFrame,
OnAppleNearbyInfoFrame, OnAppleProximityPairingFrame, OnAppleUnknownFrame,
OnAppleFindMyFrame, OnAppleHandoffFrame, OnAppleHeySiriFrame,
OnAppleMagicSwitchFrame, OnAppleTetheringSourceFrame, OnAppleTetheringTargetFrame). Each event
delivers a strongly-typed payload, so the application does not need to parse the bytes itself. The exact fields delivered by each
format, together with the corresponding class in the Beacons sample, are described in
Bluetooth LE Beacon Formats.
Raw fallbacks. Three events cover frames that do not fit a specific format:
OnAdvertisementRawFrame delivers individual AD structures that the framework did not decode, together with the data
type byte; OnAdvertisementReceived delivers the entire advertisement as a single byte stream; and
OnManufacturerRawFrame delivers any manufacturer-specific frame together with its company identifier. The last one is
listed among the per-format events above, but functionally it is the manufacturer-specific fallback.
Extended advertising. OnAdvertisementExtFrameInformation fires for Bluetooth 5 extended advertisements
and carries the fields that only the extended format exposes: address type, TX power, extended frame flags, and the primary and
secondary PHY. The details of this event and the properties that produce it are covered in
Bluetooth 5 Extended Advertising.
An application typically subscribes to OnAdvertisementFrameInformation for a raw log, plus one or two per-format events
for the specific decoders it needs. Subscribing to all of them is fine, but each event fires separately, so the handler code tends to
dominate the application's size — the Beacons sample subscribes to all of them because its job is to display every frame
it can.
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.
Reserved 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.
Advertiser lifecycle
The advertiser holds a list of advertisement objects, not a single advertisement. The workflow is always the same four steps:
Clear the previous list, Add one or more advertisement objects, Start the advertiser
with a working radio, and later Stop it. Between Start and Stop the radio broadcasts the
advertisements in rotation; after Stop the list is preserved, so a second Start resumes the same rotation
without re-adding the objects.
The Add method returns a result code. The application is expected to check it and, in native code, free the object it
passed in if the call failed. The Beacons sample wraps this in a small helper, because the same error handling is repeated
for every advertisement type:
procedure TfmMain.AddAdvertisement(const Adv: TwclBluetoothLeAdvertisement);
var
Res: Integer;
begin
Res := LeAdvertiser.Add(Adv);
if Res <> WCL_E_SUCCESS then begin
Trace('Add advertisement', Res);
Adv.Free;
end;
end;
void __fastcall TfmMain::AddAdvertisement(TwclBluetoothLeAdvertisement* const Adv)
{
int Res = LeAdvertiser->Add(Adv);
if (Res != WCL_E_SUCCESS)
{
Trace("Add advertisement", Res);
Adv->Free();
}
}
private void AddAdvertisement(wclBluetoothLeAdvertisement Adv)
{
Int32 Res = LeAdvertiser.Add(Adv);
if (Res != wclErrors.WCL_E_SUCCESS)
Trace("Add advertisement", Res);
}
Private Sub AddAdvertisement(Adv As wclBluetoothLeAdvertisement)
Dim Res As Int32 = LeAdvertiser.Add(Adv)
If Res <> wclErrors.WCL_E_SUCCESS Then Trace("Add advertisement", Res)
End Sub
void CBeaconsDlg::AddAdvertisement(CwclBluetoothLeAdvertisement* const Adv)
{
int Res = LeAdvertiser.Add(Adv);
if (Res != WCL_E_SUCCESS)
{
Trace(_T("Add advertisement"), Res);
delete Adv;
}
}
Two ownership rules follow from this pattern. First, the list is owned by the advertiser — calling Clear releases
every object that was previously added, so the application must not reuse or free those objects after Clear. Second,
if Add returns an error, the advertiser has not taken ownership of the object, so the caller is responsible for freeing
it (as the Delphi, C++ Builder, and native C++ samples do). The managed code samples (C#, VB.NET) rely on the garbage collector
for the failure path, which is why their helpers only log the error.
Because the list survives a Stop, the application can stop and restart the advertiser without rebuilding the
advertisement list. Rebuilding is only necessary when the content of the advertisements changes — for example, when the user edits
the beacon UUID, or when a different format is selected in the UI.
Interval and rotation
The Interval property sets how often the advertiser transmits. Lower values make the device more discoverable and
reduce the latency of telemetry updates, but they increase power consumption and the amount of airtime the radio uses. Higher values
do the opposite: they save power and reduce congestion, at the cost of slower discovery and coarser telemetry granularity. The
Beacons sample exposes the interval as an editable field and passes the parsed value directly to the property.
When more than one advertisement is added to the same advertiser, the interval applies to the rotation as a whole rather than to each advertisement individually. The advertiser transmits one advertisement per interval, then moves to the next one in the list, and so on, cycling. The effective rate of any single advertisement is therefore the configured interval divided by the number of advertisements in the list. This is what makes it possible to broadcast iBeacon to legacy scanners and Eddystone URL to browsers from a single radio, at the cost of broadcasting each of them less often.
The interval is a hint to the underlying driver, not a guarantee. The driver may round the value to the nearest supported step, and the radio itself may not start a new advertisement while it is still transmitting the previous one. Applications that need precise timing should read back the value after the advertiser starts, or rely on the interval only as a relative ordering between different configurations.
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. When more than one advertisement is added, the advertiser
does not transmit them simultaneously — it rotates through them, sending one advertisement, then the next, then the next, and so on,
cycling. The example below creates an iBeacon and a manufacturer-specific advertisement, adds both to the advertiser's rotation, 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 supports both predefined and fully custom advertisement packets, subject to driver restrictions. Multiple advertisements can be added to the same advertiser, but they are broadcast in rotation — not simultaneously. - Which scanning mode should I use — active or passive?
- Active scanning (
smActive) also sends scan requests and picks up scan responses, which is the safer default for beacon receivers. Passive scanning (smPassive) only listens, uses slightly less radio time, and is appropriate when the receiver is battery-powered or when scan-response payloads are not needed. - What does the advertising Interval control?
- The
Intervalproperty sets how often the advertiser transmits. Short intervals (for example,100 ms) reduce discovery latency but consume more power and airtime; long intervals (for example,1000 ms) do the opposite. When several advertisements are added to the same advertiser, the interval applies to the rotation as a whole — one advertisement is sent per interval, then the advertiser moves to the next. - Do I need to open the Bluetooth manager manually?
- Yes. The
wclBluetoothManagermust be opened before any radio can be used, and closed when the application exits. A common pattern is to open it lazily on first use and close it in the form's destruction handler.