BLE是蓝牙4.0标准中的一个子集,也就是说,蓝牙4.0标准包含了BLE和经典蓝牙(BR/EDR)两种模式。BLE是低功耗蓝牙,而经典蓝牙则是一种传统的蓝牙技术,适用于传输音频和文件等大数据量数据。
蓝牙4.0是一个综合性协议规范,它将低功耗蓝牙和经典蓝牙两种技术融合在一起,从而提供了更广泛的应用场景和更高效的数据传输能力。在蓝牙4.0中,低功耗蓝牙和经典蓝牙可以同时运行,并且可以互相通信,从而实现了更加灵活和高效的应用。
因此,BLE和蓝牙4.0之间的关系是子集和总体的关系,BLE是蓝牙4.0标准中的一个特定技术分支。

GATT

GATT(Generic Attribute Profile)是蓝牙低功耗(Bluetooth Low Energy,BLE)协议栈中的一部分,它定义了 BLE 设备之间交换数据的格式和规范。
GATT基于属性和服务的概念,通过将数据封装在属性中,从而实现设备之间的通信。在GATT中,一个服务表示一个特定的功能,一个服务可以包含多个属性。每个属性都有一个唯一的标识符(UUID),可以用来识别它们。属性可以是只读的(Read),也可以是可写的(Write)。属性还可以包含一个描述符(Descriptor),用于描述属性的特性和值。
此外,GATT使用基于请求-响应模型的通信方式。当一个设备想要读取或写入属性时,它会发送一个请求给另一个设备,请求的格式包含要访问的属性的UUID和操作类型(读或写)。接收方设备会根据请求返回响应消息,其中包含请求的数据,或者在写入操作时返回确认消息。
BLE 里面的数据以属性(Attribute)方式存在,每条属性由四个元素组成:

  1. 属性句柄(Attribute Handle):正如我们可以使用内存地址查找内存中的内容一样,ATT 属性的句柄也可以协助我们找到相应的属性,例如第一个属性的句柄是0x0001,第二个属性的句柄是 0x0002,以此类推,最大可以到 0xFFFF。
  2. 属性类型(Attribute UUID):每个数据有自己需要代表的意思,例如表示温度、发射功率、电池等等各种各样的信息。蓝牙组织(Bluetooth SIG)对常用的一些数据类型进行了归类,赋予不同的数据类型不同的标识码(UUID)。例如0x2A09表示电池信息,0x2A6E表示温度信息。UUID可以是 16 比特的(16-bit UUID),也可以是 128 比特的(128-bit UUID)。
  3. 属性值(Attribute Value):属性值是每个属性真正要承载的信息,其他 3个元素都是为了让对方能够更好地获取属性值。有些属性的长度是固定的,例如电池属性(Battery Level)的长度只有 1个字节,因为需要表示的数据仅有 0~100%,而 1 个字节足以表示1~100的范围;而有些属性的长度是可变的,例如基于 BLE实现的透传模块。
  4. 属性许可(Attribute Permissions):每个属性对各自的属性值有相应的访问限制,比如有些属性是可读的、有些是可写的、有些是可读又可写的等等。拥有数据的一方可以通过属性许可,控制本地数据的可读写属性。

服务器和客户端的交互

我们把存有数据(即属性)的设备叫做服务器(Server),而将获取别人设备数据的设备叫做客户端(Client)。下面是服务器和客户端间的常用操作:

客户端给服务端发数据,通过对服务器的数据进行写操作(Write),来完成数据发送工作。写操作分两种,一种是写入请求(Write Request),一种是写入命令(Write Command),两者的主要区别是前者需要对方回复响应(Write Response),而后者不需要对方回复响应。

服务端给客户端发数据,主要通过服务端指示(Indication)或者通知(Notification)的形式,实现将服务端更新的数据发给客户端。与写操作类似,指示和通知的主要区别是前者需要对方设备在收到数据指示后,进行回复(Confirmation)。

客户端也可以主动通过读操作读取服务端的数据。

android 断开蓝牙HID 设备_串口


例如 PC 要获取传感器的数据时,PC 为 GATT Client,传感器为 GATT Server。

android 断开蓝牙HID 设备_数据_02

ESP32 蓝牙主机与控制器

