Getting started with Bluetooth BLE and Node

published on 13 February 2022

This post guides you through the process of reading the battery level from your phone using Bluetooth and the Node library Noble. Reading a battery level is the perfect 'hello world' example for BLE. It's a small step that will give you the fundamentals you need to build bigger and better projects.

Out of all the BLE libraries available, Noble is special because it supports Mac, Linux, and Windows.

Searching for details about the Noble library can be tough. Use the Abandonware version of the library found here. The original maintainers abandoned the repository. The kind folks at Abandonware have kept it up to date.

Wavecake makes this easier

By the way, now's a good time to mention, completing the same experiment in Wavecake takes 17 seconds. Click here to try it out for yourself. Or continue on through the rest of the tutorial and check it out later!

Installation

npm install @abandonware/noble

Preparing the phone

Now we need to prepare the battery service. The preparation will be different for different phones. I'll be setting this up on an iPhone. I recommend using the nRF Connect app. You can find it for Android and iPhone in the app stores. 

First, navigate to the Peripheral tab. Then, add an advertiser.

IMG_4837-ifnlw

Next, add the battery service.

IMG_4838_small-ka2ah

The peripheral tab should look like this:

IMG_4839_small-fwy4z

Last, select the switch to enable advertising the battery service.

IMG_4840_small-eoj6o

Reading the battery level

The readme documentation provides a straightforward example of how to read a device's battery level.

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

The output:

f0-c3-71-2c-e7-5b (Battery Service): 62%

A further step

That was almost too easy. We set up our phone to advertise it's battery service. That made it work well with this example. Advertising space is precious though. Bluetooth limits the amount of information that can be stored in the advertising packet. It would be more practical to search for a device by name, or by a proprietary service UUID. To keep with the example in the previous Python guide, this next step will read the battery level of a device after finding it by name.

First, return to nRF Connect and give the service a more unique advertising name.

IMG_4933-7l66j

Now, alter the event call back to accept all devices. Next, modify the discover event handler to manually filter the devices by name. When the name matches, report the battery level and quit.

const noble = require('@abandonware/noble');

var device_name = 'awesomecoolphone'

noble.on('stateChange', async (state) => {
  if (state === 'poweredOn') {
    await noble.startScanningAsync();
  }
});

noble.on('discover', async (peripheral) => {
  if(peripheral.advertisement.localName === device_name) {
    console.log(peripheral)
    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);
  }
});

Conclusion

A straightforward library for sure. I especially appreciate the function discoverSomeServicesAndCharacteristicsAsync. Typically, this command gets broken into two pieces. First, discover a service, then discover a characteristic. Since most BLE processes invoke both commands back-to-back, it makes sense to combine them into a single function call. Super useful!

About Wavecake

Completing the same experiment in Wavecake takes less than 1 minute. Sign up on our home page to get an invitation to the private beta.

Read more