四、蓝牙低能耗(BLE)

有许多方法可以实现设备之间的无线通信。第 3 章介绍了许多通过 Wi-Fi 连接与世界上任何地方的设备通信的协议。本章重点介绍蓝牙低能耗BLE ,这是一种广泛应用于两个相互靠近的设备之间的无线通信。如果最大限度地减少能源使用特别重要,例如在电池供电的产品中,以及当与另一设备(如手机)直接通信是互联网接入的可接受替代方案时,产品会选择使用 BLE 而不是 Wi-Fi。从心率监测器到电动牙刷到烤箱,许多物联网产品都使用 BLE。产品制造商通常提供移动应用程序或桌面配套应用程序来监控或控制这些产品。

BLE 是蓝牙标准的第 4 版,于 2010 年首次推出。最初的蓝牙是在 2000 年标准化的,用于短距离发送数据流。BLE 显著降低了原始蓝牙的能耗,使其在单次充电后能够工作更长时间。BLE 部分通过减少传输的数据量来实现这一点。传输距离越短,消耗的能量也越少;BLE 设备的覆盖范围通常不超过 100 米,而 Wi-Fi 的覆盖范围要大得多。BLE 的低功耗和低成本使其非常适合许多物联网产品。

使用本章中的信息,你可以构建自己的运行在微控制器上的 BLE 设备。

Note

本章中的示例适用于 ESP32。如果您尝试为 ESP8266 构建它们,构建将会失败,因为 ESP8266 没有 BLE 硬件。然而,这些例子确实可以在其他集成了可修改的 SDK 支持的 BLE 的设备上运行,包括高通的 QCA4020 和硅实验室的 Blue Gecko。

BLE 基础知识

如果您是使用 BLE 的新手,本节中的信息是必不可少的,因为它解释了本章其余部分中使用的概念。如果你熟悉 BLE,仍然可以考虑快速浏览一下这一部分,以熟悉本书中使用的术语,以及它与可修改 SDK 的 BLE API 的关系。

gap 中心和外围设备

通用访问配置文件间隙定义了设备如何自我宣传,它们如何彼此建立连接,以及安全性。GAP 定义的两个主要角色是中枢外围

中央扫描充当外围设备的设备,并发起与外围设备建立新连接的请求。充当中央设备的设备通常具有相对较高的处理能力和大量内存,例如智能手机、平板电脑或电脑,而外围设备通常体积较小,使用电池供电。外围设备自我广告并接受建立连接的请求。

BLE 规范允许一个中心连接到多个外围设备,一个外围设备连接到多个中心。中央处理器同时连接到几个外围设备是很常见的。例如,您可以使用智能手机连接到您的心率监测器、智能手表和灯。一个外围设备同时连接到多个中央设备是不常见的;大多数外设不允许多个并发连接。可修改的 SDK 的 BLE API 允许外围设备一次连接一个中央设备。

GATT 客户端和服务器

通用属性配置文件,或 GATT ,定义了 BLE 设备之间建立连接后来回传输数据的方式——客户端-服务器关系。

一个 GATT 客户端是一个通过发送读/写请求从远程 GATT 服务器访问数据的设备。一个 GATT 服务器是一个本地存储数据,接收读/写请求,并通知远程 GATT 客户机其特性值的变化的设备。在本章中,术语服务器用于表示 GATT 服务器,而客户机表示 GATT 客户机。

GAP 与关贸总协定

许多 BLE 教程错误地将术语中央客户端互换使用,并将术语外围设备服务器互换使用。这是因为中央系统通常承担客户端角色,而外围设备通常承担服务器角色。然而,BLE 规范指出,中心或外围设备可以担当客户机、服务器或两者的角色。

中央外围是由 GAP 定义的术语,告诉您 BLE 连接是如何管理的。客户端服务器是 GATT 定义的术语,告诉你连接建立后数据的存储和流动。只有在 GAP 所定义的广告和连接过程完成之后,GATT 才会出现。

概况、服务和特征

关贸总协定还定义了数据的格式,有一个层次的简档、服务、特征。如图 4-1 所示,层级的顶层是一个概要。

