A Node.js BLE (Bluetooth Low Energy) central module.
Want to implement a peripheral? Check out bleno.
Note: macOS / Mac OS X, Linux, FreeBSD and Windows are currently the only supported OSes.
Documentation
Quick Start Example
const noble = require('@abandonware/noble');
noble.on('stateChange', async (state) => {
if (state === 'poweredOn') {
await noble.startScanningAsync(['180f'], false);
}
});
noble.on('discover', async (peripheral) => {
await noble.stopScanningAsync();
await peripheral.connectAsync();
const {characteristics} = await peripheral.discoverSomeServicesAndCharacteristicsAsync(['180f'], ['2a19']);
const batteryLevel = (await characteristics[0].readAsync())[0];
console.log(`${peripheral.address} (${peripheral.advertisement.localName}): ${batteryLevel}%`);
await peripheral.disconnectAsync();
process.exit(0);
});
Use Noble With BLE5 Extended Features With HCI
const noble = require('@abandonware/noble/with-custom-binding')({extended: true});
Installation
Prerequisites
OS X
- Install Xcode
- On newer versions of OSX, allow bluetooth access on the terminal app: "System Preferences" —> "Security & Privacy" —> "Bluetooth" -> Add terminal app (see Sandboxed terminal)
Linux
- Kernel version 3.6 or above
libbluetooth-dev
needs to be installed. For instructions for specific distributions, see below.- To set the necessary privileges to run without sudo, see this section. This is required for all distributions (Raspbian, Ubuntu, Fedora, etc). You will not get any errors if running without sudo, but nothing will happen.
Ubuntu, Debian, Raspbian
See the generic Linux notes above first.
sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev
Make sure node
is on your PATH
. If it's not, some options:
If you are having trouble connecting to BLE devices on a Raspberry Pi, you should disable the pnat
plugin. Add the following line at the bottom of /etc/bluetooth/main.conf
:
DisablePlugins=pnat
Then restart the system.
See Issue #425 · OpenWonderLabs/homebridge-switchbot.
Fedora and other RPM-based distributions
See the generic Linux notes above first.
sudo yum install bluez bluez-libs bluez-libs-devel
Intel Edison
See the generic Linux notes above first.
See Configure Intel Edison for Bluetooth LE (Smart) Development.
FreeBSD
Make sure you have GNU Make:
sudo pkg install gmake
Disable automatic loading of the default Bluetooth stack by putting no-ubt.conf into /usr/local/etc/devd/no-ubt.conf
and restarting devd (sudo service devd restart
).
Unload ng_ubt
kernel module if already loaded:
sudo kldunload ng_ubt
Make sure you have read and write permissions on the /dev/usb/*
device that corresponds to your Bluetooth adapter.
Windows
node-gyp requirements for Windows
Install the required tools and configurations using Microsoft's windows-build-tools from an elevated PowerShell or cmd.exe (run as Administrator).
npm install --global --production windows-build-tools
node-bluetooth-hci-socket prerequisites
- Compatible Bluetooth 5.0 Zephyr HCI-USB adapter (you need to add BLUETOOTH_HCI_SOCKET_USB_VID and BLUETOOTH_HCI_SOCKET_USB_PID to the process env)
- Compatible Bluetooth 4.0 USB adapter
- WinUSB driver setup for Bluetooth 4.0 USB adapter, using Zadig tool
See @don's setup guide on Bluetooth LE with Node.js and Noble on Windows
Docker
Make sure your container runs with --network=host
options and all specific environment preriquisites are verified.
Installing and using the package
npm install @abandonware/noble
In Windows OS add your custom hci-usb dongle to the process env
set BLUETOOTH_HCI_SOCKET_USB_VID=xxx
set BLUETOOTH_HCI_SOCKET_USB_PID=xxx
const noble = require('@abandonware/noble');
API docs
All operations have two API variants – one expecting a callback, one returning a Promise (denoted by Async
suffix).
Additionally, there are events corresponding to each operation (and a few global events).
For example, in case of the "discover services" operation of Peripheral:
- There's a
discoverServices
method expecting a callback:
peripheral.discoverServices((error, services) => {
});
- There's a
discoverServicesAsync
method returning a Promise:
try {
const services = await peripheral.discoverServicesAsync();
} catch (e) {
}
- There's a
servicesDiscover
event emitted after services are discovered:
peripheral.once('servicesDiscover', (services) => {
});
API structure:
Scanning and discovery
Event: Adapter state changed
noble.on('stateChange', callback(state));
state
can be one of:
unknown
resetting
unsupported
unauthorized
poweredOff
poweredOn
Start scanning
noble.startScanning();
noble.startScanning([], true);
var serviceUUIDs = ['<service UUID 1>', ...];
var allowDuplicates = falseOrTrue;
noble.startScanning(serviceUUIDs, allowDuplicates[, callback(error)]);
NOTE: noble.state
must be poweredOn
before scanning is started. noble.on('stateChange', callback(state));
can be used to listen for state change events.
Event: Scanning started
noble.on('scanStart', callback);
The event is emitted when:
- Scanning is started
- Another application enables scanning
- Another application changes scanning settings
Stop scanning
noble.stopScanning();
Event: Scanning stopped
noble.on('scanStop', callback);
The event is emitted when:
- Scanning is stopped
- Another application stops scanning
Event: Peripheral discovered
noble.on('discover', callback(peripheral));
peripheral
:
{
id: '<id>',
address: '<BT address'>,
addressType: '<BT address type>',
connectable: trueOrFalseOrUndefined,
advertisement: {
localName: '<name>',
txPowerLevel: someInteger,
serviceUuids: ['<service UUID>', ...],
serviceSolicitationUuid: ['<service solicitation UUID>', ...],
manufacturerData: someBuffer,
serviceData: [
{
uuid: '<service UUID>',
data: someBuffer
},
]
},
rssi: integerValue,
mtu: integerValue
};
Note: On macOS, the address will be set to '' if the device has not been connected previously.
Event: Warning raised
noble.on('warning', callback(message));
Reset device
noble.reset()
Peripheral
Connect
peripheral.connect([callback(error)]);
Some of the bluetooth devices doesn't connect seamlessly, may be because of bluetooth device firmware or kernel. Do reset the device with noble.reset() API before connect API.
Event: Connected
peripheral.once('connect', callback);
Cancel a pending connection
peripheral.cancelConnect();
Disconnect
peripheral.disconnect([callback(error)]);
Event: Disconnected
peripheral.once('disconnect', callback);
peripheral.updateRssi([callback(error, rssi)]);
peripheral.once('rssiUpdate', callback(rssi));
Discover services
peripheral.discoverServices();
var serviceUUIDs = ['<service UUID 1>', ...];
peripheral.discoverServices(serviceUUIDs[, callback(error, services)]);
Discover all services and characteristics
peripheral.discoverAllServicesAndCharacteristics([callback(error, services, characteristics)]);
Discover some services and characteristics
var serviceUUIDs = ['<service UUID 1>', ...];
var characteristicUUIDs = ['<characteristic UUID 1>', ...];
peripheral.discoverSomeServicesAndCharacteristics(serviceUUIDs, characteristicUUIDs, [callback(error, services, characteristics));
Event: Services discovered
peripheral.once('servicesDiscover', callback(services));
Read handle
peripheral.readHandle(handle, callback(error, data));
Event: Handle read
peripheral.once('handleRead<handle>', callback(data));
<handle>
is the handle identifier.
Write handle
peripheral.writeHandle(handle, data, withoutResponse, callback(error));
Event: Handle written
peripheral.once('handleWrite<handle>', callback());
<handle>
is the handle identifier.
Service
Discover included services
service.discoverIncludedServices();
var serviceUUIDs = ['<service UUID 1>', ...];
service.discoverIncludedServices(serviceUUIDs[, callback(error, includedServiceUuids)]);
Event: Included services discovered
service.once('includedServicesDiscover', callback(includedServiceUuids));
Discover characteristics
service.discoverCharacteristics()
var characteristicUUIDs = ['<characteristic UUID 1>', ...];
service.discoverCharacteristics(characteristicUUIDs[, callback(error, characteristics)]);
Event: Characteristics discovered
service.once('characteristicsDiscover', callback(characteristics));
Characteristic
Read
characteristic.read([callback(error, data)]);
Event: Data read
characteristic.on('data', callback(data, isNotification));
characteristic.once('read', callback(data, isNotification));
Emitted when:
- Characteristic read has completed, result of
characteristic.read(...)
- Characteristic value has been updated by peripheral via notification or indication, after having been enabled with
characteristic.notify(true[, callback(error)])
Note: isNotification
event parameter value MAY be undefined
depending on platform. The parameter is deprecated after version 1.8.1, and not supported on macOS High Sierra and later.
Write
characteristic.write(data, withoutResponse[, callback(error)]);
withoutResponse
:
false
: send a write request, used with "write" characteristic propertytrue
: send a write command, used with "write without response" characteristic property
Event: Data written
characteristic.once('write', withoutResponse, callback());
Emitted when characteristic write has completed, result of characteristic.write(...)
.
Broadcast
characteristic.broadcast(broadcast[, callback(error)]);
Event: Broadcast sent
characteristic.once('broadcast', callback(state));
Emitted when characteristic broadcast state changes, result of characteristic.broadcast(...)
.
Subscribe
characteristic.subscribe([callback(error)]);
Subscribe to a characteristic.
Triggers data
events when peripheral sends a notification or indication. Use for characteristics with "notify" or "indicate" properties.
Event: Notification received
characteristic.once('notify', callback(state));
Emitted when characteristic notification state changes, result of characteristic.notify(...)
.
Unsubscribe
characteristic.unsubscribe([callback(error)]);
Unsubscribe from a characteristic.
Use for characteristics with "notify" or "indicate" properties
Discover descriptors
characteristic.discoverDescriptors([callback(error, descriptors)]);
Event: Descriptors discovered
characteristic.once('descriptorsDiscover', callback(descriptors));
descriptors
:
[
{
uuid: '<uuid>'
},
]
Descriptor
Read value
descriptor.readValue([callback(error, data)]);
Event: Value read
descriptor.once('valueRead', data);
Write value
descriptor.writeValue(data[, callback(error)]);
Event: Value written
descriptor.once('valueWrite');
Advanced usage
Override default bindings
By default, noble will select appropriate Bluetooth device bindings based on your platform. You can provide custom bindings using the with-bindings
module.
var noble = require('@abandonware/noble/with-bindings')(require('./my-custom-bindings'));
Running without root/sudo (Linux-specific)
Run the following command:
sudo setcap cap_net_raw+eip $(eval readlink -f `which node`)
This grants the node
binary cap_net_raw
privileges, so it can start/stop BLE advertising.
Note: The above command requires setcap
to be installed.
It can be installed the following way:
- apt:
sudo apt-get install libcap2-bin
- yum:
su -c \'yum install libcap2-bin\'
Multiple Adapters (Linux-specific)
hci0
is used by default.
To override, set the NOBLE_HCI_DEVICE_ID
environment variable to the interface number.
For example, to specify hci1
:
sudo NOBLE_HCI_DEVICE_ID=1 node <your file>.js
If you are using multiple HCI devices in one setup you can run two instances of noble with different binding configurations by initializing them seperatly in code:
const HCIBindings = require('@abandonware/noble/lib/hci-socket/bindings');
const Noble = require('@abandonware/noble/lib/noble');
const params = {
deviceId: 0,
userChannel: true,
extended: false //ble5 extended features
};
const noble = new Noble(new HCIBindings(params));
Reporting all HCI events (Linux-specific)
By default, noble waits for both the advertisement data and scan response data for each Bluetooth address. If your device does not use scan response, the NOBLE_REPORT_ALL_HCI_EVENTS
environment variable can be used to bypass it.
sudo NOBLE_REPORT_ALL_HCI_EVENTS=1 node <your file>.js
bleno compatibility (Linux-specific)
By default, noble will respond with an error whenever a GATT request message is received. If your intention is to use bleno in tandem with noble, the NOBLE_MULTI_ROLE
environment variable can be used to bypass this behaviour.
Note: this requires a Bluetooth 4.1 adapter.
sudo NOBLE_MULTI_ROLE=1 node <your file>.js
Common problems
Maximum simultaneous connections
This limit is imposed by the Bluetooth adapter hardware as well as its firmware.
Platform | |
---|
OS X 10.11 (El Capitan) | 6 |
Linux/Windows - Adapter-dependent | 5 (CSR based adapter) |
Sandboxed terminal
On newer versions of OSX, the terminal app is sandboxed to not allow bluetooth connections by default. If you run a script that tries to access it, you will get an Abort trap: 6
error.
To enable bluetooth, go to "System Preferences" —> "Security & Privacy" —> "Bluetooth" -> Add your terminal into allowed apps.
Adapter-specific known issues
Some BLE adapters cannot connect to a peripheral while they are scanning (examples below). You will get the following messages when trying to connect:
Sena UD-100 (Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)): Error: Command disallowed
Intel Dual Band Wireless-AC 7260 (Intel Corporation Wireless 7260 (rev 73)): Error: Connection Rejected due to Limited Resources (0xd)
You need to stop scanning before trying to connect in order to solve this issue.
Backers
Support us with a monthly donation and help us continue our activities. [Become a backer]
Become a sponsor and get your logo on our README on Github with a link to your site. [Become a sponsor]
Useful links
License
Copyright (C) 2015 Sandeep Mistry sandeep.mistry@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.