© Peter Hoddie and Lizzie Prader 2020
P. Hoddie, L. PraderIoT Development for ESP32 and ESP8266 with JavaScripthttps://doi.org/10.1007/978-1-4842-5070-9_4

4. Bluetooth Low Energy (BLE)

Peter Hoddie1  and Lizzie Prader1
(1)
Menlo Park, CA, USA
 

There are many ways to enable wireless communication between devices. Chapter 3 introduced many protocols that operate over a Wi-Fi connection to communicate with devices anywhere in the world. This chapter focuses on Bluetooth Low Energy, or BLE, a wireless communication widely used between two devices in close proximity to each other. Products choose to use BLE instead of Wi-Fi if minimizing energy use is particularly important, such as in battery-powered products, and when direct communication with another device, such as a mobile phone, is an acceptable alternative to internet access. Many IoT products use BLE, from heart rate monitors to electric toothbrushes to ovens. Product manufacturers often offer a mobile app or desktop companion application to monitor or control these products.

BLE is version 4 of the Bluetooth standard and was first introduced in 2010. The original Bluetooth was standardized in 2000 to send streams of data over short distances. BLE significantly reduces the energy use of the original Bluetooth, enabling it to operate much longer on a single battery charge. BLE achieves this, in part, by reducing the amount of data transmitted. Transmitting over shorter distances also uses less energy; BLE devices typically have a range of no more than 100 meters, whereas Wi-Fi has a much larger range. BLE’s lower power consumption and cost make it well suited for many IoT products.

Using the information in this chapter, you can build your own BLE devices that run on a microcontroller.

Note

The examples in this chapter are for the ESP32. If you attempt to build them for the ESP8266, the build will fail because the ESP8266 doesn’t have BLE hardware. The examples do, however, run on other devices with integrated BLE supported by the Moddable SDK, including Qualcomm’s QCA4020 and the Blue Gecko by Silicon Labs.

BLE Basics

If you’re new to working with BLE, the information in this section is essential, as it explains concepts used throughout the rest of this chapter. If you’re familiar with BLE, still consider giving this section a quick scan to familiarize yourself with the terminology used in this book and how it relates to the BLE API of the Moddable SDK.

GAP Centrals and Peripherals

The Generic Access Profile , or GAP, defines how devices advertise themselves, how they establish connections with each other, and security. The two main roles defined by GAP are Central and Peripheral .

A Central scans for devices acting as Peripherals and initiates requests to establish a new connection with a Peripheral. A device acting as a Central usually has relatively high processing power and plenty of memory—for example, a smart phone, tablet, or computer—whereas Peripherals are often small and battery-powered. Peripherals advertise themselves and accept requests to establish a connection.

The BLE specification enables a Central to be connected to multiple Peripherals, and a Peripheral to be connected to multiple Centrals. It’s common for a Central to connect to several Peripherals at the same time. For example, you may use your smart phone to connect to your heart rate monitor, smart watch, and lights. It’s uncommon for a Peripheral to connect to more than one Central at a time; most Peripherals do not allow for multiple concurrent connections. The BLE API of the Moddable SDK enables a Peripheral to connect with a single Central at a time.

GATT Clients and Servers

The Generic Attribute Profile, or GATT, defines the way BLE devices transfer data back and forth after a connection is established between them—a client-server relationship.

A GATT Client is a device that accesses data from a remote GATT Server by sending read/write requests. A GATT Server is a device that stores data locally, receives read/write requests, and notifies the remote GATT Client of changes to the values of its characteristics. In this chapter, the term Server is used to mean a GATT Server, and Client means a GATT Client.

GAP vs. GATT

Many BLE tutorials incorrectly use the terms Central and Client interchangeably and the terms Peripheral and Server interchangeably. This is because Centrals usually take on the Client role and Peripherals usually take on the Server role. However, the BLE specification says that either Centrals or Peripherals may take on the role of Client, Server, or both.

Central and Peripheral are terms defined by GAP, telling you how the BLE connection is managed. Client and Server are terms defined by GATT, telling you about the storage and flow of data after a connection has been established. GATT comes into the picture only after the advertising and connection process defined by GAP is complete.

Profiles, Services, and Characteristics

