文章目录
目的
蓝牙是现在非常常用的一种无线通讯技术,ESP32集成了蓝牙功能,这篇文章就将对相关功能及最常见应用场景(当作服务器 / 从设备)下的使用方式做个简单介绍。
蓝牙基础说明
从技术层面来说蓝牙还是有点复杂的,当然我们只是使用来说就相对简单些。生活中很多电子产品都是蓝牙的连接的,像是蓝牙耳机、运动手环、蓝牙键盘等等。这些设备使用时是作为从机使用的,相当于一台服务器,用户可以从其中获取数据或是向它写入数据。
上图就是蓝牙设备作为服务器来说其内部简单的结构:
- 首先是蓝牙设备(Device)作为服务器使用(Server);
- 一个服务器中运行了一个或多个服务(Service),每个服务由一个UUID来标识;
- 每个服务中含有一个或多个特征(Characteristic),每个特征由一个UUID来标识;
- 每个特征中包含一个值(Value),另外还包含零个或多个描述(Descriptor),每个描述由一个UUID来标识;
对于蓝牙设备来说用户真正打交道的是Characteristic中的Value,拿蓝牙温度计来说,其温度数据就是保存在某个Value中的,而该温度数据的小数点位数、单位、分辨率等信息都保存在该Value所在的Characteristic中各个Descriptor中。
上面有个UUID,这是一个128位的编码(通用唯一识别码 Universally Unique Identifier),客户端连接上服务器之后就是通过这个编码来区别访问对应的服务与数据的。这个编码可以自定义,可以通过工具生成: https://www.uuidgenerator.net/
蓝牙技术联盟对UUID也有一些规定,如果看到 0000xxxx-0000-1000-8000-00805F9B34FB
这个UUID,那基本就是与蓝牙相关的功能。根据不同的功能其中xxxx部分也不相同,比如对于Service来说如果使用了 0000180F-0000-1000-8000-00805F9B34FB
这个UUID,那通常表示这个Service是电池相关的服务;对于Characteristic来说如果使用了 00002A23-0000-1000-8000-00805F9B34FB
这个UUID,那通常表示这个Characteristic是用来提供系统ID的。
更详细的内容可以参考:https://www.bluetooth.com/specifications/assigned-numbers/
ESP32的蓝牙功能最常见的也是作为服务器使用。ESP32的蓝牙支持蓝牙低功耗(BLE),所以开发的时候可以看到相关功能代码大多以BLE开头。
ESP32蓝牙相关架构说明可以参考文档:《ESP32 蓝牙架构》
测试工具
蓝牙相关的测试工具没有找到合适的PC版的,只有一些安卓版的,可以看看下面两个:
- BLE调试助手
这是南京沁恒微电子股份有限公司(出品CH340 USB转串口芯片的那家)出品的用于调试BLE的工具,下载地址如下:
http://www.wch.cn/downloads/BLEAssist_ZIP.html - nRF Connect
这是Nordic Semiconductor出品的用于调试BLE的工具,可以在其GitHub项目https://github.com/NordicSemiconductor/Android-nRF-Connect 的Releases中下载到apk文件;
作为服务器使用
创建蓝牙设备并广播
Arduino core for the ESP32中使用蓝牙功能首先需要引用相关的库,然后声明一个蓝牙设备对象,作为服务器使用的时候还需要开启Advertising广播才能让别人通道蓝牙扫描发现你,下面是个最基本的演示:
#include <BLEDevice.h> // 引入相关库
void setup() {
BLEDevice::init("ESP32-BLE"); // 填写自身对外显示的蓝牙设备名称,并初始化蓝牙功能
BLEDevice::startAdvertising(); // 开启Advertising广播
}
void loop() {}
上传上面代码后就可以在手机和电脑上搜索到所设定的名称的蓝牙设备了。
创建Server
作为服务器使用还需要在上面的基础上将蓝牙设备指定为Server(服务器)使用。Server可以传入一组回调函数,分别会在有客户端设备接入时和断开连接时触发。 在有设备接入后Advertising广播会被停止,所以要在设备断开连接时重新开启广播,可以通过此处的回调函数知道设备是否断开连接。
#include <BLEDevice.h>
// Server回调函数声明
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
Serial.println("现在有设备接入~");
};
void onDisconnect(BLEServer* pServer) {
Serial.println("现在有设备断开连接~");
// 在有设备接入后Advertising广播会被停止,所以要在设备断开连接时重新开启广播
// 不然的话只有重启ESP32后才能重新搜索到
pServer->startAdvertising(); //该行效果同 BLEDevice::startAdvertising();
}
};
void setup() {
Serial.begin(115200);
Serial.println();
BLEDevice::init("ESP32-BLE");
BLEServer *pServer = BLEDevice::createServer(); // 创建服务器
pServer->setCallbacks(new MyServerCallbacks()); // 绑定回调函数
BLEDevice::startAdvertising();
}
void loop() {}
创建Service并启动
有了Server后我们可以在Server下面创建Service(服务),一个Server中可以有多个Service,比如上一个例子的动图演示中可以看到我连接到设备后展开的是一个Service列表,该列表中的 Generic Attribute 和 Generic Access 就是两个Service,这两个Service是蓝牙设备默认创建的。
#include <BLEDevice.h>
#define SERVICE_UUID1 "4fafc201-1fb5-459e-8fcc-c5c9c331914b" // 自定义UUID
#define SERVICE_UUID2 "0000180F-0000-1000-8000-00805F9B34FB" // 0x180F是蓝牙技术联盟定义的Battery Service的16-bit UUID
void setup() {
BLEDevice::init("ESP32-BLE");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService1 = pServer->createService(SERVICE_UUID1); // 创建服务
pService1->start(); // 启动服务
BLEService *pService2 = pServer->createService(SERVICE_UUID2); // 创建服务
pService2->start(); // 启动服务
BLEDevice::startAdvertising();
}
void loop() {}
上面演示中创建了两个Service,其中一个使用了自定义的UUID,另一个使用了蓝牙技术联盟有做出定义的UUID。我使用的测试工具中对UUID进行了识别,后一个Service就被识别出来了。
创建Characteristic
Service主要是向外界提供服务的,作为设备来说就是提供该设备的特性信息与数据这些,所以我们需要在Service下面创建Characteristic(特征)。一个Service可以包含多个Characteristic。
#include <BLEDevice.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID1 "beb5483e-36e1-4688-b7f5-ea07361b26a8" // 自定义UUID
#define CHARACTERISTIC_UUID2 "00002A23-0000-1000-8000-00805F9B34FB" // 0x2A23是蓝牙技术联盟定义的System ID Characteristic的16-bit UUID
void setup() {
BLEDevice::init("ESP32-BLE");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic1 = pService->createCharacteristic( // 创建特征
CHARACTERISTIC_UUID1,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
BLECharacteristic *pCharacteristic2 = pService->createCharacteristic( // 创建特征
CHARACTERISTIC_UUID2,
BLECharacteristic::PROPERTY_NOTIFY
);
pService->start();
BLEDevice::startAdvertising();
}
void loop() {}
读写Characteristic中的Value
每个Characteristic有一个Value,这个Value就是蓝牙设备中真正提供给用户交互的数据,服务端可以设置客户端对该Value的读写权限。下面是个简单的读写演示:
#include <BLEDevice.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
BLEDevice::init("ESP32-BLE");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | // 启用读取
BLECharacteristic::PROPERTY_WRITE // 启用写入
);
pCharacteristic->setValue("Hello World"); // 设置该Characteristic的Value值
// 如果客户端连上设备后没有任何写入的情况下第一次读取到的数据应该是这里设置的值
pService->start();
BLEDevice::startAdvertising();
}
void loop() {}
上面的演示中可以看到对于读写来说Value是公共的一个存储数据的区域。 在Arduino core for the ESP32中Value默认可容纳600字节。
Characteristic主动向客户端发送Value
Characteristic中的Value除了可以通过客户端主动读写访问外还可以由设备主动推送给客户端。
#include <BLEDevice.h>
#include <BLE2902.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
uint32_t value = 0;
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
void setup() {
BLEDevice::init("ESP32-BLE");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_NOTIFY | // 启用通知
BLECharacteristic::PROPERTY_INDICATE // 启用通知
);
pCharacteristic->addDescriptor(new BLE2902()); // 不加这行可能无法使用通知
pServer->setCallbacks(new MyServerCallbacks());
pService->start();
BLEDevice::startAdvertising();
}
void loop() {
if (deviceConnected) {
pCharacteristic->setValue((uint8_t*)&value, 4); // 设置value值
pCharacteristic->notify(); // 使用notify方法向客户端发送数据,也可以使用indicate方法
value++;
// delay(3); // 等待蓝牙处理完成
}
delay(2000);
}
服务器向客户端推送数据上面启用的两种方法 notify 和 indicate , notify 是非阻塞的,不会确认客户端是否接收到, indicate 是阻塞的,会确认客户端响应。 默认情况下两种方法都只会发送Value的前20个字节数据。这个和蓝牙的MTU有关,默认情况下MTU是23字节,扣除固定3字节,可用的就是20个字节。MTU可以从客户端进行修改,比如上面调试助手中就可以修改MTU为512(这个还和蓝牙版本有关) 。
notify 和 indicate 虽然是由服务器主动向客户端发送消息的,但客户端接不接收这些消息还是客户端自己决定的。
Characteristic Value操作回调函数
对Characteristic Value的各类操作都可以触发事件,下面是个简单的演示:
#include <BLEDevice.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyCallbacks: public BLECharacteristicCallbacks {
void onRead(BLECharacteristic* pCharacteristic) { // 客户端读取事件回调函数
Serial.println("触发读取事件");
}
void onWrite(BLECharacteristic *pCharacteristic) { // 客户端写入事件回调函数
Serial.println("触发写入事件");
}
};
void setup() {
Serial.begin(115200);
Serial.println();
BLEDevice::init("ESP32-BLE");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
pService->start();
BLEDevice::startAdvertising();
}
void loop() {}
除了 onRead 和 onWrite 外还可以使用 onNotify 和 onStatus方法。
Descriptor
Descriptor(描述)是对Characteristic Value的描述说明,这个东西并不是必须的,一个Characteristic中可以有零个或多个Descriptor。Descriptor由UUID和保存在其中的数据内容组成。蓝牙技术联盟定义了一些特殊的UUID。
在Arduino core for the ESP32中你可以向Characteristic添加自己的Descriptor,也可以直接套用自带的两个Descriptor(16-bit UUID 0x2902 、 0x2904)。
16-bit UUID 0x2902 的Descriptor提供了客户端控制服务器是否使能 notify 和 indicate 的功能。
#include <BLEDevice.h>
#include <BLE2902.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
BLEDevice::init("ESP32-BLE");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
pCharacteristic->addDescriptor(new BLE2902()); // 添加描述
pService->start();
BLEDevice::startAdvertising();
}
void loop() {}
Advertising补充说明
蓝牙里面使用Advertising广播自身,是客户端可以发现自己。这个Advertising广播的内容可以包含很多东西,比如字节的设备类型是什么、里面的服务都有什么UUID、自定义的数据等等。另外Advertising本身也有一些参数可以用于设置其工作特性,像是广播间隔时间、广播类型、广播过滤策略等。ESP32使用蓝牙作为服务器使用来说合理设置Advertising也是比较重要的,这里就对相关内容做个说明。先看下面例子:
#include <BLEDevice.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
pServer->startAdvertising(); // 有客户端连接后广播会停止,这里再次打开使其它设备可以搜索到
};
void onDisconnect(BLEServer* pServer) {}
};
void setup() {
Serial.begin(115200);
Serial.println();
BLEDevice::init("ESP32-BLE");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService *pService = pServer->createService(SERVICE_UUID);
pService->start();
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); // 获取Advertising对象
pAdvertising->addServiceUUID(SERVICE_UUID); // 广播Service的UUID
BLEDevice::startAdvertising();
}
void loop() {}
上面的代码中使用 BLEDevice::getAdvertising()
获取到了Advertising对象,然后使用 addServiceUUID()
方法添加了一个服务的UUID,这样广播的时候就会主动暴露这个UUID。下面对Advertising对象的部分方法做个说明:
void addServiceUUID(BLEUUID serviceUUID)
void addServiceUUID(const char* serviceUUID)
向广播数据中添加服务的UUID;void start()
void stop()
启用和停止广播;
需要注意的是有客户端连接后广播会自动停止;void setAppearance(uint16_t appearance)
设置设备的类型,各种类型代码可以在:https://www.bluetooth.com/specifications/assigned-numbers/ 的Appearance Values Document中找到;void setMaxInterval(uint16_t maxinterval)
void setMinInterval(uint16_t mininterval)
设置广播消息发送间隔;void setAdvertisementData(BLEAdvertisementData& advertisementData)
void setScanResponseData(BLEAdvertisementData& advertisementData)
设置广播额外数据和扫描响应数据;void setScanResponse(bool)
是否对主动扫描发送扫描响应数据,默认为false;void setScanFilter(bool scanRequertWhitelistOnly, bool connectWhitelistOnly)
设置对扫描和连接请求的响应过滤规则;void setAdvertisementType(esp_ble_adv_type_t adv_type)
void setAdvertisementChannelMap(esp_ble_adv_channel_t channel_map)
设置广播类型和通道等;
总结
Arduino core for the ESP32中ESP32启用蓝牙服务器功能作为从设备与手机电脑等的交互数据还是不复杂的,更多内容可以参考下面链接:
https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE