# 协议介绍

蓝牙是一种短距的无线通讯技术,可实现设备与手机APP之间的数据交换。本协议规定了BLE蓝牙设备与手机APP通信涉及到的数据格式和功能指令,可用于实现设备数据读写、网络配置等近场设备操作。

# 设备连接

蓝牙设备与移动APP在进行数据交换通信前,需要先完成标准的连接操作。为了确保APP能够正确识别到设备的蓝牙广播信号,设备蓝牙广播标识名称应符合如下标准:

DELI_{产品型号}_{设备ID后6位}。例如产品型号为PT的设备ID为PT_12345678,则应设置广播名称为DELI_PT_345678。

APP一旦扫描到符合该命名规范的设备,便可以直接与该设备建立通信链路以便于后续的数据交换传输。

注意

如果设备ID不足6位,请左补零。另外,请尽量保证设备ID后6位具备不重复性,避免APP扫描到多台相同的蓝牙广播名称。

# 数据通讯规范

出于规范和一致性考虑,所有蓝牙通信应采用统一的传输方式以及消息报文格式。

# 蓝牙服务及特征值

蓝牙服务统一标准如下:

服务UUID(serviceID) 说明
0000ff00-0000-1000-8000-00805f9b34fb APP与设备蓝牙通讯服务UUID

特征值统一标准如下:

特征值UUID(characteristicID) 权限 说明
0000ff02-0000-1000-8000-00805f9b34fb write APP向设备发送数据
0000ff01-0000-1000-8000-00805f9b34fb notify 设备向APP发送数据

# 消息报文格式

消息报文结构说明如下:

包头 消息指令 负载长度 负载数据 校验位
0x40444cfa 0x00 0x0004 0x68656c64 0x6b

各部分数据说明如下:

参数 长度 说明
包头 4字节 固定为0x40444cfa
消息指令 1字节 具体的消息指令类型
负载长度 2字节 负载的内容长度N,单位为字节byte
负载数据 N字节 具体的负载内容,以UTF-8格式编码
校验位 1字节 报文完整性校验值。计算公式为报文内每个byte相加的和取低位字节。按照示例报文计算为:0x40+0x44+0x4c+0xfa+0x00+0x04+0x68+0x65+0x6c+0x64=0x36b, 校验位为0x6b

后续所有按照该标准通信格式的交互指令均按照此规范格式进行通信。另外,对于某些存在单次发送固定报文长度需求的通信方式(例如蓝牙4.0要求每个包固定20个字节),负载数据可以通过填充0x00字节的方式来适配。

如果通信指令请求出现异常,设备可通过指令0x00向APP响应异常错误信息,响应负载内容如下:

错误代码 错误描述
1个字节 N-1字节

其中,预定义的错误代码有:

错误代码 描述
0x01 报文解析出错
0x02 校验位计算错误
0x03 不能识别或支持的指令

# 消息指令

下面就具体的指令进行详细说明。

# 设备信息读取

APP可通过指令0x01向设备请求设备基本信息和支持的协议版本号。

提示

通过协议版本号APP可以判断设备支持的指令功能集。

协议版本号是v2版本新增字段,APP在解析协议版本时会根据负载数据的长度进行自动判断。

APP请求负载为空。设备收到请求后,应在当前指令下返回如下负载内容:

设备ID长度 设备ID 产品型号长度 产品型号 支持的协议版本
1字节 N字节 1字节 N字节 2字节

各部分数据说明如下:

参数 说明
设备ID 设备的唯一标识,例如: PT_123456
产品型号 设备对应的产品型号标识,例如: PT
支持的协议版本v2 设备当前支持的接入协议版本,从1开始递增。注意,该字段是协议v2新增字段,如果不存在则视为v1。

# 设备接入验证

对于不支持联网的小型蓝牙设备,其接入合法性验证过程可以通过指令0x02来完成。

提示

该指令仅针对APP对设备接入安全性有需求的场景,如果无这方面的需求,则不需要实现。

整个验证流程如下所示:

uml diagram