img/474664_1_En_4_Fig1_HTML.png

图 4-1

关贸总协定概况层次结构

轮廓

一个配置文件定义了 BLE 在多个设备间通信的特定用途,包括相关设备的角色及其一般行为。例如,标准健康温度计配置文件定义了温度计设备(传感器)和收集器设备的角色;温度计装置测量温度,收集器装置从温度计接收温度测量值和其他数据。该配置文件指定温度计必须实例化的服务(健康温度计服务和设备信息服务),并声明该配置文件的预期用途是在医疗保健应用中。

BLE 设备上不存在配置文件;相反,它们是由 BLE 设备实现的规范。官方采用的 BLE 简介列表可在 bluetooth.com/specifications/gatt 获得。当您实现自己的 BLE 设备时,最好检查是否有满足您产品需求的标准配置文件。如果有的话,您将受益于与支持该标准的其他产品的互操作性,从而节省您设计新概要文件的时间。

服务

一个服务是描述 BLE 设备一部分行为的特征集合。例如,健康温度计服务提供来自温度计设备的温度数据。服务可以具有一个或多个特征,并通过 UUID 来区分。官方采用的 BLE 服务有 16 位 UUIDs。健康温度计服务的编号为0x1809。您可以通过为它们提供 128 位 UUID 来创建自己的自定义服务。

服务由 BLE 设备进行广告。官方采用的 BLE 服务列表可在 bluetooth.com/specifications/gatt/services 获得。

特征

一个特征是一个提供 GATT 服务信息的单个值或数据点。数据的格式取决于特征;例如,心率服务使用的心率测量特征提供整数形式的心率测量值,而设备名称特征提供字符串形式的设备名称。

官方采用的 BLE 特征列表可在 bluetooth.com/specifications/gatt/characteristics 获得。与服务一样,官方采用的 BLE 特征有 16 位 UUID,您可以使用 128 位 UUID 创建自己的 uuid。

可修改的 SDK 的 BLE API

对于 GAP 和 GATT 定义的角色,可修改的 SDK 在其 BLE API 中没有明确的类。相反,它在单个BLEClient类中为 Centrals 和 GATT 客户机提供功能,在单个BLEServer类中为外设和 GATT 服务器提供功能。此类组织反映了 BLE 设备的两种最常见的配置:充当中心并承担客户端角色的设备,以及充当外围设备并承担服务器角色的设备。

BLEClient

BLEClient类提供了用于创建 Centrals 和 GATT 客户端的函数。中心的功能执行以下操作:

  1. 扫描外围设备。

  2. 发起与外围设备建立连接的请求。

GATT 客户端的函数执行这些操作:

  1. 找到感兴趣的关贸总协定服务。

  2. 在每项服务中寻找感兴趣的特征。

  3. 读取、写入和启用每个服务中特征的通知。

您可以子类化BLEClient类来实现特定的 BLE 设备,该设备支持您的设备所需的操作。子类调用BLEClient类的方法来启动前面的操作。由BLEClient执行的所有 BLE 操作都是异步的,以避免在不确定的时间内阻塞执行。因此,BLEClient类的实例通过回调接收结果。例如,BLEClient类有一个startScanning方法,您可以调用它来开始扫描外设,还有一个onDiscovered回调函数,当发现外设时会自动调用。

您只需要实现处理设备所需的外围设备、服务和特性所需的回调。

BLEServer

BLEServer类提供了用于创建外设和 GATT 服务器的函数。外围设备的功能执行以下操作:

  1. 广告以便中心可以发现外围设备。

  2. 接受来自中心的连接请求。

GATT 服务器的功能执行这些操作:

  1. 部署服务。

  2. 响应来自客户端的特征读取和写入请求。

  3. 接受来自客户端的特征值更改通知请求。

  4. 通知客户特征值的变化。