GATT also defines the format of data, with a hierarchy of profiles, services, and characteristics. As illustrated in Figure 4-1, the top level of the hierarchy is a profile.
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig1_HTML.png
Figure 4-1

GATT profile hierarchy

Profiles

A profile defines a specific use of BLE for communication between multiple devices, including the roles of the devices involved and their general behaviors. For example, the standard Health Thermometer profile defines the roles of a Thermometer device (a sensor) and a Collector device; the Thermometer device measures the temperature, and the Collector device receives the temperature measurement and other data from the Thermometer. The profile specifies the services that the Thermometer must instantiate (the Health Thermometer service and the Device Information service) and states that the intended use of the profile is in healthcare applications.

Profiles don’t exist on a BLE device; rather, they are specifications implemented by a BLE device. The list of officially adopted BLE profiles is available at bluetooth.com/specifications/gatt. When you’re implementing your own BLE device, it’s a good idea to check whether a standard profile is available that meets the needs of your product. If there is, you’ll benefit from interoperability with other products that support the standard, saving you the time of designing a new profile.

Services

A service is a collection of characteristics that describe the behavior of part of a BLE device. For example, the Health Thermometer service provides temperature data from a Thermometer device. A service may have one or more characteristics and is distinguished by a UUID. Officially adopted BLE services have 16-bit UUIDs. The Health Thermometer service is assigned the number 0x1809. You can create your own custom services by giving them a 128-bit UUID.

Services are advertised by a BLE device. The list of officially adopted BLE services is available at bluetooth.com/specifications/gatt/services.

Characteristics

A characteristic is a single value or data point that provides information about a GATT service. The format of the data depends on the characteristic; for example, the Heart Rate Measurement characteristic used by the Heart Rate service provides a heart rate measurement as an integer, and the Device Name characteristic provides the name of a device as a string.

The list of officially adopted BLE characteristics is available at bluetooth.com/specifications/gatt/characteristics. As with services, officially adopted BLE characteristics have 16-bit UUIDs and you can create your own with a 128-bit UUID.

The BLE API of the Moddable SDK

The Moddable SDK does not have distinct classes in its BLE API for the roles defined by GAP and for GATT. Instead, it provides functions for Centrals and GATT Clients in a single BLEClient class, and functions for Peripherals and GATT Servers in a single BLEServer class. This class organization reflects the two most common configurations of BLE devices: devices that act as Centrals and take on the Client role and devices that act as Peripherals and take on the Server role.

The BLEClient Class

The BLEClient class provides functions you use to create Centrals and GATT Clients. The functions for Centrals perform the following operations:
  1. 1.

    Scan for Peripherals.

     
  2. 2.

    Initiate requests to establish a connection with Peripherals.

     
The functions for GATT Clients perform these operations:
  1. 1.

    Find the GATT services of interest.

     
  2. 2.

    Find characteristics of interest in each service.

     
  3. 3.

    Read, write, and enable notifications for characteristics within each service.

     

You subclass the BLEClient class to implement a specific BLE device that supports the operations your device requires. Subclasses call methods of the BLEClient class to initiate the preceding operations. All BLE operations performed by BLEClient are asynchronous to avoid blocking execution for an indeterminate period of time. Consequently, instances of the BLEClient class receive results through callbacks. For example, the BLEClient class has a startScanning method that you call to start scanning for Peripherals, and an onDiscovered callback that’s automatically invoked when a Peripheral is discovered.

You only need to implement the callbacks required to work with the Peripherals, services, and characteristics your device requires.

The BLEServer Class

The BLEServer class provides functions you use to create Peripherals and GATT Servers. The functions for Peripherals perform the following operations:
  1. 1.

    Advertise so that Centrals can discover the Peripheral.

     
  2. 2.

    Accept connection requests from a Central.

     
The functions for GATT Servers perform these operations:
  1. 1.

    Deploy services.

     
  2. 2.

    Respond to characteristic read and write requests from a Client.

     
  3. 3.

    Accept characteristic value change notification requests from a Client.

     
  4. 4.

    Notify a Client of characteristic value changes.

     