从整体结构上,蓝牙可分为控制器(Controller)和主机(Host)两大部分:控制器包括了PHY、Baseband、Link Controller、Link Manager、Device Manager、HCI 等模块,用于硬件接口管理、链路管理等等;主机则包括了L2CAP、SMP、SDP、ATT、GATT、GAP以及各种规范,构建了向应用层提供接口的基础,方便应用层对蓝牙系统的访问。主机可以与控制器运行在同一个宿主上,也可以分布在不同的宿主上。

android 断开蓝牙HID 设备_串口_03

硬件连接

经典蓝牙

本章硬件无连线,使用蓝牙连接开发板和电脑,使用开发板端的蓝牙转串口,虚拟出一个串口设备。电脑端连接开发板蓝牙后,也会虚拟出一个串口,使用两个串口来通信。

android 断开蓝牙HID 设备_android 断开蓝牙HID 设备_04

软件设计

经典蓝牙

打开例程

本章使用Arduino的例程,选择示例—BluetoothSerial—SerialToSerial

android 断开蓝牙HID 设备_网络_05


打开后修改一下蓝牙名称和配对密码即可,不修改也行。

android 断开蓝牙HID 设备_数据_06


编译运行信息如下。

android 断开蓝牙HID 设备_android 断开蓝牙HID 设备_07

连接蓝牙

使用电脑端的蓝牙搜索。能看到已经有了。点击连接。正常会要求输入配对密码,输入后连接。其会安装一些文件。连接成功后会虚拟出一个串口。(此处为 COM11)

android 断开蓝牙HID 设备_android 断开蓝牙HID 设备_08


android 断开蓝牙HID 设备_网络_09


android 断开蓝牙HID 设备_数据_10

打开虚拟串口

设定如下的参数后,打开 COM11。

android 断开蓝牙HID 设备_串口_11

验证功能

此时也打开Arduino的串口监视器。设定波特率 115200。

android 断开蓝牙HID 设备_#define_12

android 断开蓝牙HID 设备_数据_13


点击串口助手中的发送按钮,Arduino的串口监视器中就出现了发送的字符了。也可以从串口监视器发送字符到串口助手,双向通信没问题。

android 断开蓝牙HID 设备_android 断开蓝牙HID 设备_14


经过以上步骤,板子就可以和电脑实现无线通信了。

完整代码
//This example code is in the Public Domain (or CC0 licensed, at your option.)
//By Evandro Copercini - 2018
//
//This example creates a bridge between Serial and Classical Bluetooth (SPP)
//and also demonstrate that SerialBT have the same functionalities of a normal Serial

#include "BluetoothSerial.h"

//#define USE\_PIN // Uncomment this to use PIN during pairing. The pin is specified on the line below
const char \*pin = "3625"; // Change this to more secure PIN.

String device_name = "CC-BT";

#if !defined(CONFIG\_BT\_ENABLED) || !defined(CONFIG\_BLUEDROID\_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

#if !defined(CONFIG\_BT\_SPP\_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin(device_name); //Bluetooth device name
  Serial.printf("The device with name \"%s\" is started.\nNow you can pair it with Bluetooth!\n", device_name.c\_str());
  //Serial.printf("The device with name \"%s\" and MAC address %s is started.\nNow you can pair it with Bluetooth!\n", device\_name.c\_str(), SerialBT.getMacString()); // Use this after the MAC method is implemented
  #ifdef USE\_PIN
    SerialBT.setPin(pin);
    Serial.println("Using PIN");
  #endif
}

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}

BLE

由于微信官方文档-小程序指南中写了小程序目前不支持经典蓝牙,只支持蓝牙低功耗(BLE)。所以就有了下面 BLE 的内容。

android 断开蓝牙HID 设备_数据_15

打开例程

本章使用Arduino的例程,选择示例—ESP32 BLE Arduino—BLE_uart

android 断开蓝牙HID 设备_串口_16


看例程前面的注释,含义如下。

例程创建了一个 BLE 服务器,有三个 UUID,第一个为设备 UID,第二个用于发送带有“Notify”的数据,第三个用于接收带有“Write”的数据。