您可以实施标准的 BLE 模式,如心率或您自己定制的模式,以支持您产品的独特功能。在这两种情况下,首先在 JSON 文件中定义 GATT 服务,然后子类化BLEServer类来实现特定的 BLE 设备。子类调用BLEServer类的方法来启动前面的操作。由BLEServer执行的所有 BLE 操作都是异步的,以避免在不确定的时间内阻塞执行。因此,BLEServer类的实例通过回调接收结果。

安装 BLE 主机

本章中的示例使用第 1 章中描述的模式进行安装:您使用mcconfig在您的设备上安装主机,然后使用mcrun安装示例应用程序。

主机在$EXAMPLES/ch4-ble/host目录中。从命令行导航到这个目录,用mcconfig安装它。

创建 BLE 扫描仪

$EXAMPLES/ch4-ble/scanner示例实现了一个中心,它扫描附近的外围设备,并跟踪它们的名称到控制台。它是使用BLEClient类实现的。清单 4-1 展示了这个例子的大部分源代码。

class Scanner extends BLEClient {
    onReady() {
        this.startScanning();
    }
    onDiscovered(device) {
        let scanResponse = device.scanResponse;
        let completeName = scanResponse.completeName;
        if (completeName)
            trace(`${completeName}\n`);
    }
}

Listing 4-1.

Scanner类实现了两个BLEClient回调:

  • 当 BLE 栈准备好使用时,调用onReady回调。在这个例子中,onReady回调函数调用startScanning来扫描附近的外设。

  • 对于每个被发现的外设,onDiscovered回调被调用一次或多次。在这个例子中,onDiscovered回调跟踪发现的外围设备的名称到控制台。

在这个简单的例子中,您的中心会发现您周围的外围设备,并告诉您它们的名称。现在您可以更进一步了:下一个例子演示了如何使用BLEClient类的其他特性来创建一个与虚拟外设通信的 BLE 设备。

创造双向交流

$EXAMPLES/ch4-ble/text-client示例实现了一个连接到外设并通过特征值变化通知接收文本数据的中心。

要查看示例的工作情况,您需要一个提供文本数据特征的外设。你可以使用 Bluefruit 创建一个,这是一个在 iOS 和 Android 设备上免费提供的移动应用程序。要创建外设,采取以下步骤,如图 4-24-3 所示:

img/474664_1_En_4_Fig2_HTML.jpg

图 4-2

蓝果的周边模式

  1. 下载打开 Bluefruit,进入外设模式。在广告信息部分,将本地名称字段更改为esp

img/474664_1_En_4_Fig3_HTML.jpg

图 4-3

UART 服务使能

  1. 确定 UART 服务已打开。

接下来的部分解释了在 ESP32 上运行以实现中央设备的代码。

连接到外围设备

应用程序顶部的常数对应于您在 Bluefruit 中设置的设备名称以及 UART 服务使用的服务和特征 UUID:

const PERIPHERAL_NAME = 'esp';
const SERVICE_UUID = uuid`6E400001B5A3F393E0A9E50E24DCCA9E`;
const CHARACTERISTIC_UUID = uuid`6E400003B5A3F393E0A9E50E24DCCA9E`;

scanner例子一样,这个例子实现了onReadyonDiscovered回调,如清单 4-2 所示。但是这个例子并不只是跟踪设备的名称到控制台,而是检查每个发现的外围设备的名称,看它是否与PERIPHERAL_NAME常量匹配。如果是,它停止扫描外围设备并调用connect方法,该方法在BLEClient和目标外围设备之间发起连接请求。