You can implement standard BLE profiles like Heart Rate or your own custom-defined profile to support your product’s unique capabilities. In both cases, you first define GATT services in JSON files and then subclass the BLEServer class to implement specific BLE devices. Subclasses call methods of the BLEServer class to initiate the preceding operations. All BLE operations performed by BLEServer are asynchronous to avoid blocking execution for an indeterminate period of time. Consequently, instances of the BLEServer class receive results through callbacks.

Installing the BLE Host

The examples in this chapter are installed using the pattern described in Chapter 1: you install the host on your device using mcconfig, then install example applications using mcrun.

The host is in the $EXAMPLES/ch4-ble/host directory. Navigate to this directory from the command line and install it with mcconfig.

Creating a BLE Scanner

The $EXAMPLES/ch4-ble/scanner example implements a Central that scans for nearby Peripherals and traces their names to the console. It’s implemented using the BLEClient class. Listing 4-1 shows most of the source code for this example.
class Scanner extends BLEClient {
    onReady() {
        this.startScanning();
    }
    onDiscovered(device) {
        let scanResponse = device.scanResponse;
        let completeName = scanResponse.completeName;
        if (completeName)
            trace(`${completeName} `);
    }
}

Listing 4-1.

The Scanner class implements two BLEClient callbacks:
  • The onReady callback is invoked when the BLE stack is ready to use. In this example, the onReady callback calls startScanning to enable scanning for nearby Peripherals.

  • The onDiscovered callback is invoked one or more times for each Peripheral discovered. In this example, the onDiscovered callback traces the discovered Peripheral’s name to the console.

With this simple example, your Central discovers Peripherals around you and tells you their names. Now you’re ready to go a step further: the next example demonstrates how to use other features of the BLEClient class to create a BLE device that communicates with a virtual Peripheral.

Creating Two-Way Communication

The $EXAMPLES/ch4-ble/text-client example implements a Central that connects to a Peripheral and receives text data via characteristic value change notifications.

To see the example working, you’ll need a Peripheral that provides the text data characteristic. You can create one using Bluefruit, a mobile app available for free on iOS and Android devices. To create the Peripheral, take the following steps, illustrated in Figures 4-2 and 4-3:
  1. 1.

    Download and open Bluefruit and enter Peripheral mode. In the ADVERTISING INFO section, change the Local Name field to esp.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig2_HTML.jpg
Figure 4-2

Peripheral mode in Bluefruit

  1. 2.

    Make sure the UART service is turned on.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig3_HTML.jpg
Figure 4-3

UART service enabled

The next sections explain the code that runs on the ESP32 to implement the Central device.

Connecting to the Peripheral

