想当初活动报名的时候,觉得这么长的时间,完成作品应该很轻松。实际上手才发现,时间根本不够用,主要平时只能利用业余时间搞一下。
本次作品名称为《基于BLE的智能门禁系统》,涉及到BLE、Thread和服务器三部分。目前仅完成了BLE部分,Thread部分第一次接触,信息量太大,还需要慢慢研究。虽然没有完成整个作品,但还是将已完成部分与大家分享一下。
图1 基于BLE的智能门禁系统框图
2.功能说明
整个系统包括BLE、Thread和服务器三部分。BLE部分主要完成身份识别,Thread部分完成数据的上传,服务器主要记录门禁信息。
两个开发板,一个做主机,一个做从机。从机广播蓝牙信号,提供各种服务,其为服务的提供者,因此又叫服务器(Server)。主机为控制中心,扫描蓝牙信号并连接到从机,相对于从机,为客户端(client)。
这里开发板2作为一个Beacon设备,一直处于广播状态。开发板1作为主机,处于扫描状态。当扫描到2的广播信号,通过RSSI值,可判断2的距离。当2处于1的有效距离范围内,1将会触发开门等一系列操作,这里通过1上的绿色LED来指示。当检测到2时,绿色LED点亮,否则熄灭。
软件设计思路
BLE部分
这里的代码基于官方SDK中的Beacon和HID_Host例程。
1.从机端:
开发板2做BLE从机,一直广播蓝牙信号,不可被连接,称为Beacon设备。
基于官方Beacon例程,需要修改的地方有:
- 修改设备地址(MAC Address)
- 修改广播的UUID
说明:
1.这里主机通过MAC地址来辨别不同设备,假设有两个Beacon设备广播相同的信息,通过MAC地址即可将他们区分开来。
2.广播UUID主要用于主机扫描时进行过滤。比如不是我们的Beacon设备广播的信息,直接忽略。
代码修改
接下来我们来看代码。
打开Beacon例程。
首先打开app_config.c文件,定位到如下代码:
const gapAdvertisingParameters_t gAppAdvParams = {
/* minInterval */ 800 /* 500 ms */,
/* maxInterval */ 1600 /* 1 s */,
/* advertisingType */ gAdvNonConnectable_c,
/* addressType */ gBleAddrTypePublic_c,
/* directedAddressType */ gBleAddrTypePublic_c,
/* directedAddress */ {0, 0, 0, 0, 0, 0},
/* channelMap */ (gapAdvertisingChannelMapFlags_t) (gAdvChanMapFlag37_c | gAdvChanMapFlag38_c | gAdvChanMapFlag39_c),
/* filterPolicy */ gProcessAll_c
};
该结构体为广播参数的设置。设置了广播间隔,广播类型,地址类型,广播信道等。Beacon设备一直广播,不可被连接,其广播类型为gAdvNonConnectable_c,不可连接的广播。
接下来定位到以下代码:
static const uint8_t adData0[1] = { (gapAdTypeFlags_t)(gLeGeneralDiscoverableMode_c | gBrEdrNotSupported_c) };
static uint8_t adData1[26] = {
/* Company Identifier*/
mAdvCompanyId,
/* Beacon Identifier */
mBeaconId,
/* UUID */
mUuid,
/* A */
0x00, 0x00,
/* B */
0x00, 0x00,
/* C */
0x00, 0x00,
/* RSSI at 1m */
0x1E};
static const gapAdStructure_t advScanStruct[] = {
{
.length = NumberOfElements(adData0) + 1,
.adType = gAdFlags_c,
.aData = (void *)adData0
},
{
.length = NumberOfElements(adData1) + 1,
.adType = gAdManufacturerSpecificData_c,
.aData = (void *)adData1
}
};
该结构体配置了广播数据。设备广播时,借助Dongle抓包可以查看到这些数据。
其中mAdvCompanyId和mBeaconId可用于区分不同的Beacon。这里mUuid为一个宏,将其值修改为:
#define mUuid 0xa7,0xbf,0x42,0xa2,0xfc,0x4d,0x49,0xf4,0xb2,0x2c,0xd6,0xf7,0x39,0x12,0x28,0xbe
然后在main_task()函数中,注释掉BleApp_Init()函数,其会产生一串随机数来修改mUuid的值。
接下来打开app_preinclude.h文件,修改MAC地址,如下:
#define BD_ADDR 0x12,0x34,0x56,0x78,0x9A,0xBC
通过以上几步简单的操作,Beacon设备的代码就修改完成了。接下来编译烧录并运行,使用Dongle抓取广播数据,看一下是否跟预期的一致。
广播数据抓包
这里使用TI的CC2540 USB Dongle,结合其软件,抓取蓝牙信号。如下图:
图2 Dongle抓包数据
抓取的广播数据和地址与我们上面定义的一致。
话说为什么NXP不提供USB Dongle呢?两块板子都给了,Dongle还舍不得吗?虽然开发板可以烧固件成Dongle,但像我这种两块板子一起使用的就尴尬了。
2.主机端:
开发板1做BLE主机,不断扫描蓝牙广播信号。
打开HID_Host例程,定位到 BleApp_ScanningCallback 函数中的CheckScanEvent函数,如下:
static bool_t CheckScanEvent(gapScannedDevice_t* pData)
{
uint8_t index = 0;
uint8_t name[10];
uint8_t nameLength;
bool_t foundMatch = FALSE;
while (index < pData->dataLength)
{
gapAdStructure_t adElement;
adElement.length = pData->data[index];
adElement.adType = (gapAdType_t)pData->data[index + 1];
adElement.aData = &pData->data[index + 2];
/* Search for Temperature Custom Service */
if ((adElement.adType == gAdIncomplete16bitServiceList_c) ||
(adElement.adType == gAdComplete16bitServiceList_c))
{
uint16_t uuid = gBleSig_HidService_d;
foundMatch = MatchDataInAdvElementList(&adElement, &uuid, sizeof(uint16_t));
}
if ((adElement.adType == gAdShortenedLocalName_c) ||
(adElement.adType == gAdCompleteLocalName_c))
{
nameLength = MIN(adElement.length, 10);
FLib_MemCpy(name, adElement.aData, nameLength);
}
/* Move on to the next AD elemnt type */
index += adElement.length + sizeof(uint8_t);
}
if (foundMatch)
{
/* UI */
shell_write("rnFound device: rn");
shell_writeN((char*)name, nameLength-1);
SHELL_NEWLINE();
shell_writeHex(pData->aAddress, 6);
}
return foundMatch;
}
先说一下HID主机例程是如何识别并连接到从机的。首先判断UUID类型是否是16位,若是则继续判断是否为0x1812,即HID服务。若是则说明匹配,主机将尝试与之连接。
接下来修改这一部分的代码,如下:
static bool_t CheckScanEvent(gapScannedDevice_t* pData)
{
bool_t foundMatch = FALSE;
uint8_t temp[32]; //用于调试使用
memcpy(temp,&pData->data[0],pData->dataLength);
//长度判断
if(pData->dataLength == 0x1F)
{
//UUID过滤
if(FLib_MemCmp(&temp[8],mUuid,16))
{
//MAC地址匹配
if(FLib_MemCmp(mac_address,&pData->aAddress,6))
{
//获取信号强度
beacon_rssi = pData->rssi;
foundMatch = true;
}
}
}
if (foundMatch)
{
/* UI */
shell_write("rnFound device: rn");
shell_write("rnMAC Address: rn");
shell_writeHex(pData->aAddress, 6);
}
return foundMatch;
}
通过一步步判断广播数据长度,UUID、MAC地址,筛选出目标设备。当三者都匹配时,获取信号强度,串口打印相关信息,并返回真值。这里使用MAC地址作为用户ID。
接下来先编译代码并调试一下,在长度判断处打一个断点,当程序运行到断点处,看一下temp变量的值,如下图:
图3 调试数据
与使用Dongle抓取的数据一致。
在BleApp_ScanningCallback函数中,会判断刚刚的返回值,若返回值为真,说明是我们的Beacon设备。然后再判断信号强度是否满足要求,即是否在有效距离范围内。为了便于测试,这里取-30dBm。当信号强度满足要求,则点亮绿色LED。具体代码如下:
if (CheckScanEvent(&pScanningEvent->eventData.scannedDevice))
{
Gap_StopScanning(); //停止扫描
if(beacon_rssi > -30)
{
Led3On();
}
else
{
Led3Off();
}
BleApp_Start(); //启动扫描
}
Thread、服务器部分
暂无
相关分享帖集锦
KW41Z开箱及导入SDK的问题
KW41Z之BLE(低功耗蓝牙)初探
KW41Z之BLE安全
作品源代码
上述代码分析,只对重要部分做了简单的讲解。其它地方,如广播间隔,扫描间隔等参数的选择就不一一分析了。具体参见源码。
演示视频
见文末。
说明:
开发板1(主机)静止不动,开发板2(Beacon)慢慢靠近主机,距离缩短到一定范围后,主机上面的绿色LED灯点亮。接下来Beacon慢慢远离主机,当超过一定距离后,主机上面的绿色LED灯熄灭。当让Beacon快速靠近主机又离开时,绿色LED不断闪烁。说明尽管Beacon移动很快,也能被主机迅速识别。
总结及展望
本次作品为基于BLE的智能门禁系统,分为BLE、Thread和服务器三部分,目前仅实现了BLE部分。在Beacon识别过程中,主机通过广播信息过滤设备,并通过MAC地址来区分不同的Beacon设备。由于使用MAC地址作为身份验证,这样即使其它设备克隆广播信息,也无法通过主机的验证。但还是存在安全隐患,即如果克隆者克隆了MAC地址,则主机会验证通过。因此后续仍需要做的是提高系统的安全性。
本方案中,可以再增加一个主机,即开发板1。这里假设原先的主机为A,新增的主机为B。将B放置于上下班必经之处,与A隔一段距离。上班的时候,B先检测到Beacon信号,之后是A。而下班的时候,顺序刚好相反。由此可判断出人的行为。
由于时间的原因,未能完全完成本次作品,但通过这次活动,收获了很多东西。对Thread有了初步的了解,对BLE有了更加深入的认识。在这里感谢EEWORLD和NXP(排名不分先后)举办此次活动。感谢幕后所有的工作人员!!!