class TextClient extends BLEClient {
    onReady() {
        this.startScanning();
    }
    onDiscovered(device) {
        if (PERIPHERAL_NAME ===
                device.scanResponse.completeName) {
            this.stopScanning();
            this.connect(device);
        }
    }
    ...

Listing 4-2.

connect的参数是Device类的一个实例,代表一个外围设备。BLEClient在发现外设时自动创建Device类的实例;应用程序不会直接实例化它们。然而,应用程序确实与Device类的实例直接交互——例如,通过调用方法来执行 GATT 服务和特征发现。

onConnected回调

onConnected方法是当中央处理器连接到外设时调用的回调。这个例子调用了device对象的discoverPrimaryService方法来从外围设备获取主要的 GATT 服务。discoverPrimaryService的参数是要发现的服务的 UUID。

onConnected(device) {
    device.discoverPrimaryService(SERVICE_UUID);
}

您可以使用discoverAllPrimaryServices方法发现外围设备的所有主要服务。例如,onConnected回调可以写成如下形式:

onConnected(device) {
    device.discoverAllPrimaryServices();
}

onServices回调

onServices方法是服务发现完成时调用的回调。services参数是一组service对象——Service类的实例——每个对象都提供对单个服务的访问。如果调用discoverPrimaryService来查找单个服务,服务数组只包含找到的一个服务。

如清单 4-3 所示,该示例检查外设是否提供了与SERVICE_UUID常量定义的 UUID 相匹配的服务。如果是的话,它调用service对象的discoverCharacteristic方法来寻找 UUID 与CHARACTERISTIC_UUID常量定义的服务特征相匹配的服务特征。

onServices(services) {
    let service = services.find(service =>
            service.uuid.equals(SERVICE_UUID));
    if (service) {
        trace(`Found service\n`);
        service.discoverCharacteristic(CHARACTERISTIC_UUID);
    }
    else
        trace(`Service not found\n`);
}

Listing 4-3.

您可以使用discoverAllCharacteristics方法发现所有的服务特征。例如,onServices回调可以用下面的代码行替换调用discoverCharacteristic的代码行:

service.discoverAllCharacteristics();

onCharacteristics回调

onCharacteristics方法是在特性发现完成时调用的回调。characteristics参数是一组characteristic对象——Characteristic类的实例——每个对象都提供对单个服务特征的访问。如果调用discoverCharacteristic来查找单个特征,那么特征数组包含所找到的单个特征。

当找到所需的特性时,该示例调用characteristic对象的enableNotifications方法,以在特性的值改变时启用通知,如清单 4-4 所示。

onCharacteristics(characteristics) {
    let characteristic = characteristics.find(characteristic =>              characteristic.uuid.equals(CHARACTERISTIC_UUID));
    if (characteristic) {
        trace(`Enabling notifications\n`);
        characteristic.enableNotifications();
    }
    else
        trace(`Characteristic not found\n`);
}

Listing 4-4.

如果您正确设置了外围设备,当您运行text-client应用程序时,您将在调试控制台中看到以下消息:

Found service
Enabling notifications

接收通知

启用通知后,您可以通过从智能手机更改外围设备的特征值来向客户端发送通知。要更改该值,点击 UART 按钮。这将带您进入图 4-4 所示的屏幕。在屏幕底部的输入字段中输入文本,点击发送更新特征值。

img/474664_1_En_4_Fig4_HTML.jpg

图 4-4

Bluefruit 中的 UART 屏幕

特征值通过ArrayBuffer中的onCharacteristicNotification回调传递给客户端。这个例子假设值是一个字符串,所以它使用String.fromArrayBuffer(XS 的一个特性,使应用程序更容易处理二进制数据)将ArrayBuffer转换成一个字符串。有一个平行的ArrayBuffer.fromString。这些都不是 JavaScript 语言标准的一部分。

onCharacteristicNotification(characteristic, buffer) {
    trace(String.fromArrayBuffer(buffer)+"\n");
}

创建心率监测器

现在您已经了解了实现从服务器接收通知的客户机的基本知识,这个例子将向您展示如何使用BLEServer类的特性来实现一个外围设备,该外围设备在连接到中央服务器之后承担服务器的角色。

$EXAMPLES/ch4-ble/hrm示例宣传标准心率和电池服务,接受来自 Centrals 的连接请求,发送模拟心率值的通知,并响应来自客户端的模拟电池电量的读取请求。接下来的几节解释了如何使用BLEServer类实现它。

定义和部署服务

GATT 服务是在位于主机的bleservices目录中的 JSON 文件中定义的。JSON 在构建时会自动转换成特定于平台的本机代码,编译后的目标代码会链接到应用程序。

每个 GATT 服务都是在自己的 JSON 文件中定义的。清单 4-5 显示了标准心率服务。

{
    "service": {
        "uuid": "180D",
        "characteristics": {
            "bpm": {
                "uuid": "2A37",
                "maxBytes": 2,
                "type": "Array",
                "permissions": "read",
                "properties": "notify"
            }
        }
    }
}

Listing 4-5.

以下是对一些重要属性的解释:

  • 对象的属性是 GATT 规范分配给服务的号码。心率服务有 UUID 180F

  • characteristics对象描述了服务支持的每个特征。每个直接属性是一个特征的名称。在这个例子中,只有一个特征:bpm,代表每分钟的节拍数。

  • 一个characteristic对象的uuid属性是由 GATT 规范分配给特征的唯一数字。心率服务的bpm特征有 UUID 2A37

  • 属性指定了 JavaScript 代码中使用的特征值的类型。BLEServer类使用type属性的值将客户端传输的二进制数据转换为 JavaScript 类型。这为您的服务器代码节省了在不同类型的数据之间来回转换的工作(ArrayBufferStringNumber等等)。

  • permissions属性定义特性是只读、只写还是读/写,以及访问特性是否需要加密连接。bpm属性是只读的,因为在心率监视器中,每分钟的心跳数是由传感器读数决定的,因此不能由客户端写入。read权限表示客户端可以通过未加密或加密的连接读取特征;当值只能通过加密连接访问时,使用readEncrypted。类似地,使用writewriteEncrypted获得写权限。要表明某个特征同时支持读取和写入,请在权限字符串中包含读取和写入值,用逗号分隔,例如"readEncrypted,writeEncrypted"

  • properties属性定义了特征的属性。可能是readwritenotifyindicate,或者是这些的组合(逗号分隔)。readwrite值允许读取和写入特征值,notify允许服务器通知客户端特征值的变化,而无需请求和确认通知已被接收,并且indicatenotify相同,除了它需要确认通知已被接收,然后才能发送另一个指示。

一旦 BLE 栈完成初始化,它就调用onReady回调。onReadyhrm示例实现发起广告,使其服务能够被客户端发现。下一节解释了当广告激活时子类如何管理。

广告

外围设备广播广告数据来宣告它们自己。BLEServer类有一个startAdvertising方法开始广播广告包,还有一个stopAdvertising方法停止广播。

当 BLE 栈准备好使用时,以及当到中心的连接丢失时,hrm示例调用startAdvertising。当startAdvertising被调用时,外设广播它的广告数据类型标志值、它的名称和它的服务(心率和电池),如清单 4-6 所示。心率和电池服务的 UUIDs 来自 GATT 规范。

this.startAdvertising({
    advertisingData: {
        flags: 6,
        completeName: this.deviceName,
        completeUUID16List: [uuid`180D`, uuid`180F`]
    }
});

Listing 4-6.

当成功建立到中心的连接时,外围设备停止发送广告数据包,因为它一次只支持一个连接:

onConnected() {
    this.stopAdvertising();
}

当连接丢失时,外围设备再次开始广告。

BLE 广告可能包含附加数据,例如,实现 BLE 信标。BLE 信标为许多中心做广告,让他们不用连接就能看到数据。清单 4-7 中的代码来自可修改 SDK 中的examples/network/ble/uri-beacon示例,它实现了一个为可修改网站做广告的 UriBeacon。这里的 UUID 来自于赋值的数字规范(见 bluetooth.com/specifications/assigned-numbers/16-bit-uuids-for-members )。encodeData方法以 UriBeacon 规范指定的格式对 URI 进行编码。源代码见uri-beacon示例。

this.startAdvertising({
    advertisingData: {
        completeUUID16List: [uuid`FED8`],
        serviceDataUUID16: {
            uuid: uuid`FED8`,
            data: this.encodeData("http://www.moddable.com")
        }
    }
});

Listing 4-7.

广告数据只传输数据;没有办法回复。双向通信要求一台设备承担 GATT 客户端角色,另一台设备承担 GATT 服务器角色。在这发生之前,由 GAP 定义的连接过程必须完成。

建立连接

一旦心律外围设备开始发布广告,它就会等待中央设备请求与其连接。你可以使用各种各样的移动应用程序来创建一个这样的中心。在本节中,您将使用 LightBlue,它可以在 iOS 和 Android 设备上免费获得。LightBlue 具有中央模式,使您能够扫描、连接外围设备并向其发送读/写请求。通过执行以下步骤,您可以将其用作外围设备的客户端:

img/474664_1_En_4_Fig5_HTML.jpg

图 4-5

浅蓝色外设列表中的心率监测器外设