The constants at the top of the application correspond to the device name you set in Bluefruit and the service and characteristic UUID used by the UART service:
const PERIPHERAL_NAME = 'esp';
const SERVICE_UUID = uuid`6E400001B5A3F393E0A9E50E24DCCA9E`;
const CHARACTERISTIC_UUID = uuid`6E400003B5A3F393E0A9E50E24DCCA9E`;
Like the scanner example, this example implements the onReady and onDiscovered callbacks, as shown in Listing 4-2. But instead of just tracing the names of devices to the console, this example checks the name of each discovered Peripheral to see if it matches the PERIPHERAL_NAME constant. If it does, it stops scanning for Peripherals and calls the connect method, which initiates a connection request between the BLEClient and a target Peripheral device.
class TextClient extends BLEClient {
    onReady() {
        this.startScanning();
    }
    onDiscovered(device) {
        if (PERIPHERAL_NAME ===
                device.scanResponse.completeName) {
            this.stopScanning();
            this.connect(device);
        }
    }
    ...

Listing 4-2.

The argument to connect is an instance of the Device class, representing a single Peripheral. BLEClient automatically creates instances of the Device class when a Peripheral is discovered; applications do not instantiate them directly. Applications do, however, interact with instances of the Device class directly—for example, by calling methods to perform GATT service and characteristic discovery.

The onConnected Callback

The onConnected method is a callback that’s invoked when the Central connects to a Peripheral. This example calls the device object’s discoverPrimaryService method to obtain the primary GATT service from the Peripheral. The argument to discoverPrimaryService is the UUID of the service to discover.
onConnected(device) {
    device.discoverPrimaryService(SERVICE_UUID);
}
You can discover all of the primary services of a Peripheral using the discoverAllPrimaryServices method. For example, the onConnected callback could instead be written as follows:
onConnected(device) {
    device.discoverAllPrimaryServices();
}

The onServices Callback

The onServices method is a callback that’s invoked when service discovery is complete. The services argument is an array of service objects—instances of the Service class—each of which provides access to a single service. If discoverPrimaryService was called to find a single service, the services array contains only the one service found.

As shown in Listing 4-3, this example checks to see if the Peripheral provides a service with the UUID that matches the one defined by the SERVICE_UUID constant. If it does, it calls the service object’s discoverCharacteristic method to look for the service characteristic with the UUID that matches the one defined by the CHARACTERISTIC_UUID constant.
onServices(services) {
    let service = services.find(service =>
            service.uuid.equals(SERVICE_UUID));
    if (service) {
        trace(`Found service `);
        service.discoverCharacteristic(CHARACTERISTIC_UUID);
    }
    else
        trace(`Service not found `);
}

Listing 4-3.

You can discover all of the service characteristics using the discoverAllCharacteristics method. For example, the onServices callback could replace the line that calls discoverCharacteristic with the following line:
service.discoverAllCharacteristics();

The onCharacteristics Callback

The onCharacteristics method is a callback that’s invoked when characteristic discovery is complete. The characteristics argument is an array of characteristic objects—instances of the Characteristic class—each of which provides access to a single service characteristic. If discoverCharacteristic was called to find a single characteristic, the characteristics array contains the single characteristic found.

When the desired characteristic is found, the example calls the characteristic object’s enableNotifications method to enable notifications when the characteristic’s value changes, as shown in Listing 4-4.
onCharacteristics(characteristics) {
    let characteristic = characteristics.find(characteristic =>              characteristic.uuid.equals(CHARACTERISTIC_UUID));
    if (characteristic) {
        trace(`Enabling notifications `);
        characteristic.enableNotifications();
    }
    else
        trace(`Characteristic not found `);
}

Listing 4-4.

If you set up your Peripheral correctly, you’ll see the following messages in the debug console when you run the text-client application:
Found service
Enabling notifications

Receiving Notifications

After notifications are enabled, you can send notifications to the Client by changing the value of the Peripheral’s characteristic from your smart phone. To change the value, tap the UART button. This will take you to the screen shown in Figure 4-4. Enter text in the input field at the bottom of the screen and tap Send to update the characteristic value.
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig4_HTML.jpg
Figure 4-4

UART screen in Bluefruit

The characteristic value is delivered to the Client through the onCharacteristicNotification callback in an ArrayBuffer. This example assumes the value is a string, so it converts the ArrayBuffer to a string using String.fromArrayBuffer (a feature of XS to make it easier for applications to work with binary data). There is a parallel ArrayBuffer.fromString. These are not part of the JavaScript language standard.
onCharacteristicNotification(characteristic, buffer) {
    trace(String.fromArrayBuffer(buffer)+" ");
}

Creating a Heart Rate Monitor

Now that you know the basics of implementing a Client that receives notifications from a Server, this example will show you how to use features of the BLEServer class to implement a Peripheral that takes on the Server role after connecting to a Central.

The $EXAMPLES/ch4-ble/hrm example advertises the standard Heart Rate and Battery services , accepts connection requests from Centrals, sends notifications for simulated heart rate values, and responds to read requests from a Client for a simulated battery level. The next few sections explain how it’s implemented using the BLEServer class.

Defining and Deploying Services

GATT services are defined in JSON files located in the host’s bleservices directory. The JSON is automatically converted to platform-specific native code at build time, and the compiled object code is linked to the application.

Each GATT service is defined in its own JSON file. Listing 4-5 shows the standard Heart Rate service.
{
    "service": {
        "uuid": "180D",
        "characteristics": {
            "bpm": {
                "uuid": "2A37",
                "maxBytes": 2,
                "type": "Array",
                "permissions": "read",
                "properties": "notify"
            }
        }
    }
}

Listing 4-5.

Here are explanations of some of the important properties:
  • The uuid property of the service object is the number assigned to the service by the GATT specification. The Heart Rate service has the UUID 180F.

  • The characteristics object describes each characteristic supported by the service. Each immediate property is the name of a characteristic. In this example there is just one characteristic: bpm, which stands for beats per minute.

