Bluetooth Low Energy (BLE) has revolutionized how devices communicate wirelessly, enabling lightweight, low-power connectivity. If you’re diving into BLE with Node.js, understanding how to scan BLE advertisements is essential. I recently tackled this challenge using the @abandonware/noble
library and TypeScript, and I want to share the process, step by step.
Thank me by sharing on Twitter 🙏
What is BLE (Bluetooth Low Energy) Advertisement Scanning?
Before we start, BLE devices continuously broadcast small packets of data, known as advertisements, to nearby listeners. These packets include information such as device names, signal strength, and supported services. Scanning advertisements is an excellent way to interact with BLE-enabled devices like smart sensors, wearables, or IoT gadgets.
Using @abandonware/noble
, you can discover BLE devices in real-time and retrieve detailed information about their broadcasts. Now, let me walk you through how I approached this task.
Setting Up Noble for Scanning
First, you need to install the @abandonware/noble
library. It’s a robust choice for BLE development in Node.js, especially with TypeScript, thanks to its maintained support and TypeScript type definitions.
To install the library, use:
USB 3.0 Hub, VIENON 4-Port USB Hub USB Splitter USB Expander for Laptop, Xbox, Flash Drive, HDD, Console, Printer, Camera,Keyborad, Mouse
$7.99 (as of January 23, 2025 11:35 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)HP 67 Black Ink Cartridge | Works with HP DeskJet 1255, 2700, 4100 Series, HP ENVY 6000, 6400 Series | Eligible for Instant Ink | 3YM56AN
$18.89 (as of January 23, 2025 11:35 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)HP 67 Black/Tri-color Ink Cartridges (2 Pack) | Works with HP DeskJet 1255, 2700, 4100 Series, HP ENVY 6000, 6400 Series | Eligible for Instant Ink | 3YP29AN
$36.89 (as of January 23, 2025 11:35 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)npm install @abandonware/noble
After installation, the library provides everything you need to start scanning for BLE devices. Once your Bluetooth adapter is ready, you can programmatically begin scanning, handle discovered devices, and process their advertisement data.
Writing the Core Code
Here’s the TypeScript code I used to scan BLE advertisements. This script initializes Noble, starts scanning when the Bluetooth adapter is powered on, and logs information about discovered devices.
import noble, { Peripheral } from '@abandonware/noble';
// Callback for discovered BLE devices
function onDiscover(peripheral: Peripheral): void {
const { id, address, advertisement, rssi } = peripheral;
console.log('Discovered device:');
console.log(` ID: ${id}`);
console.log(` Address: ${address}`);
console.log(` RSSI: ${rssi}`);
if (advertisement) {
const { localName, txPowerLevel, manufacturerData, serviceData, serviceUuids } = advertisement;
console.log(` Advertisement:`);
console.log(` Local Name: ${localName || 'N/A'}`);
console.log(` Tx Power Level: ${txPowerLevel || 'N/A'}`);
console.log(` Manufacturer Data: ${manufacturerData?.toString('hex') || 'N/A'}`);
console.log(` Service Data: ${JSON.stringify(serviceData) || 'N/A'}`);
console.log(` Service UUIDs: ${serviceUuids || 'N/A'}`);
}
console.log('---------------------------------------------');
}
// Handle Noble state changes
noble.on('stateChange', (state: string) => {
if (state === 'poweredOn') {
console.log('Bluetooth adapter powered on. Starting scanning...');
noble.startScanning([], true);
} else {
console.log(`Bluetooth adapter state changed to ${state}. Stopping scanning.`);
noble.stopScanning();
}
});
// Listen for device discovery
noble.on('discover', onDiscover);
// Graceful shutdown on process exit
process.on('SIGINT', () => {
console.log('Stopping scanning...');
noble.stopScanning(() => {
process.exit(1);
});
});
Breaking Down the Script
Monitoring Bluetooth State
BLE scanning requires the Bluetooth adapter to be in the right state. Using the stateChange
event, the script waits until the adapter is poweredOn
before starting the scan. If the state changes (e.g., the adapter turns off), scanning stops automatically. This ensures your script runs smoothly across different environments and hardware setups.
noble.on('stateChange', (state: string) => {
if (state === 'poweredOn') {
noble.startScanning([], true).catch((error) => {
console.error('Error starting scan:', error);
});
} else {
noble.stopScanning().catch((error) => {
console.error('Error stopping scan:', error);
});
}
});
Handling Discovered Devices
The discover
event is triggered each time a BLE device is found. Here, the onDiscover
function processes and logs details like device ID, address, signal strength (RSSI), and advertisement data. Advertisement packets often include key-value pairs, such as the device’s name or manufacturer-specific data.
function onDiscover(peripheral: Peripheral): void {
const { id, address, advertisement, rssi } = peripheral;
console.log('Discovered device:');
console.log(` ID: ${id}`);
console.log(` Address: ${address}`);
console.log(` RSSI: ${rssi}`);
if (advertisement) {
console.log(` Advertisement:`);
console.log(` Local Name: ${advertisement.localName || 'N/A'}`);
console.log(` Manufacturer Data: ${advertisement.manufacturerData?.toString('hex') || 'N/A'}`);
}
}
Managing Shutdown Gracefully
When the script is interrupted (e.g., via Ctrl+C
), it ensures BLE scanning stops cleanly. This prevents your Bluetooth adapter from being left in a scanning state, which could block other applications.
process.on('SIGINT', () => {
console.log('Stopping scanning...');
noble.stopScanning().then(() => {
process.exit(0);
}).catch((error) => {
console.error('Error while stopping scanning:', error);
process.exit(1);
});
});
Interpreting Advertisement Data
Understanding BLE advertisement data can seem daunting at first, but Noble makes it manageable. The advertisement
object in each discovered peripheral contains useful properties like:
- Local Name: The human-readable name of the device.
- Manufacturer Data: Raw data bytes provided by the device manufacturer.
- Service UUIDs: A list of supported BLE services, identifying what the device offers.
These properties help you identify devices of interest and decode their purpose.
Debugging and Expanding the Script
As with any asynchronous Node.js program, debugging BLE scripts can involve some trial and error. If your script doesn’t detect devices, ensure:
- Your Bluetooth adapter supports BLE.
- The adapter is powered on and accessible.
- The script runs with appropriate permissions (e.g.,
sudo
on Linux).
From here, you can extend the script to:
- Filter devices based on specific service UUIDs.
- Connect to peripherals and exchange data.
- Log advertisements to a database for later analysis.
Wrapping Up
Scanning BLE advertisements is an exciting entry point into the world of wireless communication. With TypeScript and @abandonware/noble
, I found it straightforward to build a robust scanner that can handle real-world scenarios. Whether you’re exploring IoT projects or just tinkering with Bluetooth, this approach gives you the flexibility to unlock the potential of BLE devices.