Bluetooth Low Energy for JavaScript
- Usage - How to use Sblendid and interact with Peripherals
- API - Full API description of all functions of this library
- Examples - Further examples
With Sblendid you can find and connect to Bluetooth Low Energy (BLE) devices (called peripherals) and interact with them. It is implemented in TypeScript and built on top of native code using the system's bluetooth APIs.
npm install @sblendid/sblendid @sblendid/adapter-node
Sblendid has not oficially been released yet. You can already install it, but this is a "silent launch" and supports macOS and Windows only thus far. I am currently working on Linux support and will publish version 1.0.0 once I have finished this and the documentation.
Sblendid will support Linux, macOS and Windows. In the future, Sblendid should support other platforms
including React Native and WebBluetooth. Hence, you have to install the @sblendid/adapter-node
package as a seperate dependency. Native modules for macOS and Windows are provided by
Timeular (thanks!)
- If something doesn’t work, please file an issue.
- If you have feature request, please file an issue.
As this is in an early stage, your feedback is very welcome, please don't hesitate to file issues.
With BLE you usually use the GATT protocol. This means you
connect to a peripheral
, get one ore more services
of that peripheral and read
/ write
/ subscribe to
values on these services. With Sblendid this works as follows:
import Sblendid from "@sblendid/sblendid";
(async () => {
const peripheral = await Sblendid.connect("My Peripheral");
const service = await peripheral.getService("uuid");
const value = await service.read("uuid");
await service.write("uuid", Buffer.from("value", "utf8"));
service.on("uuid", value => console.log(value));
// You need to power off your Bluetooth adapter or your Node process won't end
await Sblendid.powerOff();
})();
It is important to know that no function in this libary has a timeout.
Sblendid.connect
will scan indefinitely unless you make sure it doesn't. At some point in the future timeouts will be built in but it is not a scope of version 1.0.0
In the previous example, all values you read, write or get notified for are
Buffers
. It might get weary to constantly convert
Buffers
to the values you actually want to work on. For this, Sblendid introduces a
concept called converters
.
import Sblendid from "@sblendid/sblendid";
// Converts buffers to another value and back. You can
// work with numbers, objects, classes or anything as
// long as you write appropriate decode and encode functions
const converters: {
myValue: {
uuid: "uuid",
decode: buffer => buffer.toString(),
encode: message => Buffer.from(message, "utf8")
},
otherValue: {
uuid: "anotherUuid",
decode: buffer => buffer.readUInt8(0),
encode: num => Buffer.from([num])
}
};
(async () => {
const peripheral = await Sblendid.connect("My Peripheral");
const service = await peripheral.getService("uuid", converters);
// value will be a string
const value = await service.read("myValue");
// you can pass a string
await service.write("myValue", "value");
// value will also be a string
service.on("myValue", value => console.log(value));
// values for "otherValue" will be numbers
const value2 = await service.read("otherValue");
await service.write("otherValue", 22);
service.on("otherValue", value => console.log(value));
// You need to power off your Bluetooth adapter or your Node process won't end
await Sblendid.powerOff();
})();
import Sblendid from "@sblendid/sblendid";
(async () => {
const sblendid = await Sblendid.powerOn();
sblendid.startScanning(peripheral => {
const { uuid, name, connectable, advertisement } = peripheral;
const { txPowerLevel, manufacturerData, serviceUUIDs } = advertisement;
console.log("Found Peripheral:");
console.log(uuid, name, connectable);
console.log(txPowerLevel, manufacturerData, serviceUUIDs);
});
// You need to power off your Bluetooth adapter or your Node process won't end
// Note the lower case "s", powerOff is a static and an instance method
await sblendid.powerOff();
})();
The callback in startScanning
will receive an instance of Peripheral
. See
There are several ways to find and connect to a peripheral.
You can use Sblendid.connect
and pass either a
- Peripheral
Name
- Peripheral
UUID
- Peripheral
Address
- A
callback function
returning a boolean, or a Promise resolving to a boolean
to it to tell Sblendid which peripheral you want to connect to.
Sblendid.connect
will use your criteria to scan your surroundings
and connect to and return the peripheral once it's found.
import Sblendid from "@sblendid/sblendid";
(async () => {
const peripheral = await Sblendid.connect("My Peripheral");
const peripheral = await Sblendid.connect("3A62F159");
const peripheral = await Sblendid.connect("00-14-22-01-23-45");
const peripheral = await Sblendid.connect(peripheral =>
peripheral.name.startsWith("My")
);
const peripheral = await Sblendid.connect(
async peripheral => await checkSomething(peripheral)
);
await Sblendid.powerOff();
})();
The return type of connect
will be an instance of Peripheral
. See
import Sblendid from "@sblendid/sblendid";
(async () => {
const peripheral = await Sblendid.connect("My Peripheral");
const services = await peripheral.getServices();
await Sblendid.powerOff();
})();
The return type of getServices
will be an array of instances of Peripheral
. See
- [peripheral.getServices]
- Service
import Sblendid from "@sblendid/sblendid";
(async () => {
const peripheral = await Sblendid.connect("My Peripheral");
const service = await peripheral.getService("a000");
const characteristics = await service.getCharacteristics();
await Sblendid.powerOff();
})();
Unless you use
converters
, values read will be Buffers
import Sblendid from "@sblendid/sblendid";
const batteryServiceUuid = "180f";
const batteryLevelUuid = "2a19";
(async () => {
const peripheral = await Sblendid.connect(peripheral =>
peripheral.hasService(batteryServiceUuid)
);
const batteryService = await peripheral.getService(batteryServiceUuid);
const batteryLevel = await batteryService.read(batteryLevelUuid);
console.log("Battery Level", batteryLevel.readUInt8(0), "%");
await Sblendid.powerOff();
})();
Unless you use
converters
, values you get from a subscription will be Buffers
import Sblendid from "@sblendid/sblendid";
const batteryServiceUuid = "180f";
const batteryLevelUuid = "2a19";
(async () => {
const peripheral = await Sblendid.connect(peripheral =>
peripheral.hasService(batteryServiceUuid)
);
const batteryService = await peripheral.getService(batteryServiceUuid);
await batteryService.on(batteryLevelUuid, batteryLevel => {
console.log("Battery Level", batteryLevel.readUInt8(0), "%");
});
await Sblendid.powerOff();
})();
Unless you use
converters
, values you pass to a write operation must be Buffers
import Sblendid from "@sblendid/sblendid";
const alertServiceUuid = "1811";
const newAlertUuid = "2a44";
(async () => {
const peripheral = await Sblendid.connect(peripheral =>
peripheral.hasService(alertServiceUuid)
);
const alertService = await peripheral.getService(alertServiceUuid);
await alertService.write(newAlertUuid, Buffer.from("Message", "utf8");
await Sblendid.powerOff();
})();
You can find more examples in the Examples folder:
👉 packages/sblendid/examples
You can run any of these examples by cloning this repository, building the library and calling
yarn example
:git clone [email protected]:LukasBombach/sblendid.git cd sblendid yarn && yarn build cd packages/sblendid yarn example examples/<filename>
Sblendid has 4 main classes
Class | Desciption |
---|---|
Sblendid |
Lets you find and connect to peripherals |
Peripheral |
Lets you connect to peripherals and read their services and RSSIs |
Service |
Lets you read, write and subscribe to updates on values (characteristics) of a service as well as |
Characteristic |
A representation of a single characteristic of a service that lets you read, write and subscribe to updates of a specific value. Usually you will not need to use this class as everything you can do with this on a single characteristic, you can already do with the service class on all available characteristics |
Here you can see the entire public API of the Sblendid
class for an overview. You can find
a more detailed description below.
class Sblendid {
public adapter: Adapter;
constructor() {}
public static async powerOn(): Promise<Sblendid> {}
public static async powerOff(): Promise<Sblendid> {}
public static async connect(condition: Condition): Promise<Peripheral> {}
public async powerOn(): Promise<void> {}
public async powerOff(): Promise<void> {}
public async find(condition: Condition): Promise<Peripheral> {}
public startScanning(listener?: PeripheralListener): void {}
public stopScanning(): void {}
}
adapter
- An instance of the low-level Bluetooth adapter that Sblendid uses, right now this will be an instance of@sblendid/adapter-node
. See packages/adapter-node
Before you can use BLE on your machine you need to turn on your BLE adapter. This static method will turn on the adapter and return an instance of sblendid that you can then use to find and connect to peripherals
import Sblendid from "@sblendid/sblendid";
const sblendid = await Sblendid.powerOn();
sblendid.startScanning();
When you use BLE you need power on your BLE adapter (Your actual hardware). This library will keep the BLE adapter running while your node process runs. When you are done with using BLE you need to power off the BLE adapter again.
If you don't do this, your Node process will keep running after your script is done. I.e. you will see the last line of your code being executed, but your Node script just won't return to your console.
Don't worry, if you forgot to call powerOff, you can just press ctrl + c to stop your script and there will be no hard done whatsoever, It can just be very annoying.
import Sblendid from "@sblendid/sblendid";
const sblendid = await Sblendid.powerOn();
sblendid.startScanning();
await Sblendid.powerOff();
Often times you have a specific peripheral in mind you want to connect to. You would usually
- turn on your BLE adapter
- start scanning
- check each peripheral if it is what you are looking for
- stop scanning
- connect to the peripheral
There is a shortcut for that. You can call Sblendid.connect
to do all that.
Pass either a peripheral name, uuid, address or callback function as an argument
and you will get a connected peripheral as return value once the peripheral has
been found.
The callback function will receive an instance of a Peripheral
that represents any
peripheral found while scanning. The callback function can be sync or async
(i.e. return a Promise) and must always return aĂłr resolve to a Boolean signaling
of the given peripheral is the one you were looking for. If true, Sblendid.connect
will stop scanning, connect to that peripheral and return it.
import Sblendid from "@sblendid/sblendid";
// By Name
const peripheral = await Sblendid.connect("My Peripheral");
// By UUID
const peripheral = await Sblendid.connect("3A62F159");
// By Address
const peripheral = await Sblendid.connect("00-14-22-01-23-45");
// With a callback
const peripheral = await Sblendid.connect(periperal =>
Boolean(periperal.connectable)
);
// With an async callback
const peripheral = await Sblendid.connect(
async periperal => await isPeripheralIAmLookingFor(periperal)
);
You can instantiate Sblendid like any other class. It has no parameters. You will have to turn it on before using it though.
import Sblendid from "@sblendid/sblendid";
const sblendid = new Sblendid();
If you instantiate Sblendid in your own, this method lets you turn on your BLE adapter. It returns a promise that resolves (with no value) when the adater ist powered on.
Note that you can also use const sblendid = await Sblendid.powerOn();
to achieve the
same thing.
import Sblendid from "@sblendid/sblendid";
const sblendid = new Sblendid();
await sblendid.powerOn();
The powerOff
method is available as a class method as well as an instance method.
They do exactly the same thing. It is only there for convencience in sutations where
you have an instance but not the class. Please refer to the documentation of
Sblendid.powerOff
to read about it.
import Sblendid from "@sblendid/sblendid";
const sblendid = await Sblendid.powerOn();
sblendid.startScanning();
await sblendid.powerOff();
Will scan for a peripheral and return it once it's found. Unlike Sblendid.connect
find
will not automatically connect to the peripheral. find
will accept the same
parameters as Sblendid.connect
to find a a peripheral.
Note that this is an instance method, so you will have to instantiate and turn on Sblendid first.
import Sblendid from "@sblendid/sblendid";
const sblendid = await Sblendid.powerOn();
const peripheral = await sblendid.find("My Peripheral");
Will start scanning for peripherals. Instead of finding a specific peripheral
and returning it, this method will just scan your surroundings and call a
callback for every peripheral it finds. It will do so indefinitely until you
call sblendid.stopScanning()
. This method has no return value and is not
asynchronous.
The callback function will receive a single argument which is an instance of
Peripheral
.
Note that when you call
sblendid.startScanning
multiple times with different listeners only the last listener will be used and all others will be discarded.
import Sblendid from "@sblendid/sblendid";
function listener(peripheral) {
console.log("Found peripheral with uuid", peripheral.uuid);
}
const sblendid = await Sblendid.powerOn();
sblendid.startScanning(listener);
Will tell sblendid to stop scanning. The listener you may have provided in
sblendid.startScanning
will be discarded and not be called anymore.
import Sblendid from "@sblendid/sblendid";
function listener(peripheral) {
console.log("Found peripheral with uuid", peripheral.uuid);
}
const sblendid = await Sblendid.powerOn();
sblendid.startScanning(listener);
await new Promise(resolve => setTimeout(resolve, 1000));
sblendid.stopScanning();
Here you can see the entire public API of the Peripheral
class for an overview. You can find
a more detailed description below.
class Peripheral {
public uuid: PUUID;
public adapter: Adapter;
public name: string;
public address: string;
public addressType: AddressType;
public advertisement: Advertisement;
public connectable?: boolean;
public state: State;
constructor(uuid: PUUID, adapter: Adapter, options: Options) {}
public async connect(): Promise<void> {}
public async disconnect(): Promise<void> {}
public async getService(uuid: SUUID, converters?: Converters): Promise<Service<Converters> | undefined> {}
public async getServices(serviceConverters?: ServiceConverters): Promise<Service<any>[]> {}
public async hasService(uuid: SUUID): Promise<boolean> {}
public async getRssi(): Promise<number> {}
public isConnected(): boolean {}
}
uuid
- The UUID of the peripheraladapter
- An instance of the low-level Bluetooth adapter that Sblendid uses, right now this will be an instance of@sblendid/adapter-node
. See packages/adapter-nodename
- ThelocalName
of the peripheral. This is the same asperipheral.advertisement.localName
and will be an empty string if localName on the advertisement is not availableaddress
- The address of the peripheraladdressType
- The address type of the peripheral, can either bepublic
,random
orunknown
advertisement
- An object with the advertisement information of a peripheral. See packages/adapter-node/src/peripheral.tsconnectable
- Whether or not the peripheral can be connected to. Important This is an info the peripheral advertises itself with. Sometimes, even thought it says it's not connectable, it actually is.state
- The connection state of the peripheral, can either beconnecting
,connected
,disconnecting
ordisconnected
Creates a new Peripheral instance
import { Peripheral } from "@sblendid/sblendid";
import Adapter from "@sblendid/adapter-node";
const options = {
address: "00-14-22-01-23-45",
addressType: "public",
advertisement: {
localName: "My Peripheral",
txPowerLevel: 10,
serviceUUIDs: ["f055e3", "143cba", "e72a1e"],
manufacturerData: Buffer.from([]),
serviceData: [
{
uuid: "43f9d8",
data: Buffer.from([])
}
]
},
connectable: true
};
const adapter = new Adapter();
const peripheral = new Peripheral("3A62F159", adapter, options);
However, you would usually create a Peripheral instance by
getting it from the Sblendid
class using connect
, find
or startScanning
import Sblendid from "@sblendid/sblendid";
const peripheral = Sblendid.connect();
const peripheral = new Sblendid().find();
new Sblendid().startScanning(peripheral => {});
To bring this API documentation close to how most people would use this library I will receive my
Periperal
instance from theSblendid
class instead of the constructor in the following examples
Here you can see the entire public API of the Service
class for an overview. You can find
a more detailed description below.
class Service {
public uuid: SUUID;
public peripheral: Peripheral;
public async read(name: Name): Promise<Value> {}
public async write( name: Name, value: Value, withoutResponse?: boolean): Promise<void> {}
public async on(name: Name, listener: Listener): Promise<void> {}
public async off(name: Name, listener: Listener): Promise<void> {}
public async getCharacteristic(name: Name): Promise<Characteristic> {}
public async getCharacteristics(): Promise<Characteristic[]> {}
}
Here you can see the entire public API of the Characteristic
class for an overview. You can find
a more detailed description below.
class Characteristic {
public uuid: CUUID;
public service: Service;
public properties: Properties;
public async read(): Promise<Value> {}
public async write(value: Value, withoutResponse?: boolean): Promise<void> {}
public async on(event: "notify", listener: Listener): Promise<void> {}
public async off(event: "notify", listener: Listener): Promise<void> {}
}
MIT