目录
低功耗蓝牙(android&ios)设备需要广播:
参数说明:
参数名 | 描述 |
---|---|
ServiceUUID | 0xFCE7 |
Write Characteristics UUID | 0xFCC7 |
Indicate Characteristics UUID | 0xFCC8 |
Read Characteristics UUID | 0xFCC9 |
厂商自定义字段:
广播包格式如下:(图片来自蓝牙官方文档)
数据包长度为31字节, 考虑到部分芯片商预留字段不可修改,我们自定义数据段尽量简短。
仅定义21字节,以0xEE表示开始。
格式参考:
设备状态 | 固定填0x02 |
---|---|
设备ID | 在服务商管理端,登记设备后获取 |
sign | 预留字段,填0即可 |
需要在广播包中加入签名,为了防止员工抓取蓝牙信号伪造打卡,广播包中还需要增加一个自增的nonce。长度25字节,以0xED表示开始。
格式参考:
设备状态格式参考如下:
5bit | 1bit | 2bit |
---|---|---|
预留 | 电量 | 绑定状态 |
- | 0:高 1:低 | 1:已绑定 2:未绑定 |
广播包每隔5分钟变化一次,nonce必须比前一次大(取值范围在0~60),sign由特定函数生成。
sign生成算法:
sign= Hi64Bit(Md5(seed|(nonce)));
其中:
1、seed为哈希种子,默认为预先烧录的secretNo,并且企业微信云端允许设备联网登陆后刷新seed。(建议一天更新一次seed,如果设备没有网络模块可以不刷新seed)
2、nonce为随机数(uint32)
3、|表示字符串拼接
4、Hi64Bit表示取md5结果的高64位
参考代码如下:
uint64_t GetSign(const string & sSeed, uint32_t nonce)
{
string data = StrFormat("%s%u", sSeed.c_str(), nonce); //拼接
MD5 md5;
md5.update((unsigned char *)data.c_str(), data.size());
md5.finalize();
char sMd5[16];
md5.raw_digest2((unsigned char *)sMd5);
uint64_t res;
memcpy(&res, sMd5, sizeof(res));
return res;
}
StrFormat为字符串拼接函数,Md5为Md5类,厂商根据自己的开发情况替换。
当设备电量低于10%应设置device_status
Read Characteristics:
为了支持手机给设备配网,该网络需要配置设备MAC地址白名单,设备需要在企业微信的service下面,暴露一个read character,内容包含6字节的MAC地址。
格式说明:
6字节 | 2字节 |
---|---|
mac地址 | 蓝牙协议版本号 |
目前假定设备都是BLE蓝牙设备,网络模块均为wifi。
数据包使用私有的协议格式。透传数据包可以模拟数据流。
企业微信规定了蓝牙BLE设备需要先模拟成流(即stream,输入输出流)。流具有的特性有:
显然,蓝牙BLE无法传输无限长度的数据,为了实现这个目的,需要定义一个规范。
蓝牙设备需暴露两个特征值(Characteristics):Write特征值,Indication特征值。蓝牙设备从Write特征值接受数据,从Indication特征值发送数据。
Indication特征值类型是bytes。
这里我们约定,把一个特征值一次传输的数据,称为一帧(不同类型的特征值一次传输的数据长度是不一样的)。
注意:应用层上的数据包(例如1k大小),会分散成许多帧来传输。
蓝牙设备写过程:
蓝牙设备读过程:
当蓝牙设备发现读特征值收到数据的时候,就接收数据,并追加到设备的buf里。
注意:蓝牙设备必须等企业微信app订阅了Characteristics之后,才能indicate数据,否者会造成设备发送数据丢失的问题。
注意:企业微信收发取数据为大端字节序。
业务层面的数据流采用定长包头+变长包体的形式
定长包头:
struct PkgFixHead
{
unsigned char bMagicNumber; // 1 bytes
unsigned char bVer;
unsigned short nLength;
unsigned short nCmdId;
unsigned short nSeq;
unsigned char nProtoType;
};
变长包体:
二进制流数据
参数说明:
字段 | 类型 | 说明 |
---|---|---|
bMagicNumber | unsigned char | 填0xFE |
bVer | unsigned char | 包格式版本号,填1 |
nLength | unsigned short | 为包头+包体的长度 |
nCmdId | unsigned short | 命令号,表示要调用哪个接口 |
nSeq | unsigned short | 递增。一个Req对应一个Resp,并且它们的nSeq相同,并且永不为0。Push的nSeq永远为0; |
nProtoType | unsigned char | 表示数据类型,目前0表示json |
定长包头cmdid: cmd_push_set_wifi = 30003;
请求数据格式:json
数据示例:
{
"ssid":"xxx",
"bssid":"xxxx",
"password":"xxxx"
}
参数说明:
参数名 | 是否必须 | 类型 | 描述 |
---|---|---|---|
ssid | 是 | String | Wi-Fi ssid |
bssid | 否 | String | Wi-Fi bssid |
password | 否 | String | Wi-Fi 密码 |
protocol | 否 | String | Wi-Fi 协议,目前仅支持: None WEP WPA WPA2 |
接口说明:
如果蓝牙协议版本号为0x02时,企业微信进入查看设备详情页会发出该指令,设备接收后应调用“上报设备状态”接口上报对应数据。
接口调用说明:
定长包头cmdid: cmd_push_fetch_device_status = 30004;
请求数据格式:空包
接口说明:
App设置/重置wifi时,通过该指令调取设备探测到的wifi信号
接口调用说明:
定长包头cmdid: cmd_push_get_wifi_list = 30005;
请求数据格式:
{
"req_id":"xxx",
"limit":10
}
设备接收到该事件后应调用 上报wifi信号列表
参数说明:
参数名 | 是否必须 | 类型 | 描述 |
---|---|---|---|
req_id | 是 | String | 请求id,设备回包时也应该带上次id |
limit | 是 | uint32 | 返回wifi信息条数,若探测到多个wifi信号,返回信号最强的前limit条即可 |
接口说明:
蓝牙匹配后调用,发起握手请求。
接口调用说明:
定长包头cmdid: cmd_req_handshake = 10001;
请求数据格式:json
数据示例:
{
"client_nonce":"123451",
"sn":"JAS6007",
"scene":"handshake"
}
参数说明:
参数名 | 是否必须 | 类型 | 描述 |
---|---|---|---|
sn | 是 | String | 设备序列号 |
client_nonce | 是 | String | 64位整型随机数的字符串表示 |
scene | 是 | String | 固定为handshake |
返回说明:
定长包头cmdid:cmd_resp_handshake = 20001;
JSON数据包示例:
{
"errcode": 0,
"errmsg": "ok",
"server_nonce":"12354",
"signature":"7a83140e7bc7a4f75ac0bd094aca7474878e7f33"
}
参数名 | 描述 |
---|---|
errcode | 错误码 |
errmsg | 错误码描述 |
signature | 服务端返回的签名 |
server_nonce | 服务端产生的nonce, 64位整型随机数的字符串表示 |
签名计算方法:
HMAC_SHA1算法, signature= HMAC_SHA1(sort("wxwork", string(client_nonce), string(server_nonce), "handshake"))。sort的含义是将参数值按照字母字典排序,然后从小到大拼接成一个字符串。HMAC_SHA1指定key为预先烧录的secretNo。
示例:
client_nonce=123451
server_nonce=12354
secretNo=3b00147353d569ac9a4e21063d6xxxxx
sort("wxwork", string(client_nonce), string(server_nonce), "handshake")=12345112354handshakewxwork
signature= HMAC_SHA1(12345112354handshakewxwork)=7a83140e7bc7a4f75ac0bd094aca747487xxxxx
接口说明:
设备调用接口 发起握手 后,server端返回server签名,此处设备应计算server签名是否合法,不合法则断开链接。合法则确认握手
接口调用说明:
定长包头cmdid: cmd_req_confirm_handshake = 10002;
请求数据格式:json
数据示例:
{
"signature":"e03d6219fb787764747967127a344deef85xxxxx"
}
参数说明:
参数名 | 是否必须 | 类型 | 描述 |
---|---|---|---|
signature | 是 | String | 设备签名 |
签名计算方法:
HMAC_SHA1算法, signature= HMAC_SHA1(sort(Sn,string(server_nonce),"handshake"))。sort的含义是将参数值按照字母字典排序,然后从小到大拼接成一个字符串。HMAC_SHA1指定key为预先烧录的secretNo。
示例:
sn=JAS6007
server_nonce=12354
secretNo=3b00147353d569ac9a4e21063d6xxxxx
sort(Sn,string(server_nonce),"handshake")=12354JAS6007handshake
HMAC_SHA1(12354JAS6007handshake)=e03d6219fb787764747967127a344deef85xxxxx
返回说明:
定长包头cmdid:cmd_resp_confirm_handshake = 20002;
JSON数据包示例:
{
"errcode": 0,
"errmsg": "ok",
"bind_status":1
}
参数名 | 描述 |
---|---|
errcode | 错误码 |
errmsg | 错误码描述 |
bind_status | 设备是否已绑定,0:未绑定/已解绑 1:已绑定。当errcode非0时忽略该字段。 |
注意:签名错误终端会断开连接。
接口说明:
设备连上网络成功或者失败后应主动调用该接口上报状态。
接口调用说明:
定长包头cmdid: cmd_req_report_device_status = 10004;
请求数据格式:json
数据示例:
{
"errcode":0
"timestamp":1493913600,
"wifi_connected":true,
"ip_address": "10.9.248.30",
"mac_address": "B0:E5:ED:74:80:D1",
"wifi_name":"wtf"
}
参数说明:
参数名 | 是否必须 | 类型 | 描述 |
---|---|---|---|
errcode | 是 | Int32 | 联网错误码。 0: 成功 1001: wifi不存在 1002: 密码错误 1003: 连接中 |
timestamp | 是 | Uint32 | 时间戳 |
wifi_connected | 是 | Bool | 是否已连接Wi-Fi |
ip_address | 是 | String | 设备IP地址 |
mac_address | 是 | String | 设备MAC地址,联网失败时也需要带上该值 |
wifi_name | 否 | string | wifi名 |
返回说明:
定长包头cmdid:cmd_resp_report_device_status = 20004;
JSON数据包示例:
{
"errcode": 0,
"errmsg": "ok",
}
参数名 | 描述 |
---|---|
errcode | 错误码 |
errmsg | 错误码描述 |
接口说明:
当设备收到调取设备探测到的wifi信号列表推送(cmd:30005)后,应调用本接口上报wifi列表。
接口调用说明:
定长包头cmdid: cmd_req_report_wifi_list = 10005;
请求数据格式:json
数据示例:
{
"req_id":"xxx"
"wifi_info":
[
{
"ssid":"xxx",
"rssi":0,
"need_password":false
},
{
"ssid":"xxx",
"rssi":-50,
"need_password":true
}
]
}
参数说明:
参数名 | 是否必须 | 类型 | 描述 |
---|---|---|---|
req_id | 是 | string | 请求id,透传即可 |
ssid | 是 | String | Wi-Fi ssid |
rssi | 是 | int32 | 信号强度,单位dbm |
need_password | 是 | bool | 是否需要密码 |
返回说明:
定长包头cmdid:cmd_resp_report_wifi_list = 20005;
JSON数据包示例:
{
"errcode": 0,
"errmsg": "ok",
}
参数名 | 描述 |
---|---|
errcode | 错误码 |
errmsg | 错误码描述 |
对于Android设备,提供以SDK植入的方式,供企业微信发现该设备,并使其收到设置WIFI的指令。查看SDK文档。
2020/01/13
1.确认握手回包增加bind_status字段。
2.增加30005指令,支持调取设备探测到的wifi信号列表。
3.增加10005,20005指令,支持设备上报探测到的wifi信号列表。
2019/12/16
1.Read Characteristics增加蓝牙协议版本号字段。
2.0x02蓝牙协议增加30004指令,支持企业微信app实时抓取设备信息。
3.0x02蓝牙协议10004接口增加wifi_name字段。