Use platform-specific Bluetooth features
Use bare-bluetooth-apple and bare-bluetooth-android for what bare-bluetooth leaves out: reconnect without scanning, dial bonded devices, tune MTU and scan mode.
bare-bluetooth gives you one API on every platform, and leaves out what only one platform can do. When you need one of those features, write that part of your code against the platform package that bare-bluetooth would load anyway: bare-bluetooth-apple on iOS and macOS, bare-bluetooth-android on Android.
Every example below comes from the platform package's own test suite or from an app that ships, so expect test assertions (t.ok(), t.is()) in some of them. The test snippets also lean on the suite's setup: brittle's test(), the isCI flag, and two helpers from the package's test/helpers.js, waitForPoweredOn(emitter) and waitForEvent(emitter, name, timeout), which resolves with the event's arguments or with null on timeout. They import from ../lib/...; in your own code, import from the package (require('bare-bluetooth-apple') or require('bare-bluetooth-android')) and use plain event listeners instead.
Before you begin
The platform packages are not drop-in replacements for bare-bluetooth:
- Names differ. The peripheral role is
PeripheralManageron Apple andServeron Android.bare-bluetoothcalls itServereverywhere. - States differ.
bare-bluetooth-androidreports'on'and'off'wherebare-bluetoothreports'poweredOn'and'poweredOff'. Apple's states are the same asbare-bluetooth's. - Objects don't mix. A
Peripheralfrombare-bluetoothcan't be passed to a platform package'sCentral, or the other way round. Use one package per Bluetooth manager.
Some Android-only options need no platform package at all: bare-bluetooth passes them through and ignores them elsewhere. Those are marked below.
Apple: reconnect without scanning
On Apple, a peripheral's id is a UUID that the system assigns and keeps on this host, rather than the device's address. Store it, and next time central.retrievePeripherals([id]) turns it back into something you can connect() to, without waiting for a scan to hear it. The test below scans once to learn an id, then retrieves and connects:
test('connect and disconnect a retrieved peripheral', { skip: isCI, timeout: 30000 }, async (t) => {
using central = new Central()
await waitForPoweredOn(central)
central.on('error', () => {})
let retrieved = null
let connected = null
central.startScan()
while (connected === null) {
const discovered = await new Promise((resolve) => {
central.once('discover', resolve)
})
central.stopScan()
retrieved = central.retrievePeripherals([discovered.id])[0]
if (retrieved) {
central.connect(retrieved)
const event = await waitForEvent(central, 'connect', 5000)
// A connect attempt never times out on its own, so a pending one has to be
// cancelled or it outlives this loop and fires later.
if (event) connected = event[0]
else central.disconnect(retrieved)
}
if (connected === null) central.startScan()
}
t.is(connected.id, retrieved.id)
t.is(central.retrievePeripherals([retrieved.id])[0].state, 'connected')
central.disconnect(retrieved)
t.ok(await waitForEvent(central, 'disconnect', 5000))
t.is(central.retrievePeripherals([retrieved.id])[0].state, 'disconnected')
})- A retrieved peripheral has an
id, anameand a connectionstate, but norssiorserviceData: it comes from the system's records, not from an advertisement. retrievePeripherals()throws unless Bluetooth is powered on, and throws for anidthat isn't a UUID. An unknownidreturns an empty array.- Like any connection attempt,
connect()on a retrieved peripheral never times out by itself. The test gives up after 5 seconds and callsdisconnect()to cancel the attempt; your app needs its own timeout too.
central.retrieveConnectedPeripherals(serviceUUIDs) works the same way for peripherals that are already connected to the system and offer one of the given services, for example through another app. Its state reports that system connection. You still call connect() before this central can use them.
Apple: let iOS prompt to turn Bluetooth on
By default, neither Central nor PeripheralManager asks the system to show its "turn on Bluetooth" alert when Bluetooth is off. Opt in when you create the manager. The package's tests pass this option but show no usage, so this short snippet is written for this page from its type declarations:
const { Central } = require('bare-bluetooth-apple')
const central = new Central({ showPowerAlert: true })Android: dial a bonded device
Devices the user has paired with in system settings are bonded. central.getBondedDevices() lists them, and new Peripheral({ device }) makes one dialable without a scan. The test below dials the first bonded device that supports Bluetooth Low Energy:
test('dial a bonded device', { skip: isCI, timeout: 30000 }, async (t) => {
const central = new Central()
t.teardown(() => central.destroy())
const state = await new Promise((resolve) => {
central.on('stateChange', resolve)
})
if (state !== 'on') {
t.comment('bluetooth not on: ' + state + ', skipping')
return
}
const [device] = central.getBondedDevices().filter((d) => d.type !== Device.DEVICE_TYPE_CLASSIC)
if (!device) {
t.comment('no bonded LE device, skipping')
return
}
const peripheral = new Peripheral({ device })
central.on('error', () => {})
central.connect(peripheral)
t.ok(peripheral.connecting, 'dial is in flight')
const connected = await new Promise((resolve) => {
central.once('connect', (p) => resolve(p === peripheral))
setTimeout(() => resolve(false), 15000)
})
if (!connected) {
t.comment('bonded device not reachable, skipping')
central.disconnect(peripheral)
return
}
t.ok(peripheral.connected, 'peripheral is connected')
t.is(peripheral.id, device.address)
central.disconnect(peripheral)
})getBondedDevices()throws without theBLUETOOTH_CONNECTpermission, and returns an empty array unless the adapter is on.- Filter out
Device.DEVICE_TYPE_CLASSIC: a classic-only device can't take a Bluetooth Low Energy connection. - A peripheral built from a
Devicehas noscanResult, so itsrssiisnull. - The test gives up after 15 seconds and calls
disconnect()to cancel the attempt if the device isn't reachable.
Android: ask for a larger MTU
Each GATT write or notification carries at most the connection's MTU minus 3 bytes. Apple and Linux negotiate a larger MTU themselves; on Android, ask for one after connecting with peripheral.requestMtu(517), and wait for mtuChanged with the size the two sides agreed on. A failure arrives as error with code MTU_CHANGE_FAILED.
requestMtu() is also on bare-bluetooth's Peripheral, where it does nothing on Apple and Linux. ble-swarm calls it unconditionally after every connection for that reason.
Android: choose a scan mode
On Android, a scan runs in low-latency mode unless you say otherwise: bare-bluetooth-android defaults scanMode to SCAN_MODE_LOW_LATENCY, not to Android's own power-saving default. That suits a device picker the user is waiting on. For a long scan in the background, pass SCAN_MODE_BALANCED or SCAN_MODE_LOW_POWER to trade discovery speed for battery.
scanMode is one of bare-bluetooth's own scan options, passed to Android and ignored elsewhere, so it needs no platform package. bare-mobile-doctor sets it only on Android, spelling out the default:
const isAndroid = Bare.platform === 'android'
const scanOptions = isAndroid ? { scanMode: Central.SCAN_MODE_LOW_LATENCY } : undefinedand passes it along with the service filter when it starts scanning:
this._central.startScan([this.serviceUUID], scanOptions)Whatever the mode, stop the scan as soon as you've found what you need.
Android: open the GATT server yourself
bare-bluetooth opens Android's GATT server for you whenever the adapter turns on. With bare-bluetooth-android directly, that's your job: call server.open() once state is 'on', before addService(). The server doesn't survive the adapter turning off, and its services don't survive a reopen, so open it and add your services again on every transition back to 'on'. server.close() closes it and drops its services.
Where to go next
bare-bluetooth-applereference andbare-bluetooth-androidreference: everything each platform package exposes, including Android'sScanResultandScanRecordfor raw advertisement data.- Bluetooth in Bare: how the cross-platform package maps onto the platform ones.
- Set up Bluetooth in a mobile app: the permissions these calls need.
Last updated on