Advertise
Host a local BLE peripheral and advertise a GATT service so nearby centrals can connect.
Advertising is how this host is found. A local peripheral serves GATT, sends advertisements, and waits for a central to connect.
SimpleBLE::Peripheral is a remote device. SimpleBLE::Local::Peripheral is this host. Create the local peripheral from an Adapter, the same object used to scan.
Available on all supported platforms
SimpleBLE can host a local peripheral on every supported platform, and every language binding exposes the same flow. Native Android applications must provide their application context with SimpleBLE::Advanced::Android::set_context() before creating the local peripheral. SimpleDroidBLE does this for you when you pass a Context, and SimpleKtBLE does it for you without one.
Flow
- Get an adapter.
- Create a local peripheral.
- Select the advertised service UUIDs, if needed.
- Add services and characteristics.
- Register client or write callbacks if you need them.
- Call
start(), keep the process alive, thenstop().
Advertise a readable characteristic
auto adapters = SimpleBLE::Adapter::get_adapters();
auto adapter = adapters.front();
auto peripheral = adapter.create_local_peripheral();
peripheral.add_advertised_service("12345678-1234-5678-1234-56789abcdef0");
auto service = peripheral.add_service("12345678-1234-5678-1234-56789abcdef0");
auto characteristic = service.add_characteristic(
"12345678-1234-5678-1234-56789abcdef1",
{SimpleBLE::Local::CharacteristicCapability::READ,
SimpleBLE::Local::CharacteristicCapability::WRITE_REQUEST,
SimpleBLE::Local::CharacteristicCapability::NOTIFY});
characteristic.set_value(SimpleBLE::ByteArray("ready"));
characteristic.set_callback_on_write([&characteristic](SimpleBLE::ByteArray value) {
// Echo the written value to subscribed clients.
characteristic.set_value(std::move(value));
});
peripheral.set_callback_on_client_connected(
[](SimpleBLE::BluetoothAddress address) {
std::cout << "Client connected: " << address << std::endl;
});
peripheral.start();import simplepyble
Capability = simplepyble.LocalCharacteristicCapability
adapters = simplepyble.Adapter.get_adapters()
adapter = adapters[0]
peripheral = adapter.create_local_peripheral()
peripheral.add_advertised_service("12345678-1234-5678-1234-56789abcdef0")
service = peripheral.add_service("12345678-1234-5678-1234-56789abcdef0")
characteristic = service.add_characteristic(
"12345678-1234-5678-1234-56789abcdef1",
[Capability.READ, Capability.WRITE_REQUEST, Capability.NOTIFY],
)
characteristic.set_value(b"ready")
def on_write(value):
# Echo the written value to subscribed clients.
characteristic.set_value(value)
characteristic.set_callback_on_write(on_write)
peripheral.set_callback_on_client_connected(
lambda address: print(f"Client connected: {address}"))
peripheral.start()List<Adapter> adapters = Adapter.getAdapters();
Adapter adapter = adapters.get(0);
LocalPeripheral peripheral = adapter.createLocalPeripheral();
peripheral.addAdvertisedService(new BluetoothUUID("12345678-1234-5678-1234-56789abcdef0"));
LocalService service = peripheral.addService(new BluetoothUUID("12345678-1234-5678-1234-56789abcdef0"));
LocalCharacteristic characteristic = service.addCharacteristic(
new BluetoothUUID("12345678-1234-5678-1234-56789abcdef1"),
LocalCharacteristicCapability.READ,
LocalCharacteristicCapability.WRITE_REQUEST,
LocalCharacteristicCapability.NOTIFY);
characteristic.setValue("ready".getBytes(StandardCharsets.UTF_8));
characteristic.setEventListener(new LocalCharacteristic.EventListener() {
@Override
public void onWrite(byte[] value) {
// Echo the written value to subscribed clients.
characteristic.setValue(value);
}
});
peripheral.setEventListener(new LocalPeripheral.EventListener() {
@Override
public void onClientConnected(BluetoothAddress address) {
System.out.println("Client connected: " + address);
}
});
peripheral.start();use futures::stream::StreamExt;
use simplersble::{Adapter, CharacteristicCapability, ClientEvent, LocalCharacteristicEvent};
let adapter = Adapter::get_adapters().unwrap().remove(0);
let peripheral = adapter.create_local_peripheral().unwrap();
peripheral.add_advertised_service("12345678-1234-5678-1234-56789abcdef0").unwrap();
let service = peripheral.add_service("12345678-1234-5678-1234-56789abcdef0").unwrap();
let characteristic = service
.add_characteristic(
"12345678-1234-5678-1234-56789abcdef1",
&[
CharacteristicCapability::Read,
CharacteristicCapability::WriteRequest,
CharacteristicCapability::Notify,
],
)
.unwrap();
characteristic.set_value(b"ready").unwrap();
let mut characteristic_events = characteristic.on_event();
let echo = characteristic.clone();
tokio::spawn(async move {
while let Some(Ok(event)) = characteristic_events.next().await {
if let LocalCharacteristicEvent::Written(value) = event {
// Echo the written value to subscribed clients.
echo.set_value(&value).unwrap();
}
}
});
let mut client_events = peripheral.on_client_event();
tokio::spawn(async move {
while let Some(Ok(event)) = client_events.next().await {
if let ClientEvent::Connected(address) = event {
println!("Client connected: {}", address);
}
}
});
peripheral.start().unwrap();import { Adapter } from 'simplejsble';
const [adapter] = await Adapter.getAdapters();
const peripheral = adapter.createLocalPeripheral();
peripheral.addAdvertisedService('12345678-1234-5678-1234-56789abcdef0');
const service = peripheral.addService('12345678-1234-5678-1234-56789abcdef0');
const characteristic = service.addCharacteristic('12345678-1234-5678-1234-56789abcdef1', [
'read',
'writeRequest',
'notify',
]);
await characteristic.setValue(Buffer.from('ready'));
// Echo the written value to subscribed clients.
characteristic.on('write', value => characteristic.setValue(value));
peripheral.on('clientConnected', address => console.log(`Client connected: ${address}`));
await peripheral.start();import { Adapter } from 'simplernble';
const [adapter] = await Adapter.getAdapters();
const peripheral = adapter.createLocalPeripheral();
peripheral.addAdvertisedService('12345678-1234-5678-1234-56789abcdef0');
const service = peripheral.addService('12345678-1234-5678-1234-56789abcdef0');
const characteristic = service.addCharacteristic('12345678-1234-5678-1234-56789abcdef1', [
'read',
'writeRequest',
'notify',
]);
await characteristic.setValue(Uint8Array.from('ready', c => c.charCodeAt(0)));
// Echo the written value to subscribed clients.
characteristic.on('write', value => characteristic.setValue(value));
peripheral.on('clientConnected', address => console.log(`Client connected: ${address}`));
await peripheral.start();On Android, request BLUETOOTH_ADVERTISE and BLUETOOTH_CONNECT at runtime first.
val adapter = Adapter.getAdapters().first()
val peripheral = adapter.createLocalPeripheral()
peripheral.addAdvertisedService(BluetoothUUID("12345678-1234-5678-1234-56789abcdef0"))
val characteristic = peripheral
.addService(BluetoothUUID("12345678-1234-5678-1234-56789abcdef0"))
.addCharacteristic(
BluetoothUUID("12345678-1234-5678-1234-56789abcdef1"),
LocalCharacteristicCapability.Read,
LocalCharacteristicCapability.WriteRequest,
LocalCharacteristicCapability.Notify
)
characteristic.setValue("ready".encodeToByteArray())
// Echo the written value to subscribed clients. `scope` is the CoroutineScope that owns the host.
scope.launch(start = CoroutineStart.UNDISPATCHED) {
characteristic.onWrite.collect { value -> characteristic.setValue(value) }
}
scope.launch(start = CoroutineStart.UNDISPATCHED) {
peripheral.onClientConnected.collect { address -> println("Client connected: $address") }
}
peripheral.start()val adapter = Adapter.getAdapters().first()
val peripheral = adapter.createLocalPeripheral(applicationContext)
peripheral.addAdvertisedService(BluetoothUUID("12345678-1234-5678-1234-56789abcdef0"))
val characteristic = peripheral
.addService(BluetoothUUID("12345678-1234-5678-1234-56789abcdef0"))
.addCharacteristic(
BluetoothUUID("12345678-1234-5678-1234-56789abcdef1"),
LocalCharacteristicCapability.Read,
LocalCharacteristicCapability.WriteRequest,
LocalCharacteristicCapability.Notify
)
characteristic.value = "ready".encodeToByteArray()
characteristic.setWriteHandler { value ->
// Echo the written value to subscribed clients.
characteristic.value = value
}
lifecycleScope.launch {
peripheral.onClientConnected.collect { address ->
Log.d("SimpleBLE", "Client connected: $address")
}
}
lifecycleScope.launch {
peripheral.start()
}Configure services, characteristics, and advertising before start(). While started, update data with set_value() rather than rebuilding the GATT table. set_value() also publishes the latest value to subscribed clients when the characteristic has NOTIFY or INDICATE capability.
SimpleBLE permits the same adapter to scan while the local peripheral is started when the platform Bluetooth stack and controller support concurrent scanning and advertising. On Linux, BlueZ and the controller must support both operations at the same time.
Advertise a beacon
A beacon is a non-connectable local peripheral without services. Manufacturer data carries its payload, such as an iBeacon frame or a format of your own.
auto peripheral = adapter.create_local_peripheral();
// iBeacon: 0x02 0x15, a 16-byte UUID, major, minor, and the measured power at 1 m.
SimpleBLE::ByteArray ibeacon = {0x02, 0x15,
0xe2, 0xc5, 0x6d, 0xb5, 0xdf, 0xfb, 0x48, 0xd2,
0xb0, 0x60, 0xd0, 0xf5, 0xa7, 0x10, 0x96, 0xe0,
0x00, 0x01, // Major
0x00, 0x02, // Minor
0xc5}; // Measured power (-59 dBm)
peripheral.set_advertised_manufacturer_data(0x004c, ibeacon);
peripheral.set_connectable(false);
peripheral.start();import simplepyble
adapter = simplepyble.Adapter.get_adapters()[0]
peripheral = adapter.create_local_peripheral()
# iBeacon: 0x02 0x15, a 16-byte UUID, major, minor, and the measured power at 1 m.
ibeacon = bytes.fromhex("0215" "e2c56db5dffb48d2b060d0f5a71096e0" "0001" "0002" "c5")
peripheral.set_advertised_manufacturer_data(0x004C, ibeacon)
peripheral.set_connectable(False)
peripheral.start()Adapter adapter = Adapter.getAdapters().get(0);
LocalPeripheral peripheral = adapter.createLocalPeripheral();
// iBeacon: 0x02 0x15, a 16-byte UUID, major, minor, and the measured power at 1 m.
byte[] ibeacon = HexFormat.of().parseHex("0215" + "e2c56db5dffb48d2b060d0f5a71096e0" + "0001" + "0002" + "c5");
peripheral.setAdvertisedManufacturerData(0x004c, ibeacon);
peripheral.setConnectable(false);
peripheral.start();use simplersble::Adapter;
let adapter = Adapter::get_adapters().unwrap().remove(0);
let peripheral = adapter.create_local_peripheral().unwrap();
// iBeacon: 0x02 0x15, a 16-byte UUID, major, minor, and the measured power at 1 m.
let ibeacon = [
0x02, 0x15,
0xe2, 0xc5, 0x6d, 0xb5, 0xdf, 0xfb, 0x48, 0xd2, 0xb0, 0x60, 0xd0, 0xf5, 0xa7, 0x10, 0x96, 0xe0,
0x00, 0x01, 0x00, 0x02, 0xc5,
];
peripheral.set_advertised_manufacturer_data(0x004c, &ibeacon).unwrap();
peripheral.set_connectable(false).unwrap();
peripheral.start().unwrap();import { Adapter } from 'simplejsble';
const [adapter] = await Adapter.getAdapters();
const peripheral = adapter.createLocalPeripheral();
// iBeacon: 0x02 0x15, a 16-byte UUID, major, minor, and the measured power at 1 m.
const ibeacon = Buffer.from('0215' + 'e2c56db5dffb48d2b060d0f5a71096e0' + '0001' + '0002' + 'c5', 'hex');
peripheral.setAdvertisedManufacturerData(0x004c, ibeacon);
peripheral.setConnectable(false);
await peripheral.start();import { Adapter } from 'simplernble';
const [adapter] = await Adapter.getAdapters();
const peripheral = adapter.createLocalPeripheral();
// iBeacon: 0x02 0x15, a 16-byte UUID, major, minor, and the measured power at 1 m.
const ibeacon = Uint8Array.of(
0x02, 0x15,
0xe2, 0xc5, 0x6d, 0xb5, 0xdf, 0xfb, 0x48, 0xd2, 0xb0, 0x60, 0xd0, 0xf5, 0xa7, 0x10, 0x96, 0xe0,
0x00, 0x01, 0x00, 0x02, 0xc5,
);
peripheral.setAdvertisedManufacturerData(0x004c, ibeacon);
peripheral.setConnectable(false);
await peripheral.start();val adapter = Adapter.getAdapters().first()
val peripheral = adapter.createLocalPeripheral()
// iBeacon: 0x02 0x15, a 16-byte UUID, major, minor, and the measured power at 1 m.
val ibeacon = listOf(
0x02, 0x15,
0xe2, 0xc5, 0x6d, 0xb5, 0xdf, 0xfb, 0x48, 0xd2, 0xb0, 0x60, 0xd0, 0xf5, 0xa7, 0x10, 0x96, 0xe0,
0x00, 0x01, 0x00, 0x02, 0xc5,
).map { it.toByte() }.toByteArray()
peripheral.setAdvertisedManufacturerData(0x004c, ibeacon)
peripheral.setConnectable(false)
peripheral.start()val adapter = Adapter.getAdapters().first()
val peripheral = adapter.createLocalPeripheral(applicationContext)
// iBeacon: 0x02 0x15, a 16-byte UUID, major, minor, and the measured power at 1 m.
val ibeacon = listOf(
0x02, 0x15,
0xe2, 0xc5, 0x6d, 0xb5, 0xdf, 0xfb, 0x48, 0xd2, 0xb0, 0x60, 0xd0, 0xf5, 0xa7, 0x10, 0x96, 0xe0,
0x00, 0x01, 0x00, 0x02, 0xc5,
).map { it.toByte() }.toByteArray()
peripheral.setAdvertisedManufacturerData(0x004c, ibeacon)
peripheral.setConnectable(false)
lifecycleScope.launch { peripheral.start() }Setting data for the same company identifier again replaces it. To change the payload while advertising, call stop(), set the data, then start() again.
Beacons work on Linux, Windows, and Android. macOS and iOS can't advertise manufacturer data. Windows advertises manufacturer data only from a non-connectable peripheral without services.
Hosting from C
SimpleCBLE provides the same local-hosting flow through owned local peripheral, service, and characteristic handles. See the C hosting example for creation, start/stop, and cleanup.
Practical notes
- Keep the process alive after
start(). Advertising stops when the object is destroyed orstop()is called. - Each characteristic needs at least one capability. In C++, pass them as a
std::set(an initializer list is enough). - On Linux, BlueZ must be running and the process must have D-Bus access, as with scanning.
- On macOS, CoreBluetooth reports client connection and disconnection callbacks when the client first subscribes and finally unsubscribes.
- On Windows, the adapter and driver must support the peripheral role. Applications distributed as AppX/MSIX packages should declare
<DeviceCapability Name="bluetooth" />inPackage.appxmanifest; ordinary unpackaged executables do not need this manifest declaration. Windows controls the advertised Bluetooth name. - On Android, request
BLUETOOTH_ADVERTISEandBLUETOOTH_CONNECTat runtime beforestart(). - On Android, SimpleBLE includes the adapter's system Bluetooth name. Android's public advertising API cannot set an arbitrary per-advertisement name.
- Linux, macOS, and iOS callers that need a platform-specific advertised name can use
set_advertisement_local_name()from the correspondingSimpleBLE::Advancedplatform namespace, or its binding equivalent, beforestart().
For the full C++ walkthrough, see Advertise, Serve, Publish. For scanning from the other side of the link, see Scan.