  • The uuid property of a characteristic object is the unique number assigned to the characteristic by the GATT specification. The bpm characteristic of the Heart Rate service has the UUID 2A37.

  • The type property specifies the type of the characteristic value used in your JavaScript code. The BLEServer class uses the value of the type property to convert the binary data transferred by the Client to JavaScript types. This saves your Server code the work of converting back and forth between different types of data (ArrayBuffer, String, Number, and so on).

  • The permissions property defines whether the characteristic is read-only, write-only, or read/write and whether accessing the characteristic requires an encrypted connection. The bpm property is read-only because in a heart rate monitor, the beats per minute is determined by sensor readings and consequently cannot be written by a Client. The read permission indicates that a Client can read the characteristic over a nonencrypted or encrypted connection; use readEncrypted when the value is only accessible over an encrypted connection. Similarly, use write or writeEncrypted for write permissions. To indicate that a characteristic supports both reading and writing, include both a read and a write value in the permissions string, separated by a comma—for example, "readEncrypted,writeEncrypted".

  • The properties property defines the characteristic’s properties. It may be read, write, notify, indicate, or a combination of those (comma-separated). The read and write values permit reads and writes of the characteristic value, notify permits the Server to notify the Client of changes to the characteristic value without its being requested and without acknowledgment that the notification was received, and indicate is the same as notify except that it requires acknowledgment that the notification was received before another indication can be sent.

Once the BLE stack completes its initialization, it invokes the onReady callback. The hrm example implementation of onReady initiates advertising, enabling its services to be discovered by Clients. The following section explains how the subclass manages when advertising is active.

Advertising

Peripherals broadcast advertisement data to announce themselves. The BLEServer class has a startAdvertising method to begin broadcasting advertisement packets and a stopAdvertising method to stop.

The hrm example calls startAdvertising when the BLE stack is ready to use and also when the connection to a Central is lost. When startAdvertising is called, the Peripheral broadcasts its advertising data type flags value, its name, and its services (Heart Rate and Battery), as shown in Listing 4-6. The UUIDs for the Heart Rate and Battery services come from the GATT specification.
this.startAdvertising({
    advertisingData: {
        flags: 6,
        completeName: this.deviceName,
        completeUUID16List: [uuid`180D`, uuid`180F`]
    }
});

Listing 4-6.

When a connection is successfully established to a Central, the Peripheral stops sending advertising packets, as it supports only a single connection at a time:
onConnected() {
    this.stopAdvertising();
}

When the connection is lost, the Peripheral again starts advertising.

A BLE advertisement may contain additional data—for example, to implement a BLE beacon. BLE beacons advertise data for many Centrals to see without connecting to them. The code in Listing 4-7 is from the examples/network/ble/uri-beacon example in the Moddable SDK, which implements a UriBeacon that advertises the Moddable website. The UUID here comes from the Assigned Numbers specification (see bluetooth.com/specifications/assigned-numbers/16-bit-uuids-for-members). The encodeData method encodes the URI in the format specified by the UriBeacon spec. See the uri-beacon example for the source code.
this.startAdvertising({
    advertisingData: {
        completeUUID16List: [uuid`FED8`],
        serviceDataUUID16: {
            uuid: uuid`FED8`,
            data: this.encodeData("http://www.moddable.com")
        }
    }
});

Listing 4-7.

Advertising data only transmits data; there is no way to reply. Bidirectional communication requires one device to take on the GATT Client role and one device to take on the GATT Server role. Before this can happen, the connection process defined by GAP must be complete.

Establishing a Connection

Once the heart rate Peripheral starts advertising, it waits for a Central to request to connect to it. You can use any of a variety of mobile apps to create a Central that does this. In this section, you’ll use LightBlue, which is available for free on iOS and Android devices. LightBlue has a Central mode that enables you to scan for, connect to, and send read/write requests to Peripherals. You can use it as the Client for your Peripheral by taking the following steps:
  1. 1.

    Run the example on your ESP32.

     
  2. 2.

    Download and open LightBlue and wait for the Heart Rate Monitor Peripheral to appear, as shown in Figure 4-5.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig5_HTML.jpg
Figure 4-5

Heart Rate Monitor Peripheral in LightBlue Peripherals list