  1. 在您的 ESP32 上运行该示例。

  2. 下载并打开 LightBlue,等待心率监测器外设出现,如图 4-5 所示。

  3. 轻触心率监测器外围设备,与其建立连接。

当中央处理器连接到心率外设时,会调用onConnected回调。在本例中,它停止广播广告并响应扫描响应包,如清单 4-8 所示。

class HeartRateService extends BLEServer {
    ...
    onConnected() {
        this.stopAdvertising();
    }
    ...
}

Listing 4-8.

发送通知

BLE 客户端可以请求具有notify属性的特征的通知,例如本例中的bpm特征。启用通知时,服务器会通知客户端特性值的更改,而无需服务器请求该值。通知节省了能量,这是 BLE 设计的一个关键特性,因为它消除了客户端轮询服务器以了解特征变化的需要。

要接收浅蓝色的模拟心率通知,请采取以下步骤(如图 4-64-74-8 所示):

img/474664_1_En_4_Fig6_HTML.jpg

图 4-6

带有心率测量按钮的心率监护仪特征屏幕

  1. 点击心率测量特性。

img/474664_1_En_4_Fig7_HTML.jpg

图 4-7

带有收听通知按钮的心率测量屏幕

  1. 轻触收听通知以启用通知。

img/474664_1_En_4_Fig8_HTML.jpg

图 4-8

出现在通知值部分的心率值

  1. 观察模拟心率值的出现。

现在让我们看看在服务器端实现心率服务通知的代码。onCharacteristicNotifyEnabled方法是一个回调函数,当客户端启用某个特性的通知时,就会调用这个函数。onCharacteristicNotifyDisabled方法是一个回调函数,当客户端禁用某个特性的通知时会调用这个函数。两者的characteristic参数都是Characteristic类的一个实例,它提供了对单个服务特征的访问。

onCharacteristicNotifyEnabled方法(如清单 4-9 所示)调用notifyValue方法,该方法以 1000 毫秒(1 秒)的间隔向连接的客户端发送特征值变化通知。这模拟了心率传感器,尽管真实的心率监测器不会发送定期更新;相反,当值实际改变时,它会发送一个通知。

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.

onCharacteristicNotifyDisabled方法(清单 4-10 )通过调用stopMeasurements方法结束通知的发送。

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.

响应读取请求

客户端可能会请求支持read属性的特征值,例如本例中的电池服务。要发送读取浅蓝色模拟电池电量值的请求,请采取以下步骤(如图 4-94-104-11 所示):

img/474664_1_En_4_Fig9_HTML.jpg

图 4-9

电池电量按钮的心率监测器特征屏幕

  1. 点击电池电量特性。

img/474664_1_En_4_Fig10_HTML.jpg

图 4-10

电池电量屏幕,带有再次读取按钮

  1. 点击再次读取

img/474664_1_En_4_Fig11_HTML.jpg

图 4-11

显示在读取值部分的电池电量值

