Skip to content

LukasBombach/sblendid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sblendid

Bluetooth Low Energy for JavaScript

separator

  • 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.

Build Status Code Climate Coverage Code Climate Maintainability MIT License

Install

npm install @sblendid/sblendid @sblendid/adapter-node
Important development notice

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!)

As this is in an early stage, your feedback is very welcome, please don't hesitate to file issues.

Basic workflow

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();
})();
Timeouts

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

Converters

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();
})();

Scan for Peripherals around you

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

Connect to a Peripheral

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

Get all Services from a Peripheral

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

Get all Characteristics from a 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();
})();

Read a Characteristic

Read Values

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();
})();

Subscribe to a Characteristic

Subscribe Values

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();
})();

Write to a Characteristic

Write Values

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();
})();

More examples

You can find more examples in the Examples folder:

👉 packages/sblendid/examples

Running Examples

You can run any of these examples by cloning this repository, building the library and calling yarn example:

git clone git@github.com:LukasBombach/sblendid.git
cd sblendid
yarn && yarn build
cd packages/sblendid
yarn example examples/<filename>

API

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

Sblendid

API Overview

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 {}
}

Properties

  • 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

static async powerOn(): Promise<Sblendid>

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();

static async powerOff(): Promise<void>

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();

static async connect(condition: Condition): Promise<Peripheral>

Often times you have a specific peripheral in mind you want to connect to. You would usually

  1. turn on your BLE adapter
  2. start scanning
  3. check each peripheral if it is what you are looking for
  4. stop scanning
  5. 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)
);

new Sblendid() (Constructor)

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();

async powerOn(): Promise<void>

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();

async powerOff(): Promise<void>

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();

async find(condition: Condition): Promise<Peripheral>

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");

startScanning(listener?: PeripheralListener): void

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);

stopScanning(): void

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();

Peripheral

API Overview

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 {}
}

Properties

  • uuid - The UUID of the peripheral
  • 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
  • name - The localName of the peripheral. This is the same as peripheral.advertisement.localName and will be an empty string if localName on the advertisement is not available
  • address - The address of the peripheral
  • addressType - The address type of the peripheral, can either be public, random or unknown
  • advertisement - An object with the advertisement information of a peripheral. See packages/adapter-node/src/peripheral.ts
  • connectable - 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 be connecting, connected, disconnecting or disconnected

new Peripheral(uuid: PUUID, adapter: Adapter, options: Options) (Constructor)

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 the Sblendid class instead of the constructor in the following examples

Service

API Overview

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[]> {}
}

Characteristic

API Overview

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> {}
}

License

MIT

About

A JavaScript Bluetooth Low Energy (BLE) Library

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published