创建 BLE 服务器有以下几步:

  1. 创建 BLE Server。
  2. 创建 BLE Service。
  3. 在 BLE Service 上创建 BLE Characteristic。
  4. 在 BLE Characteristic 上创建 BLE Descriptor。
  5. 启动服务。
  6. 开始广播。
修改例程

在下图中的网址中生成 3 组 UUID。

android 断开蓝牙HID 设备_串口_17


android 断开蓝牙HID 设备_android 断开蓝牙HID 设备_18


再将 3 组 UUID 分别填入SERVICE_UUIDCHARACTERISTIC_UUID_RXCHARACTERISTIC_UUID_TX的位置,代码如下。

#define SERVICE\_UUID "2c3eaaaa-6762-4039-8902-6fd7e51b81af" // UART service UUID
#define CHARACTERISTIC\_UUID\_RX "4de93100-e96b-436b-a07b-21de47c15af6"
#define CHARACTERISTIC\_UUID\_TX "5fb4a2c3-d9cb-4f0e-9c6b-7ed3a60800be"

编译后下载。

android 断开蓝牙HID 设备_网络_19

连接蓝牙

打开手机蓝牙,安装蓝牙调试助手,我用的是下面这款。

android 断开蓝牙HID 设备_数据_20


搜索蓝牙设备。名称是代码中默认的UART Service

// Create the BLE Device
  BLEDevice::init("UART Service");

android 断开蓝牙HID 设备_数据_21

android 断开蓝牙HID 设备_#define_22


点击即可配对。能看到SERVICE_UUIDCHARACTERISTIC_UUID_RXCHARACTERISTIC_UUID_TX是之前我们设置好的值。如果SERVICE_UUID不是设置的值,则可能是下面的原因。

android 断开蓝牙HID 设备_串口_23

验证功能

在手机端连接UART Service,再断开。会出现下面开始广播的提示。

android 断开蓝牙HID 设备_网络_24


android 断开蓝牙HID 设备_网络_25


在连接的页面选择最下面的 UUID 为4de93100-e96b-436b-a07b-21de47c15af6的特征值。然后输入要发送的字符串,点击写入。就能在 ESP32 的串口看到发送的数据了。

android 断开蓝牙HID 设备_#define_26

android 断开蓝牙HID 设备_网络_27


android 断开蓝牙HID 设备_数据_28


也可以选择 16 进制发送。

android 断开蓝牙HID 设备_android 断开蓝牙HID 设备_29

android 断开蓝牙HID 设备_#define_30


16 进制发送的 0x23,接收到的数值变成了“#”,这是因为此处是将收到的数据按照数据的Ascii码打印的,16 进制的 0x23 其Ascii码为“#”。

android 断开蓝牙HID 设备_#define_31


android 断开蓝牙HID 设备_网络_32


要想将收到的数据按照 16 进制打印,修改一下给打印函数的传参,编译下载,发送 16 进制 0x23 即可。

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], HEX);

      Serial.println();
      Serial.println("\*\*\*\*\*\*\*\*\*");
    }
  }
};

android 断开蓝牙HID 设备_串口_33

完整代码

以下代码是在 VS Code 环境下,采用platformio插件创建的工程。

#include <Arduino.h>

/\*
 Video: https://www.youtube.com/watch?v=oCMOYS71NIU
 Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp\_utils/tests/BLE%20Tests/SampleNotify.cpp
 Ported to Arduino ESP32 by Evandro Copercini

 Create a BLE server that, once we receive a connection, will send periodic notifications.
 The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
 Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
 Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY"

 The design of creating the BLE server is:
 1. Create a BLE Server
 2. Create a BLE Service
 3. Create a BLE Characteristic on the Service
 4. Create a BLE Descriptor on the characteristic
 5. Start the service.
 6. Start advertising.

 In this example rxValue is the data received (only accessible inside that function).
 And txValue is the data to be sent, in this example just a byte incremented every second.
\*/
#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 "2c3eaaaa-6762-4039-8902-6fd7e51b81af" // UART service UUID
#define CHARACTERISTIC\_UUID\_RX "4de93100-e96b-436b-a07b-21de47c15af6"
#define CHARACTERISTIC\_UUID\_TX "5fb4a2c3-d9cb-4f0e-9c6b-7ed3a60800be"

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: ");