Trying to make the world better place by improving the bluetooth experience one library at a time.
- Install XCode
-
Give
net_cap_raw
tonode
This command may not work if you're using NVM or asdf!
Make sure to give the actual node binary the permissionsudo setcap cap_net_raw+eip $(eval readlink -f $(which node))
-
Stop/Disable the bluetooth service if you're planning to advertise with the
hci
bindings:Please note that this stops the
dbus
bindings from working properly!sudo systemctl stop bluetooth
You can also disable it permanently:
sudo systemctl disable bluetooth
-
sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev
-
sudo yum install bluez bluez-libs bluez-libs-devel
Install the module from npm:
npm i -E @modum-io/modblue
Then install the peer depencies depending on which one(s) you want to use:
-
hci
(using bluetooth-hci-socket)npm i -E @abandonware/bluetooth-hci-socket
-
dbus
(using dbus-next)npm i -E dbus-next
Create a new MODblue
object to get started (all bindings have the same interface)
import { HciMODblue } from '@modum-io/modblue/hci'; // for hci bindings
// import { DbusMODblue } from '@modum-io/modblue/dbus'; // for dbus bindings
const modblue = new HciMODblue(); // or: new DbusMODblue()
Now you can scan & use one or multiple of the adapters:
const adapters = await modblue.getAdapters();
const adapter = adapters[0];
Use an adapter to scan for devices in proximity:
const serviceUUIDs: string[] = []; // Optional: Advertised service UUIDs, without dashes (-)
const allowDuplicates: boolean = true; // Optional: Allow duplicate 'discover' events for the same device
await adapter.startScanning(serviceUUIDs, allowDuplicates);
Now you can either wait for a few seconds to scan and then get all the scanned peripherals
const peripherals = await adapter.getScannedPeripherals();
or you can attach an event to trigger each time a new peripheral is discovered (or more often if allowDuplicates
is true
):
adapter.on('discover', (peripheral) => console.log('Discovered', peripheral.address));
Once discovered you can connect to a peripheral and grab it's GATT to discover services and characteristics:
await peripheral.connect();
// Setup GATT
const requestMtu = 517; // Optional: Request a specific MTU
const gatt = await peripheral.setupGatt(requestMtu);
// Discover services
const services = await gatt.discoverServices();
const service = services[0];
// Discover characteristics
const characteristics = await service.discoverCharacteristics();
const characteristic = characteristics[0];
// Discover descriptors
const descriptors = await characteristic.discoverDescriptors();
const descriptor = descriptors[0];
// Read value
const buffer = await characteristic.read();
// or
const buffer = await descriptor.readValue();
// Write value
const withoutResponse: boolean = true; // Tell the peripheral we don't need a response for this write
await characteristic.write(buffer, withoutResponse);
// or
await descriptor.writeValue(buffer);
First you have to setup the local GATT and the services and characteristics you want to advertise:
import { GattServiceInput } from '@modum-io/modblue';
const maxMtu: number = 517; // Optional: Specify the maximum MTU that should be negotiated with connecting devices.
const gatt = await adapter.setupGatt(maxMtu); // Setup our local GATT server
const deviceName: string = 'MODblue Testing';
const services: GattServiceInput[] = [
{
uuid: '48ee0000bf49460ca3d77ec7a512a4ce', // UUID of the service (without dashes [-])
characteristics: [
{
uuid: '48ee0001bf49460ca3d77ec7a512a4ce', // UUID of the characteristic
properties: ['read'], // Supported properties on the characteristic
secure: [], // Which of the supported properties are secured
descriptors: [], // Descriptors on this characteristic
value: Buffer.from('test', 'utf-8') // The (constant) data that is returned for this characteristic
},
{
uuid: '48ee0002bf49460ca3d77ec7a512a4ce',
properties: ['read'],
secure: [],
descriptors: [],
onRead: async (offset) => {
// This function receives the offset at which to start reading
if (offset === 0) {
// Only do your computation when the first bytes are requested.
// In case of subsequent reads (because of long data / small MTU) we want to return the same data as before, starting at the offset
}
// Returns a tuple containing: [error: number, data: Buffer] - Use 0 for the error on success.
return [0, Buffer.from('other', 'utf-8').slice(offset)];
}
},
{
uuid: '48ee0003bf49460ca3d77ec7a512a4cd',
properties: ['write', 'write-without-response'],
secure: [],
descriptors: [],
onWrite: (offset, data, withoutResponse) => {
// This function handles writing data to the characteristic
console.log('writing', offset, data, withoutResponse);
}
}
]
}
];
gatt.setData(deviceName, services);
To advertise services and characteristics use:
const deviceName: string = 'MODblue Testing'; // You can use a different advertising name then the name in the GATT
const advertisedServiceUUIDs: string[] = []; // Optional: Advertise specific service UUIDs (without dashes [-])
await adapter.startAdvertising(deviceName, advertisedServiceUUIDs);
This test will list all available adapters for all available bindings.
-
Run test using
node tests/adapters.js
This test will connect, discover services and characteristics, read a single characteristic value and disconnect. The test runs indefinitely and rotates between all specified devices.
-
Run test using
export BINDINGS="hci"; export LOGGER_IDS="AA:AA:AA:AA:AA:AA|BB:BB:BB:BB:BB:BB"; export SERVICE_ID="48ee0000bf49460ca3d77ec7a512a4cd"; export CHARACTERISTIC_ID = "48ee000bbf49460ca3d77ec7a512a4cd"; node tests/connect.js $BINDINGS $LOGGER_IDS $SERVICE_ID $CHARACTERISTIC_ID
This test will advertise some services and characteristics under a specified name. The test runs indefinitely and waits for connections.
-
Run test using
export BINDINGS="hci"; export ADVERTISE_NAME="MODblue Testing"; node tests/advertise.js $BINDINGS "$ADVERTISE_NAME"