具体数据通信过程说明如下:

  1. APP生成随机字符串,并通过指令0x02请求签名校验。举例签名字符串为hello(16进制为0x68656c6c6f),则发送的完整报文内容为:0x40444cfa01000568656c6c6fe4。

  2. 设备收到指令,使用该随机签名字符串按照以下计算公式生成一个签名结果并返回APP,同时返回设备ID和产品型号。

    sig = CRC32(${设备ID} + "-" + ${随机签名字符串} + "-" + ${产品秘钥})

    CRC32校验算法请参考https://baike.baidu.com/item/CRC32/7460858 (opens new window)

    返回负载数据内容的格式如下:

    签名结果长度 签名结果 设备ID长度 设备ID 产品型号长度 产品型号
    1字节 N字节 1字节 N字节 1字节 N字节
  3. APP将该设备ID、产品型号、随机签名字符串以及签名结果信息发送给平台后端进行验证。如果平台后端按照相同的算法验证结果一致,则证明设备是合法的。

# 设备网络配置

APP通过指令0x03可以将Wi-Fi账户、密码信息传递给蓝牙设备,实现设备联网配置。

发送数据的负载内容结构如下:

Wi-Fi SSID长度 Wi-Fi SSID Wi-Fi密码长度 Wi-Fi密码 Wi-Fi密码加密标识
1字节 N字节 1字节 N字节 1字节

各部分数据说明如下:

参数 说明
Wi-Fi SSID Wi-Fi热点的名称字符串
Wi-Fi密码 Wi-Fi连接认证的密码。默认情况下,出于安全考虑,该密码是加密传输的,除非加密标志设置为0
Wi-Fi密码加密标识v2 Wi-Fi密码是否进行了加密处理。0表示未加密,非0表示已加密。注意,该字段是v2版本新增字段,如果不存在则视为已加密。

Wi-Fi密码加密规则

为了避免蓝牙在传输Wi-Fi密码的过程中因为广播导致密码泄漏,得力E+APP在数据传输前会对Wi-Fi密码进行加密处理。加密公式如下:

加密密码 = ('DELI@' + 明文密码) XOR 产品密钥

其中, 产品密钥为该设备对应产品型号的接入密钥,由平台通过开发者平台 (opens new window)生成并分配给设备厂商。设备收到该加密信息后,通过产品密钥再次XOR解密出明文信息,如果解密出来的信息以'DELI@'开头(加密标示前缀),则认为解密成功,设备可去掉该加密标示前缀信息取得原始明文,否则应视为解密失败。

需要注意的是,这里的异或算法规则是,用待加密的字符串的每个字节去和产品密钥的每个字节进行异或,产生最终的加密密码字节流。一段Java示例代码如下:

//src = ('DELI@' + 明文密码)
//key = 产品密钥
byte[] srcBytes = src.getBytes();
byte[] keyBytes = key.getBytes();
for (int i = 0, size = srcBytes.length; i < size; i++) {
    for (byte keyByte : keyBytes) {
          srcBytes[i] = (byte) (srcBytes[i] ^ keyByte);
    }
}
1
2
3
4
5
6
7
8
9

设备在网络连接结束后,不论成功与否,都应通过蓝牙指令0x03将结果直接反馈给APP,返回的负载数据结构如下:

设备ID长度 设备ID 产品型号长度 产品型号 设备状态码 错误消息长度 错误代码 设备MAC IP地址 子网掩码 网关地址
1字节 N字节 1字节 N字节 1字节 1字节 1字节 6字节 4字节 4字节 4字节

各部分数据说明如下:

参数 说明
设备ID 设备的唯一标识
产品型号 设备对应的产品型号标识
设备状态码 根据设备网络以及服务器连接的结果不同,显示不同的状态。包括:
-2: 路由器连接失败;
-1: 路由器连接成功,但无法访问外部网络;
0: 路由器连接成功,且能够正常连接服务器;
1: 路由器连接成功,但是连接服务器失败;


注意:如果设备仅用于局域网访问,不涉及到服务器连接,则在路由器连接成功后即可返回状态码0。
错误代码 如果设备状态码不等于0,则表示设备配置网络或连接服务器出现了故障,设备需要将故障原因通过错误代码反馈给APP。具体的错误代码见下表:
错误代码设备状态码错误描述
-6-2配网超时
-5-2设备不支持连接5G Wi-Fi热点
-4-2其他配网错误
-3-2Wi-Fi SSID或密码错误
-2-1路由器连接成功,但获取IP地址失败
-1-1路由器连接成功,但不支持外网访问(仅针对具备外网感知能力的设备)
11服务器域名解析失败或IP地址无法访问
21服务器连接被拒绝或响应错误
31服务器认证失败,拒绝设备接入(特殊场景,一般不会发生)

