2.3 门禁设备与手机蓝牙对接协议
1 通讯方式简介
1.1 拓扑结构
星形拓扑结构:门口机作为主设备,APP 为从设备。主设备管理连接,主设备只能连接一个从设备;一个从设备只能连接一个主设备。
1.2 设备状态
蓝牙门口机作为从设备,共有以下三种状态:
待机状态(standy) | 设备没有传输和发送数据,并且没有连接到任何设备 |
广播状态(advertiser) | 周期性广播状态,等待被连接 |
从设备(slave) | 作为从设备连接其他设备 |
1.2.1 状态以及连接过程
蓝牙门口机不用连接就可以广播数据(门口机名称和其他信息),手机 APP 扫描广播设备,蓝牙门口机接受到扫描后会发送一个扫描回应(扫描回应数据就是广播数据),APP 接收到扫描回应后发起连接,如果蓝牙门口机同意该连接请求,那么门口机和手机都会进入已连接状态,发起连接的设备手机变为主设备,接受连接请求的设备门口机变为从设备。
1.3 蓝牙门口机的连接参数
1.3.1 蓝牙门口机的广播事件
广播包发送是单向的,不需要任何的连接。广播包里可以包含特定的数据定义,最大 31 字节。
广播间隔:两次广播事件之间的最小的时间间隔,一般取值范围在 20ms~10.24s 之间,链路层会在每次广播时间期间产生一个随机广播延时时间加在广播时间里,这样来避免多设备之间的数据碰撞。
1.3.2 蓝牙连接参数
连接参数如下:
参数 | 说明 |
通道映射 | 连接使用的频道 |
调频增量 | 一个 5~16 之间的随机 |
连接间隔 | 1.25ms 的倍数,7.5ms~4s |
监督超时 | 10ms 的倍数,100ms~32s |
从机潜伏 | 0~499 |
1.3.3 BLE 连接事件
所有的通信都发生在两个设备的连接事件期间,连接事件周期的发生,按照连接参数指定的间隔联系,每个连接时间期间,主设备先发送,从设备会在 150us 之后做出回应,即使一个连接事件发生,但相互没有数据发送,两个设备仍然认为对方存在并保持活跃的连接。
潜伏值:从设备如果没有数据发送,允许跳过连接时间,潜伏值是允许跳过的最大的连接次数。从机不潜伏,在每个连接事件期间回应一个“空包”,除非有数据发送;从机潜伏使能, 从机可以跳过n 个连接时间,除非有数据发送才被唤醒。
1.3.4 终止连接
Master 和slave 都可以主动断开连接。
1.4 GATT 的client/service 架构
1.4.1 GATT 层
GATT 是 BLE 协议栈的通用属性配置文件层,规定了配置文件(profile)的结构。
1.4.2 GATT 的 Profile 层次结构
Profile 通常由一个或者多个服务(service)组成。每个 service 包含一个或多个特征值characteristic 特征值。每个具体的 characteristic 特征值才是 BLE 通信的主体,characteristic 特征值可以理解为标签,通过这个标签可以获取和写入想要的内容。
1.4.3 characteristic 属性
characteristic 的属性有以下几种:read,constant,write,write_no_response,notify,indicate, authenticated_read,authenticated_write,reliable_write。
2 通讯协议
2.1 手机app 与蓝牙门口机的交互过程
一旦建立连接后,门口机不会主动断开连接,需由 APP 端主动断开。
2.2 通讯参数设置
BLE 4.0 协议为低功耗蓝牙规范,但暂时不考虑功耗问题,为了使蓝牙能够快速响应并处理传输数据,将连接间隔事件时间设置为接近最短时间,将扫描和广播的频率提高但是连接的两个设备都会以高能耗运行。
2.2.1 蓝牙连接参数
参数 | 说明(后期根据实际测试情况调整) |
通道映射 | 7: Advertise on all channels |
调频增量 | 默认 |
有连接广播间隔 | Min: 20ms (0.625ms*0x20) Max: 20ms (0.625ms*0x20) |
无连接广播间隔 | Min: 100ms (0.625ms*0xa0) Max: 100ms (0.625ms*0xa0) |
扫描间隔 | APP 根据情况自定义 |
扫描窗口 | APP 根据情况自定义 |
连接间隔 | Min: 7.5ms (1.25ms*6) Max: 12.5ms (1.25ms*10) |
监督超时 | 100ms |
从机潜伏 | 0 |
2.2.2 广播数据格式
蓝牙广播包数据为一串最大长度不超过 31 字节的数据,标准格式如下:
为了APP 能够找到设备,广播数据中加入了蓝牙设备名称、2 字节的incomplete ServiceUUID。
具体格式如下(各字段含义见 2.2.3 节广播数据格式说明):
字段含义 | ASCII 码值 | 字节含义 | 字节序列索引 |
蓝牙广播包头 | 0x02 | 长度(字节) | 0 |
0x01 | Flags | 1 | |
0x06 | 广播标志头 | 2 | |
蓝牙设备名称 | 0x11 | 长度(字节) | 3 |
0x09 | Complete Local Name | 4 | |
0x53 | ‘S’ | 5 | |
0x52 | ‘R’ | 6 | |
0x45 | ‘E’ | 7 | |
0x31 | ‘1’ | 8 | |
0x30 | ‘0’ | 9 | |
0x30 | ‘0’ | 10 | |
0x31 | ‘1’ | 11 | |
0x30 | ‘0’ | 12 | |
0x30 | ‘0’ | 13 | |
0x30 | ‘0’ | 14 | |
0x31 | ‘1’ | 15 | |
0x30 | ‘0’ | 16 | |
0x30 | ‘0’ | 17 | |
0x30 | ‘0’ | 18 | |
0x30 | ‘0’ | 19 | |
0x31 | ‘1’ | 20 | |
服务ID | 0x03 | 长度(字节) | 21 |
0x02 | Incomplete List of 16-bit Service Class UUIDs | 22 | |
0xff | UUID | 23 | |
0xfa | 24 | ||
自定义数据 | 0x05 | 长度(字节) | 25 |
0xff | Manufacturer Specific Data | 26 | |
.. | 厂商自定义数据: 4 字节随机数。 每次新的连接前都会更新随机数。 | 27 | |
.. | 28 | ||
.. | 29 | ||
.. | 30 | ||
广播包总大小: | 31 字节 |
2.2.3 广播数据格式说明
1. 具体蓝牙相关标志的细节请参考:https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
2. 蓝牙设备名称
规则(一共 16 字节长度):
门口机模式:
SRE+期号(1字节)+楼栋号(3字节)+单元号(4字节)+编号(2字节)+层号(3字节)
编号:主门口机使用 00 表示、从门口机从 01 开始(01,02 加 1 递增)
层号:分正楼层和负楼层(第一个字符为 0 表示正楼层,为 1 表示负楼层)
例如默认的1 期1 幢1 单元,放置在地上一层的主门口机的广播名称为:SRE1001000100001,放置在地下一层则为:SRE1001000100101
围墙机模式:
SRE+期号(1 字节)+0000000+编号(2 字节)+层号(3 字节)
围墙机模式下的楼栋号、单元号使用 0000000 代替,剩下的期号、编号、层号规则与门口机模式一致。
3. 服务 ID
广播中的 ID 是不完整的 16bit service UUID。
4. 自定义数据
厂商自定义数据,此处存放的是由时间加密因子计算出的 4 字节随机数,用于与双方约定的 16 字节固定密钥生成随机密钥,每次链接断开后都会更新 4 字节随机数。
2.2.4 服务和特征值说明
蓝牙门口机作为从机(服务器),手机作为主机(客户端)。蓝牙门口机新增一个服务(service),在此服务中添加characteristic 特征值,作用及命名如下。
特征值名称 | 长度 | 属性 | 作用 |
发送指令 (send command) | n | write | 手机 APP 将要发送的指令报文分包写入此特征值, 每包小于等于下文“传输协议”中约定的字节数。 |
返回指令 (return command) | n | read | 门口机将返回操作的内容以及状态报文写入此特征值,并通过写的方式发送给手机 APP。 |
2.2.5 设备蓝牙的UUID
名称 | 不完整值 | 完整值 |
Service UUID | 0xFFFA | f6ecfffa-bda1-46ec-a43a-6d86de88561d |
Write Characteristics UUID | 0xFFA7 | af20ffa7-2518-4998-9af7-af42540731b3 |
Read Characteristics UUID | 0xFFA8 | af20ffa8-2518-4998-9af7-af42540731b4 |
注:上表中的 read、write 均是从设备端蓝牙芯片来看的。
2.3 传输协议
2.3.1 APP->门口机
帧头 0X24 1 BYTE | 返回结果类型 1 BYTE | 权限有效长度 1 BYTE | 权限内容 n BYTE | 累加和 checksum 1 BYTE |
说明:
1. 返回结果类型:
0x00:表示门口机收到 APP 开门指令后,将会进行数据格式+权限内容校验,并将校验之后的结果返回给手机 APP(校验通过说明蓝牙开门成功)。
0x01:表示门口机收到 APP 开门指令后,将只校验数据格式,并不关心权限内容,如果数据格式正确则返回开门成功消息给手机 APP。
2. 权限有效长度:
加密后的权限内容长度。
3. 权限内容:
标红色的权限内容字段为 AES 加密目标,用于存放开门卡号等信息。此处 nByte 表示加密后的数据长度。n 取值:0~n,暂定最大 32 字节,如果超过 32 字节则需要双方重新约定。如果加密前权限长度小于等于 15 字节,则此处加密后的长度为 16 字节;如果是大于 15 字节,小于等于 31 字节,则此处加密后的长度为 32 字节。因此最大的权限长度为 31 字节。
如果 APP 端传递的不是开门卡号,则需要另行商定。
4. 累加和:
校验位采用累加和校验,从帧头开始到权限内容最后一个字节的累加和对 256 取余,计算的是加密之后的累积和。
2.3.2 门口机->APP
帧头 0X24 1 BYTE | 保留 2 BYTE | 开门结果反馈 1 BYTE | 累加和 checksum 1 BYTE |
说明:
返回给 APP 的开门响应数据不会被加密。
开门结果反馈的状态码列表如下:
返回码 | 中文描述 | 英文描述 |
0x01 | 校验错误 | CS_ERROR |
0x02 | 帧格式不正确 | FRAMEFORMAT_ERROR |
0x03 | 认证失败 | NO_PERMISSION_ERROR |
成功信息 | ||
0x00 | 成功 | OK |
2.4 加密
2.4.1 密钥说明
为确保安全性,需要保证每个对接项目的 APP 不可相互开门,此次定制的门口机程序,只能和本地门口机程序对应的 APP 通讯开门。
因此将 16 字节密钥按照如下约定生成:
定制单编号:如:DZP20200117037
长度为 14 字节,后面填 0 扩充至 16 字节:DZP2020011703700
转换为 ASCII 码:
N[16]={0x44, 0x5A, 0x50, 0x32, 0x30, 0x32, 0x30, 0x30, 0x31, 0x31, 0x37, 0x30, 0x33, 0x37, 0x30, 0x30};
在开发APP 前需获取本次对接时的定制单编号!
2.4.2 通信帧加密
采用 128 位密钥的 AES 加密 CBC 模式(初始化向量:“1234567890abcdef”,填充模式为zeropadding),只对协议格式里面DATA 数据区加密,密钥的生成如下:
1.设备存储一个固定密钥(初始密钥):
N[16]={0x44, 0x5A, 0x50, 0x32, 0x30, 0x32, 0x30, 0x30, 0x31, 0x31, 0x37, 0x30, 0x33, 0x37, 0x30, 0x30};
2.生成一个 4 字节的随机数组:
R[4]={0x45,0x18,0x9F,0x5C};
3.如上图所示,按照间隔 4 的大小,将 1、5、9、13 字节固定密钥与随机数组 1 字节进行与操作,得到新的 1、5、9、13 字节动态密钥;其他字节也按照上图所示进行加密,最终得到用于实际 AES128 加解密处理的 16 字节动态密钥。
说明:“加”操作超出 0xff 取余;随机数在每次断开连接后会重新生成,所以每次操作的密钥都不相同。
举例:
固定密钥:
N[16]={0x44, 0x5A, 0x50, 0x32, 0x30, 0x32, 0x30, 0x30, 0x31, 0x31, 0x37, 0x30, 0x33, 0x37, 0x30, 0x30};
随机数:
{0x45,0x18,0x9F,0x5C};
则得到的新的密钥为:
{0x44, 0x72, 0xDF, 0x6E, 0x00, 0x4A, 0xBF, 0x6C, 0x01, 0x49, 0xBF, 0x6C, 0x01, 0x4F, 0xBF, 0x6C}
转换代码 :
for(i=0; i<4; i++) {
szDynamicKey[4*i] = gDefaultAesKey[4*i]&gRandNum[0];
szDynamicKey[4*i+1] = gDefaultAesKey[4*i+1] + gRandNum[1];
szDynamicKey[4*i+2] = gDefaultAesKey[4*i+2] | gRandNum[2];
szDynamicKey[4*i+3] = gDefaultAesKey[4*i+3] ^ gRandNum[3];
}
2.4.3 密钥一致性
本章节通过举例方式列出几种待加密数据的加密结果,供三方 APP 开发时参考,确保双方使用”相同待加密数据、相同固定密钥、相同 4 字节随机数”加密得到的密文一致。
数 据长度 | 待加密数据 | 固定密钥 | 4 字节随机数 | 随机密钥 | 加密后密文 (16 进制 byte array) |
14 | "12345601010702" | DZP2020011703700 | 0x45, 0x18, 0x9F, 0x5C | '0x44', '0x72', '0xdf', '0x6e', '0x0', '0x4a', '0xbf', '0x6c', '0x1', '0x49', '0xbf', '0x6c', '0x1', '0x4f', '0xbf', '0x6c' | '0xf7', '0x2f', '0x0', '0xed', '0xfc', '0x2a', '0x83', '0xcd', '0xc9', '0x6c', '0x5', '0xbc', '0x95', '0x64', '0xa6', '0x75' |
16 | "1234567812345678" | DZP2020011703700 | 0x45, 0x18, 0x9F, 0x5C | '0x44', '0x72', '0xdf', '0x6e', '0x0', '0x4a', '0xbf', '0x6c', '0x1', '0x49', '0xbf', '0x6c', '0x1', '0x4f', '0xbf', '0x6c' | '0xce', '0x53', '0x38', '0x7f', '0x11', '0x5d', '0xf4', '0xaa', '0xed', '0x9d', '0x1c', '0xe8', '0x74', '0x5d', '0x39', '0xe1', '0xeb', '0xdc', '0x81', '0x62', '0x35', '0x38', '0x78', '0xee', '0x34', '0x11', '0xd0', '0xfa', '0xcd', '0x39', '0x98', '0xa' |
31 | "1234567812345678123456781234567" | DZP2020011703700 | 0x45, 0x18, 0x9F, 0x5C | '0x44', '0x72', '0xdf', '0x6e', '0x0', '0x4a', '0xbf', '0x6c', '0x1', '0x49', '0xbf', '0x6c', '0x1', '0x4f', '0xbf', '0x6c' | '0xce', '0x53', '0x38', '0x7f', '0x11', '0x5d', '0xf4', '0xaa', '0xed', '0x9d', '0x1c', '0xe8', '0x74', '0x5d', '0x39', '0xe1', '0x45', '0x3b', '0x2', '0xc0', '0x5a', '0x71', '0x35', '0x7a', '0xfa', '0x21', '0xf5', '0x14', '0xde', '0x43', '0x6f', '0xf9' |