蓝⽛牙系统分为两种不不同的技术:经典蓝⽛牙 (Classic Bluetooth) 和蓝⽛牙低功耗 (Bluetooth Low Energy)。ESP32 ⽀支持双模蓝⽛牙,即同时⽀支持经典蓝⽛牙和蓝⽛牙低功耗。BLE隶属于蓝牙4.0协议的一部分。
版本 | 时间 | 说明 |
---|---|---|
3.0 + HS | 2009.4.21 | 交替射频技术、802.11协议适配层、电源管理、取消了UMB的应用 |
4.0+BLE | 2010.6.30 | 低功耗物理层和链路层、AES加密、Attribute Protocol(ATT)、Generic Attribute Profile(GATT)、Security Manager(SM)重要的特性是省电 |
4.1 | 2013.12 | 1)与4G不构成干扰;2)通过IPV6连接到网络。3)可同一时候发射和接收数据。 |
4.2 | 2014 | |
5.0 | 在低功耗技术(BLE)可提供2倍的传输速率、8倍的广播能力、4倍的覆盖范围 |
ESP32 里的蓝牙协议栈是符合蓝牙4.2协议规范的
BLE 角色划分
在 BLE 协议栈,不同的层级里面有不同的角色划分:
LL:可以把设备分为主机和从机,从机广播,主机发起连接;
GAP:可以把设备分为中心设备和外围设备;
GATT:可以把设备分为服务端和客户端;
这些划分相互是不受影响的,在这里,我们只讨论 GATT 层的角色。
GATT 其实是一种属性传输协议,简单的讲可以认为是一种属性传输的应用层协议,这个属性的结构其实非常简单,其实也就是由一个个 services 组成,每个 service 又由数量不等的 characteristic 组成,每个characteristic 又由很多其它的元素组成
一个鼠标是一个BLEDevice,一个BLEDevice建立了一个BLE服务器 BLEServer
一个BLE服务器里有多个服务BLEService
一个服务里有多个特征值BLECharacteristic 每个特征值是一种数据.就是通过读写这些“Characteristic”实现读写数据
居然看到一个大型脑图,很厉害的样子,先贴再消化。
BLE的核心部份:
在链路层定义了蓝牙的五种状态:
就绪态(Standby) 不收发报文,默认状态
广播态(Advertising) 可以发送报文,也可以监听。
扫描态(Scanning) 能接收广播报文。分为主动扫描和被动扫描
初始态(Initiating) 可以发起连接请求
连接态(Connection) 数据传输状态
BLE ⾥⾯的数据以属性 (Attribute) ⽅式存在,每条属性由四个元素组成:
属性句柄 (Attribute Handle):
属性类型 (Attribute UUID):
属性值 (Attribute Value):
属性许可 (Attribute Permissions):可读/可写
UUID
服务和characteristic是通过UUID来进行识别的
notify通知
如果主机的一个特征值characteristic发生改变, 可以使用通知notify来告诉客户端. 这是服务器主动给客户端发的信息
示例
ESP32 BLE服务器
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" // 服务ID
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" // 功能ID
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
BLEDevice::init("Long name works now"); // 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 says Neil"); // 创建特征后,可以写入一个值
pService->start(); // 开启服务
// BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID); // 加入功能
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // 有助于解决iPhone连接问题的功能
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("特性定义完成!可以手里连接!");
}
void loop() {
delay(2000);
}
通过手机APP连接,会看到一些附加的服务。例如读取名字等,暂时不用管它。
在未知服务一栏中,列出了SERVICE_UUID。
例子实现了:写入什么,就可以通过服务读取出来。
步骤:
-
创建服务器 BLEDevice::init(ble_name);
-
创建服务 BLEServer *pServer = BLEDevice::createServer();
-
创建若干服务 BLEService *pService = pServer->createService(SERVICE_UUID);
-
在服务上创建特征
-
对特征进行描述
-
启动服务
-
开始广播
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <String.h>
BLECharacteristic *pCharacteristic; //创建一个BLE特性pCharacteristic
bool deviceConnected = false; //连接否标志位
uint8_t txValue = 0; //TX的值
long lastMsg = 0; //存放时间的变量
String rxload = "BlackWalnutLabs"; //RX的预置值
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
//服务器回调
class MyServerCallbacks : public BLEServerCallbacks
{
void onConnect(BLEServer *pServer)
{
deviceConnected = true;
};
void onDisconnect(BLEServer *pServer)
{
deviceConnected = false;
}
};
//特性回调
class MyCallbacks : public BLECharacteristicCallbacks
{
void onWrite(BLECharacteristic *pCharacteristic)
{
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0)
{
rxload = "";
for (int i = 0; i < rxValue.length(); i++)
{
rxload += (char)rxValue[i];
Serial.print(rxValue[i]);
}
Serial.println("");
}
}
};
void setupBLE(String BLEName)
{
const char *ble_name = BLEName.c_str(); //将传入的BLE的名字转换为指针
BLEDevice::init(ble_name); //初始化一个蓝牙设备
BLEServer *pServer = BLEDevice::createServer(); // 创建一个蓝牙服务器
pServer->setCallbacks(new MyServerCallbacks()); //服务器回调函数设置为MyServerCallbacks
BLEService *pService = pServer->createService(SERVICE_UUID); //创建一个BLE服务
pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
//创建一个(读)特征值 类型是通知
pCharacteristic->addDescriptor(new BLE2902());
//为特征添加一个描述
BLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
//创建一个(写)特征 类型是写入
pCharacteristic->setCallbacks(new MyCallbacks());
//为特征添加一个回调
pService->start(); //开启服务
pServer->getAdvertising()->start(); //服务器开始广播
Serial.println("Waiting a client connection to notify...");
}
void setup()
{
Serial.begin(115200);
setupBLE("ESP32BLE"); //设置蓝牙名称
}
void loop()
{
long now = millis(); //记录当前时间
if (now - lastMsg > 1000)
{ //每隔1秒发一次信号
if (deviceConnected && rxload.length() > 0)
{
String str = rxload;
if (str=="10086")
{
const char *newValue = str.c_str();
pCharacteristic->setValue(newValue);
pCharacteristic->notify();
}
}
lastMsg = now; //刷新上一次发送数据的时间
}
}
实现了一个写入和获取,其它不重要。
BLE 扫描
#include <BLEDevice.h> // 蓝牙Ble设备库
#include <BLEUtils.h>
#include <BLEScan.h> // 蓝牙ble设备的扫描功能库
#include <BLEAdvertisedDevice.h> // 扫描到的蓝牙设备(广播状态)
int scanTime = 5; //In seconds
BLEScan* pBLEScan; // 扫描对象
// 扫描广播设备结果回调
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init(""); // 设备初始化
pBLEScan = BLEDevice::getScan(); // 创建一个新扫描入口
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());// 注册扫描结果回调
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster // 配置主动扫描
pBLEScan->setInterval(100); // 配置扫描PDU间隔
pBLEScan->setWindow(99); // less or equal setInterval value // 设置扫描窗口大小,需要小于扫描间隔
}
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults foundDevices = pBLEScan->start(scanTime, false);// 开始扫描 等待扫描结果
Serial.print("Devices found: ");
Serial.println(foundDevices.getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // 删除结果,清除内存
delay(2000);
}
不过在众多代码中,这个设备数量始终为0,不知为何。
BLE发通知
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("ESP32");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
// Create a BLE Descriptor
pCharacteristic->addDescriptor(new BLE2902());
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
// notify changed value
if (deviceConnected) {
pCharacteristic->setValue((uint8_t*)&value, 4);
pCharacteristic->notify();
value++;
delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
BLE异步收发
其实就是一个通道(特征)接收指令,另一个通道(特征)可以非实时的发送反馈。
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++)
Serial.print(rxValue[i]);
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("UART Service");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY
);
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE
);
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
if (deviceConnected) {
pTxCharacteristic->setValue(&txValue, 1);
pTxCharacteristic->notify();
txValue++;
delay(10); // bluetooth stack will go into congestion, if too many packets are sent
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
可能会用到的相关网文
参考小程序代码:
//要发送的字符串(要在起始位置添加起始字符,结束位置添加结束字符)
let order = that.stringToBytes(recs);
let byteLen = order.byteLength;//长度
let pos = 0; //字符位置
let tempBuffer; //一次发送的数据
var i = 0; //计数
//为了安全每次发送18个字节 (每次最多20个)
//发送之前
while (byteLen > 0) {
i++;
if (byteLen > 18) {
tempBuffer = order.slice(pos, pos + 18);
pos += 18;
byteLen -= 18;
console.log("第", i, "次发送:", tempBuffer);
that.writeBLECharacteristicValue(tempBuffer);
} else {
tempBuffer = order.slice(pos, pos + byteLen);
pos += byteLen;
byteLen -= byteLen;
console.log("第", i, "次发送:", tempBuffer);
that.writeBLECharacteristicValue(tempBuffer);
}
}
console.log("发送结束");
ESP32接收端参考代码
void onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
for (int i = 0; i < rxValue.length(); i++){
//防止 意外字符串过长
if(bleReslen>1024){
bleReslen=0;
}
//开始标志
if((int)rxValue[i]==ASCII_STR){//ASCII 起始符
bleReslen=0;
//结束标志
}else if((int)rxValue[i]==ASCII_END){//ASCII 结束符
extractData();//提取数据结束
bleReslen=0;
}else{
//拼接字符串
encBefor[bleReslen]=(char)rxValue[i];//赋值
bleReslen++;
}
}
}
}