  1. 3.

    Tap the Heart Rate Monitor Peripheral to establish a connection to it.

     
The onConnected callback is invoked when a Central connects to the heart rate Peripheral. In this example, it stops broadcasting advertisement and responding to scan response packets, as shown in Listing 4-8.
class HeartRateService extends BLEServer {
    ...
    onConnected() {
        this.stopAdvertising();
    }
    ...
}

Listing 4-8.

Sending Notifications

BLE Clients may request notifications for characteristics that have the notify property, such as the bpm characteristic in this example. When notifications are enabled, Servers notify the Client of changes to the characteristic’s value without the Server’s having to request the value. Notifications save energy, a key feature of the design of BLE, by eliminating the need for the Client to poll the Server for changes in characteristics.

To receive notifications for the simulated heart rate in LightBlue, take the following steps (as illustrated in Figures 4-6, 4-7, and 4-8):
  1. 1.

    Tap the Heart Rate Measurement characteristic.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig6_HTML.jpg
Figure 4-6

Heart Rate Monitor characteristics screen with Heart Rate Measurement button

  1. 2.

    Tap Listen for notifications to enable notifications.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig7_HTML.jpg
Figure 4-7

Heart Rate Measurement screen with Listen for notifications button

  1. 3.

    Watch the simulated heart rate values appear.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig8_HTML.jpg
Figure 4-8

Heart rate values appearing in NOTIFIED VALUES section

Now let’s take a look at the code that implements notifications for the Heart Rate service on the Server side. The onCharacteristicNotifyEnabled method is a callback that’s invoked when a Client enables notifications on a characteristic. The onCharacteristicNotifyDisabled method is a callback that’s invoked when a Client disables notifications on a characteristic. The characteristic argument for both is an instance of the Characteristic class, which provides access to a single service characteristic.

The onCharacteristicNotifyEnabled method (shown in Listing 4-9) calls the notifyValue method, which sends a characteristic value change notification to the connected Client, at 1,000-millisecond (1-second) intervals. This simulates a heart rate sensor, although a real heart rate monitor would not send periodic updates; rather, it would send a notification when the value actually changes.
onCharacteristicNotifyEnabled(characteristic) {
    this.bump = +1;
    this.timer = Timer.repeat(id => {
        this.notifyValue(characteristic, this.bpm);
        this.bpm[1] += this.bump;
        if (this.bpm[1] === 65) {
            this.bump = -1;
            this.bpm[1] = 64;
        }
        else if (this.bpm[1] === 55) {
            this.bump = +1;
            this.bpm[1] = 56;
        }
    }, 1000);
}

Listing 4-9.

The onCharacteristicNotifyDisabled method (Listing 4-10) ends the sending of notifications by calling the stopMeasurements method.
onCharacteristicNotifyDisabled(characteristic) {
    this.stopMeasurements();
}
...
stopMeasurements() {
    if (this.timer) {
        Timer.clear(this.timer);
        delete this.timer;
    }
    this.bpm = [0, 60]; // flags, beats per minute
}

Listing 4-10.

Responding to Read Requests

Clients may request the value of characteristics that support the read property, like the Battery service in this example. To send requests to read the value of the simulated battery level in LightBlue, take the following steps (as illustrated in Figures 4-9, 4-10, and 4-11):
  1. 1.

    Tap the Battery Level characteristic.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig9_HTML.jpg
Figure 4-9

Heart Rate Monitor characteristics screen with Battery Level button

  1. 2.

    Tap Read again.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig10_HTML.jpg
Figure 4-10

Battery Level screen with Read again button

  1. 3.

    Watch the simulated battery level appear.

     
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig11_HTML.jpg
Figure 4-11

Battery level value appearing in READ VALUES section

Now let’s take a look at the code that handles the battery level service notifications. The onCharacteristicRead method (Listing 4-11) is a callback that’s invoked when a Client reads a service characteristic value on demand. The BLEServer instance is responsible for handling the read request. In this example, the battery level starts at 100; each time it’s read, the callback returns the value and decrements it by 1.
onCharacteristicRead(params) {
    if (params.name === "battery") {
        if (this.battery === 0)
            this.battery = 100;
            return this.battery--;
    }
}

Listing 4-11.

Establishing Secure Communication

The Moddable SDK supports enhanced security features introduced in version 4.2 of the Bluetooth Core Specification: LE Secure Connections with Numeric Comparison, Passkey Entry, and Just Works pairing methods. Both the BLEClient and BLEServer classes have an optional securityParameters property that requests that devices establish an LE Secure Connection. The pairing method used depends on the devices’ capabilities and options set in the securityParameters property. Security callback functions are hosted by the BLEClient and BLEServer classes. The next section walks through a simple example.

Secure Heart Rate Monitor

The $EXAMPLES/ch4-ble/hrm-secure example is a secure version of the $EXAMPLES/ch4-ble/hrm example that requires passkey entry for pairing.

Again, you can use LightBlue as the Client. Follow the same steps as before, and when you’re prompted to enter the code from the heart rate monitor (as shown in Figure 4-12), enter the passkey traced to the console in xsbug (Figure 4-13).
../images/474664_1_En_4_Chapter/474664_1_En_4_Fig12_HTML.jpg
Figure 4-12

Prompt to enter code in LightBlue

../images/474664_1_En_4_Chapter/474664_1_En_4_Fig13_HTML.jpg
Figure 4-13

Passkey in debug console

Now you can enable notifications for the heart rate value and read the battery value on demand as before, but the connection between the Server and Client is secure.

The code has just a few differences from the $EXAMPLES/ch4-ble/hrm example. As shown in Listing 4-12, the onReady callback includes additional code to configure the device security requirements and I/O capabilities of the Peripheral.
this.securityParameters = {
    bonding: true,
    mitm: true,
    ioCapability: IOCapability.DisplayOnly
};

Listing 4-12.

The properties in this code specify the following:
  • The bonding property enables bonding, which means that both devices will store and use the keys they exchange the next time they connect. Without bonding enabled, the devices will have to go through pairing every time they connect.

  • The mitm property requests man-in-the-middle protection, meaning that data exchanged between two paired devices is encrypted to prevent an untrusted device from eavesdropping.

  • The ioCapability property indicates the user interface capabilities for the device related to confirming the passkey. This device doesn’t have a display, but it has display capability because it can trace to the debug console. Other Peripherals may have more input/output capabilities (for example, a device with a keyboard) or fewer input/output capabilities (such as a device with no way to display text). The ioCapability properties of both devices are used to determine the pairing method. For example, if neither device has a keyboard or display, the Just Works pairing method is used.

Two additional callbacks of the BLEServer class are implemented (see Listing 4-13):
  • The onPasskeyDisplay callback is invoked when you try to establish a connection to the Peripheral. In this case, it’s called when you tap the name of the Peripheral in LightBlue. As you saw before, this example traces the passkey to the debug console.

  • The onAuthenticated callback is invoked after successful device pairing. This example simply changes the authenticated property to indicate that a secure connection was established.

onPasskeyDisplay(params) {
    let passkey = this.passkeyToString(params.passkey);
    trace(`server display passkey: ${passkey} `);
}
onAuthenticated() {
    this.authenticated = true;
}

Listing 4-13.

The Server checks that the authenticated property is set when the Client enables notifications. The code inside the if block looks the same as the onCharacteristicNotifyEnabled method of the hrm example.
onCharacteristicNotifyEnabled(characteristic) {
    if (this.authenticated) {
        ...
}
The Server also defines an additional helper method, named passkeyToString. Passkey values are integers and must always include six digits when displayed to the user. This method pads the passkey with leading zeros, when necessary, for display.
passkeyToString(passkey) {
    return passkey.toString().padStart(6, "0");
}

Conclusion

Now that you understand the essentials of these examples, there’s a lot you can do with BLE on your ESP32. Instead of connecting to virtual Peripherals that you create in LightBlue, you can connect to BLE products in your home. Instead of sending simulated data like the heart rate monitor example, you can send actual sensor data from your favorite off-the-shelf sensors.

If you want to try more BLE examples, see the examples/network/ble directory in the Moddable SDK on GitHub. Examples that enable your device to become a URI-transmitting beacon, pair your device with the iPhone Music app, and more are available. If you want to learn more about the BLE API of the Moddable SDK, see the documentation/network/ble directory.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.25.74