注意:设备应尽可能准确的通过错误代码反馈网络连接故障的根本原因,以帮助用户快速解决连接故障。
设备MAC 设备MAC地址,如果获取失败请设置为0
IP地址 设备IP地址,如果获取失败请设置为0
子网掩码 设备子网掩码,如果获取失败请设置为0
网关地址 设备网关地址,如果获取失败请设置为0

注意

针对部分无法在配网结束后通过蓝牙直接返回配网结果的设备,得力E+APP额外还支持通过UDP广播监听的方式获取配网结果信息。APP会在配网指令发送后在端口24333开启UDP监听,设备如果能够正常连接路由器(不论是否能连接平台),就可以将配网结果信息通过UDP广播发送出去,发送的消息体结构与上述结构一致。

另外,对于采用这种方式返回配网结果的设备,为了避免局域网内UDP丢包,请设备发送3次UDP广播,每次发送间隔时间3s

最后,如果因为网络原因APP始终无法收到UDP广播,APP将会在一段时间后,通过查询平台端设备的连接状态来判断是否配网成功。

# 设备网络状态读取 v2

APP通过指令0x04可以主动读取蓝牙设备当前的网络连接状态信息。

连接状态 连接方式 Wi-Fi SSID长度 Wi-Fi SSID 是否安全 信号强度 设备MAC IP地址 子网掩码 网关地址
1字节 1字节 1字节 N字节 1字节 1字节 6字节 4字节 4字节 4字节

各部分数据说明如下:

参数 说明
连接状态 设备网络连接状态。包括:0-未连接,1-已连接
连接方式 设备网络连接方式。包括:0-有线,1-无线Wi-Fi,2-其他
Wi-Fi SSID Wi-Fi热点的唯一标识字符串。如果非Wi-Fi连接方式,请将长度设置为0
是否安全 Wi-Fi热点是否需要密码认证。0-不安全,1-安全。如果非Wi-Fi连接方式,请设置为0
信号强度 Wi-Fi热点的信号强度,以dBm为单位的有符号整数,一般在-30~-120之间。如果非Wi-Fi连接方式,请设置为0
设备MAC 设备MAC地址,如果获取失败请设置为0
IP地址 设备IP地址,如果获取失败请设置为0
子网掩码 设备子网掩码,如果获取失败请设置为0
网关地址 设备网关地址,如果获取失败请设置为0

# 设备Wi-Fi列表查询 v2

APP通过指令0x05可以获取设备扫描到的所有可用Wi-Fi热点列表。

注意

为了确保设备能够连接上热点,APP在进行【设备网络配置】操作前,应优先通过本指令获取设备周边的Wi-Fi列表。

如果设备不支持该指令,则应通过0x00指令返回错误以便通知APP直接扫描附近的Wi-Fi热点列表。

APP请求负载为空。设备收到请求后,应在当前指令下返回如下负载内容:

Wi-Fi热点数量 Wi-Fi热点1 Wi-Fi热点2 ...... Wi-Fi热点N
1字节 N字节 N字节 ...... N字节

设备根据信号强度返回最多20个Wi-Fi热点信息(即N<=20)。每个Wi-Fi热点信息结构一致,如下所示:

Wi-Fi SSID长度 Wi-Fi SSID 是否安全 信号强度
1字节 N字节 1字节 1字节

各部分数据说明如下:

参数 说明
Wi-Fi SSID Wi-Fi热点的唯一标识字符串
是否安全 Wi-Fi热点是否需要密码认证。0-不安全,1-安全
信号强度 Wi-Fi热点的信号强度,以dBm为单位的有符号整数,一般在-30~-120之间

# 设备应用交互

在某些具体的业务场景下,如果蓝牙直连设备与绑定的E+云应用之间需要直接的数据通信交互,可以通过支持消息指令0xff来完成。云应用和设备具体的业务数据通过报文的负载数据部分来传输,具体负载数据内部的结构可以由设备和应用之间自定义。

提示

得力E+APP通过API支持在云应用和设备之间透传蓝牙数据,该指令仅用于支持在得力E+APP部署的云应用。

包头 消息指令 负载长度 负载数据 校验位
0x40444cfa 0xff 0x???? 设备和应用定义消息 0x??

# 变更记录

变更时间 协议版本 变更人 变更内容
2019/12/23 v1 徐超国 根据蓝牙签到机、考勤机等设备的接入需求完成了第一个版本的协议开发
2022/10/24 v2 徐超国、胡金喜 增加了设备网络状态读取、设备Wi-Fi列表查询等新指令,对设备网络配置指令做了部分更新
上次更新: 7/2/2023, 3:53:59 PM