  1. 观察模拟电池电量的显示。

现在让我们来看看处理电池电量服务通知的代码。onCharacteristicRead方法(清单 4-11 )是一个回调函数,当客户端根据需要读取服务特征值时会调用它。BLEServer实例负责处理读请求。在本例中,电池电量从 100 开始;每次读取该值时,回调函数都会返回值并减 1。

onCharacteristicRead(params) {
    if (params.name === "battery") {
        if (this.battery === 0)
            this.battery = 100;
            return this.battery--;
    }
}

Listing 4-11.

建立安全通信

可修改的 SDK 支持蓝牙核心规范 4.2 版中引入的增强安全特性:通过数字比较、密钥输入和 Just Works 配对方法实现安全连接。BLEClientBLEServer类都有一个可选的securityParameters属性,请求设备建立一个 LE 安全连接。使用的配对方法取决于设备的功能和在securityParameters属性中设置的选项。安全回调函数由BLEClientBLEServer类托管。下一节将介绍一个简单的例子。

安全心率监测器

$EXAMPLES/ch4-ble/hrm-secure示例是$EXAMPLES/ch4-ble/hrm示例的安全版本,它需要输入密码进行配对。

同样,您可以使用 LightBlue 作为客户端。按照与之前相同的步骤,当心率监测器提示您输入密码时(如图 4-12 ,在xsbug中输入追踪到控制台的密钥(如图 4-13 )。

img/474664_1_En_4_Fig13_HTML.jpg

图 4-13

调试控制台中的密钥

img/474664_1_En_4_Fig12_HTML.jpg

图 4-12

以浅蓝色提示输入代码

现在,您可以像以前一样启用心率值通知并按需读取电池值,但服务器和客户端之间的连接是安全的。

这段代码与$EXAMPLES/ch4-ble/hrm的例子有一些不同。如清单 4-12 所示,onReady回调包含配置设备安全需求和外设 I/O 能力的附加代码。

this.securityParameters = {
    bonding: true,
    mitm: true,
    ioCapability: IOCapability.DisplayOnly
};

Listing 4-12.

此代码中的属性指定了以下内容:

  • 属性启用绑定,这意味着两个设备将在下次连接时存储和使用它们交换的密钥。如果不启用绑定,设备每次连接时都必须进行配对。

  • mitm属性请求中间人保护,这意味着两个配对设备之间交换的数据被加密,以防止不受信任的设备窃听。

  • ioCapability属性指示与确认密钥相关的设备的用户界面功能。这个设备没有显示器,但是它有显示功能,因为它可以跟踪调试控制台。其他外围设备可能具有更多的输入/输出能力(例如,带有键盘的设备)或更少的输入/输出能力(例如,无法显示文本的设备)。两个设备的ioCapability属性用于确定配对方法。例如,如果两个设备都没有键盘或显示器,则使用 Just Works 配对方法。

实现了另外两个BLEServer类的回调函数(参见清单 4-13 ):

  • 当您试图建立与外设的连接时,会调用onPasskeyDisplay回调。在这种情况下,当您点击浅蓝色外围设备的名称时,就会调用它。正如您之前看到的,这个示例跟踪调试控制台的密钥。

  • 设备配对成功后会调用onAuthenticated回调。这个例子简单地改变了authenticated属性来表明一个安全的连接已经建立。

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

Listing 4-13.

当客户端启用通知时,服务器检查是否设置了authenticated属性。if块中的代码看起来与hrm示例中的onCharacteristicNotifyEnabled方法相同。

onCharacteristicNotifyEnabled(characteristic) {
    if (this.authenticated) {
        ...
}

服务器还定义了一个额外的助手方法,名为passkeyToString。密钥值是整数,向用户显示时必须始终包含六位数字。此方法在必要时用前导零填充密钥以供显示。

passkeyToString(passkey) {
    return passkey.toString().padStart(6, "0");
}

结论

现在你已经理解了这些例子的要点,你可以在你的 ESP32 上用 BLE 做很多事情。您可以连接到家中的 BLE 产品,而不是连接到您在 LightBlue 中创建的虚拟外围设备。您可以从自己喜欢的现成传感器发送实际的传感器数据,而不是像心率监测器示例那样发送模拟数据。

如果你想尝试更多 BLE 的例子,可以在 GitHub 上的 Moddable SDK 中查看examples/network/ble目录。让您的设备成为 URI 传输信标、将您的设备与 iPhone 音乐应用程序配对,等等。如果你想了解更多关于可修改的 SDK 的 BLE API,请看documentation/network/ble目录。