前言
最近的作业项目要求中有一条是"结合嵌入式硬件实现"软硬结合
",正经的嵌入式学不会,只能用Arduino库函数划划水这样子.
- 开发板方面,由于考虑到是将开发板作为蓝牙信标的需求,选用了ESP32系列的板子.
- 初步设计:开发板通过WiFi网络以HTTP Post方式上报
设备蓝牙MAC与名称
,后端下发随机字符识别码,开发板以BLE Advertising的方式将识别码广播,App端按照蓝牙MAC与名称查找设备,接受并上传识别码,后端比对两端识别码从而确保整个流程的正确性. - 开发环境使用
VSCode+PlatformIO
. App端开发语言为Flutter.
开发板配网过程实现
要想建立与后端的HTTP链接,首先需要完成开发板的配网过程使开发板连上网络.通常使用的方法主要有AP热点配网(开发板开启AP,App链接AP上传WiFi信息),SmartConfig(通过发送 UDP 的广播包或多播包,并利用报文的特征,例如长度变化进行编码WiFi信息),BLE配网(链接BLE传输信息)
.AP热点配网需要连接热点,在App端可能触发Android高版本权限限制,SmartConfig在Flutter的生态尚不完善,因此我们选择了BLE配网的方法.(实际上我们采取的方案并不够安全,WiFi信息的传递应当加密进行)
在实现BLE连接功能之前,应先了解GATT(Generic Attribute Profile)
,GATT 定义了属性(Attribute),作为通用的封装数据的单位,并定义了如何通过蓝牙连接传输属性从而达到传输数据的目的.而蓝牙技术联盟沿用经典蓝牙的规范内容,为蓝牙低功耗定义了一些 profile,这些 profile 定义了一个设备在特定应用情景下如何工作.制造商应通过在实现中遵循特定的 profile 以确保兼容性.一台设备可以使用多个 profile.这些profile被称为GAP(Generic Access Profile). GAP 用来控制设备连接和广播。 它使你的设备被其他设备可见,并决定了你的设备是否可以以及怎样与合同设备进行交互.例如 Beacon 设备就只是向外广播,不支持连接,小米手环就等设备就可以与中心设备连接.
GAP 给设备定义了若干角色,其中主要的两个是:外围设备(Peripheral)和中心设备(Central).
- 外围设备:这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备。例如小米手环。
- 中心设备:中心设备相对比较强大,用来连接其他外围设备。例如手机等。
在这里也罗列一下 GATT 相关术语.(来自Wiki百科)
- 客户端 (Client): 一个发出 GATT 命令和请求的设备,然后接受响应。例如一个计算机或智能手机。
- 服务器 (Server): 一个接受 GATT 命令和请求的设备,然后返回响应。例如一个温度传感器。
- 特征 (Characteristic): 在客户端与服务器间传递的数据值,例如当前的电池电压。GATT 事务中的最低界别的是 Characteristic,Characteristic 是最小的逻辑数据单元。和 BLE 外设打交道,主要是通过 Characteristic.(可以将其视为通信管道,如Go中的Channel/Java中的队列,在权限允许的情况下,一端可以Read/Write数据,开发板端可定义回调函数相应外部更改,当开发板端写入数据后,也可以Notify另一端.)
- 服务 (Service): 有关特征的收集,具有一系列操作来执行特定功能。例如,“体温计” 服务包括一个温度测量值,以及测量的时间间隔。
描述符 (Descriptor: 描述符提供有关特征的其他信息。例如指示一个温度值特征的单位(如摄氏度),以及传感器可以测量的最大值和最小值。描述符是可选的——每个特征可以有任何数量的描述符。
因此,我们具体做的流程为:当开发板未检测到保存的WiFi信息时,进入初始化流程,发布一个特定UUID的初始化服务(Server).App端(Client)按照服务UUID搜寻BLE设备,并建立连接.初始化服务包括两个Characteristic,一个为传输数据,可读写,另一个为传输状态(成功/失败),可读写并Notify.(理论上也可以使用一个Characteristic来传输数据并返回结果,这样似乎也符合直觉,但是(更可能是由于个人编码能力)Flutter端的BLE库在这种情况下Notify会出现问题,只能放弃.)建立连接后,App按照特定格式传输信息,开发板解析并尝试连接WiFi,连接成功后在状态Characteristic中写入成功状态,App端反馈给用户.确认连接成功后,开发板将信息写入非易失存储(这里我采用的是adruinoNVS这个库,可提供类SharedPreferance的键值对存储),保证下次上电依旧可用.
开发板广播数据实现
考虑到开发板对App端的数据传送可能是1对多的场景,传统蓝牙限制了同时连接7台设备,BLE未对此作出规定.为了安全起见,我们决定在不连接的情况下使App端获取到开发板发送的特征值.BLE协议针对这种场景提供了Manufacturer Specify Data的选项.当BLE设备接收到其他设备的扫描帧时,会回复一个Scan Response.Scan Response除了包括设备名称,MAC地址,电量等信息,还可以由设备制造商写入特定值.在Ardruino中,我们使用setScanResponse
函数就可以指定Scan Response.而setManufacturerData
可以写入设备制造商指定值,这个值由设备商ID和其他信息(即我们要写入的特征值)组成.实例代码如下
void setManData(String c, int c_size, BLEAdvertisementData &adv, int m_code) {//c为需要广播字符串,c_size为长度
String s;
char b2 = (char)(m_code >> 8);
m_code <<= 8;
char b1 = (char)(m_code >> 8);
s.concat(b1);
s.concat(b2);
s.concat(c);
adv.setName(BLE_Tag.c_str()); //BLE_Tag为设备名称
adv.setManufacturerData(s.c_str());
}