协议概述
1. 协议目的
本协议规范新能源智能充电桩(从机)与 Android/iOS App(主机)的蓝牙通信交互逻辑,明确数据传输格式、指令定义、跨平台适配规则,保障通信的可靠性 (校验机制)、兼容性 (适配双平台)、安全性 (关键指令加密)和易用性(简洁帧结构),适用于充电桩硬件开发、App 端集成及设备联调。
2. 适用范围
- 硬件:支持蓝牙 4.0 及以上的智能充电桩(优先 BLE 低功耗蓝牙,兼容经典蓝牙);
- 软件:Android 4.3+(BLE)/Android 2.0+(经典蓝牙)、iOS 8.0+;
- 核心场景:设备连接、状态查询、充电控制、异常上报、参数配置。
3. 设计原则
- 跨平台兼容:统一帧格式,适配 Android/iOS 蓝牙 API 差异;
- 轻量可靠:帧结构简洁,CRC16 校验保障数据完整性;
- 安全可控:关键指令(启动充电)加验证码,防误操作 / 非法控制;
- 适配 BLE 分包:针对 BLE 单帧 20 字节限制,定义分包 / 合包规则。
一、物理层与传输层规范
1. 蓝牙硬件层(物理层)
表格
| 配置项 | 规范要求 | 跨平台适配要点 |
|---|---|---|
| 蓝牙类型 | 优先 BLE(蓝牙 4.0/5.0),兼容经典蓝牙(BR/EDR) | BLE 适配移动设备续航,经典蓝牙适用于大流量数据传输 |
| BLE 配置(推荐) | 充电桩作为 Peripheral,暴露:服务 UUID:0000FF00-0000-1000-8000-00805F9B34FB读写特征值 UUID:0000FF01-0000-1000-8000-00805F9B34FB |
Android:通过BluetoothGatt操作特征值;iOS:通过CBPeripheral读写特征值 |
| 经典蓝牙配置 | 串口透传(SPP 协议),参数:115200 波特率、8N1(8 数据位 + 无校验 + 1 停止位) | 双端参数必须一致,否则数据乱码 |
| 传输单元限制 | BLE:单帧最大 20 字节(MTU),超 20 字节需分包;经典蓝牙:单帧最大 1024 字节 | Android/iOS 需统一分包 / 合包逻辑 |
| 超时规则 | 连接超时 10s,指令响应超时 3s(重传最多 3 次) | App 端实现重传逻辑,充电桩端 3s 内必须响应指令 |
2. 数据帧结构(核心)
采用固定帧格式,所有通信数据均按此格式封装,小端序(Little Endian) 存储多字节数据(如指令码、长度)。
表格
| 字段 | 长度(字节) | 取值 / 说明 | 作用 |
|---|---|---|---|
| 帧头 | 2 | 固定值 0xAA55(十六进制) |
标识帧开始,避免粘包 / 拆包误解析 |
| 协议版本 | 1 | 0x01(当前版本) |
兼容未来协议升级,版本不匹配则拒绝通信 |
| 帧类型 | 1 | 0x00= 请求帧(App→桩)0x01= 响应帧(桩→App)0x02= 上报帧(桩主动→App) |
区分帧的用途,简化解析逻辑 |
| 数据长度 | 2 | 「指令码 + 数据域」的总字节数(0~65535) | 明确数据域边界,避免越界解析 |
| 指令码 | 2 | 业务指令唯一标识(如0x0001= 握手) |
区分不同业务指令 |
| 数据域 | N | 指令对应的参数 / 返回数据(可选,N=0 时无) | 承载核心业务数据(如充电时长、设备状态) |
| CRC16 校验 | 2 | 「协议版本 + 帧类型 + 数据长度 + 指令码 + 数据域」的 CRC16 值(多项式0x8005) |
验证数据完整性,校验失败则丢弃帧 |
| 帧尾 | 2 | 固定值 0x55AA |
标识帧结束 |
2.1 CRC16 计算规则
plaintext
初始值:0xFFFF
多项式:0x8005(x¹⁶+x¹⁵+x²+1)
计算方式:逐字节处理待校验数据,最终结果取反,小端序存储
示例:待校验数据为 0x01 00 03 00 01 01,CRC16 计算结果为 0x9D78(小端序存储为 0x789D)。
2.2 BLE 分包 / 合包规则
当单帧总长度超过 20 字节时,按以下规则分包:
- 分包标识:每个分包首字节为「高 4 位 = 总分包数,低 4 位 = 当前分包序号(从 0 开始)」;
- 合包逻辑:接收端按序号拼接所有分包,验证帧头 / 帧尾后再解析完整帧;
- 超时:接收首个分包后 5s 未收到后续分包,丢弃已接收数据,触发指令重传。
二、核心指令集
指令码按业务分类,覆盖全场景,所有指令均需遵循「请求 - 响应」规则(上报帧除外)。
1. 基础通信指令
表格
| 指令码 | 指令名称 | 帧类型 | 方向 | 数据域格式(发送 / 响应) | 说明 |
|---|---|---|---|---|---|
| 0x0001 | 握手指令 | 请求 / 响应 | App→桩 | 发送:App 版本(UTF-8 字符串,如 "V1.0.0")响应:1. 设备 ID(8 字节)2. 协议版本(1 字节)3. 设备状态(1 字节) | 建立通信,验证协议兼容性,仅握手成功后可执行其他指令 |
| 0x0002 | 心跳指令 | 请求 / 响应 | App→桩 | 发送:无响应:1. 在线状态(0x01 = 在线 / 0x00 = 离线)2. 信号强度(0-100,1 字节) | App 每 10s 发送 1 次,维持连接 |
2. 状态查询指令
表格
| 指令码 | 指令名称 | 帧类型 | 方向 | 数据域格式(发送 / 响应) | 说明 |
|---|---|---|---|---|---|
| 0x0101 | 核心状态查询 | 请求 / 响应 | App→桩 | 发送:无响应:1. 设备状态(1 字节):0x00 = 空闲 / 0x01 = 充电中 / 0x02 = 故障2. 剩余电量(2 字节,%)3. 充电功率(2 字节,W)4. 温度(1 字节,℃) | 充电关键参数查询 |
| 0x0102 | 故障详情查询 | 请求 / 响应 | App→桩 | 发送:无响应:1. 故障码(2 字节)2. 故障描述(UTF-8 字符串)3. 故障时间(4 字节,Unix 时间戳) | 仅设备状态为故障时有效 |
3. 充电控制指令(核心)
表格
| 指令码 | 指令名称 | 帧类型 | 方向 | 数据域格式(发送 / 响应) | 说明 |
|---|---|---|---|---|---|
| 0x0201 | 启动充电 | 请求 / 响应 | App→桩 | 发送:1. 充电时长(2 字节,分钟)2. 最大电流(1 字节,A)3. 验证码(4 字节,MD5 摘要)响应:1. 执行结果(1 字节:0x00 = 成功)2. 订单号(8 字节) | 关键指令,验证码防误操作,仅空闲桩可执行 |
| 0x0202 | 停止充电 | 请求 / 响应 | App→桩 | 发送:1. 订单号(8 字节)响应:1. 执行结果(1 字节)2. 充电总量(2 字节,kWh)3. 充电时长(2 字节,分钟) | 支持手动 / 紧急停止 |
| 0x0203 | 暂停充电 | 请求 / 响应 | App→桩 | 发送:1. 订单号(8 字节)响应:1. 执行结果(1 字节) | 临时暂停,可恢复 |
4. 异常上报指令(桩主动)
表格
| 指令码 | 指令名称 | 帧类型 | 方向 | 数据域格式 | 说明 |
|---|---|---|---|---|---|
| 0x0301 | 异常上报 | 上报帧 | 桩→App | 1. 异常类型(1 字节):0x01 = 过流 / 0x02 = 过压 / 0x03 = 高温 / 0x04 = 断电2. 异常时间(4 字节,时间戳)3. 异常参数(2 字节,如过流值) | 桩主动上报,App 需监听并弹窗提示用户 |
5. 错误码定义(1 字节)
表格
| 错误码 | 含义 | 适用场景 |
|---|---|---|
| 0x00 | 成功 | 所有指令响应 |
| 0x01 | 指令不支持 | 接收到未定义的指令码 |
| 0x02 | 参数格式错误 | 数据域长度 / 取值不符合要求 |
| 0x03 | 设备忙 | 充电中执行启动充电指令 |
| 0x04 | CRC16 校验失败 | 帧数据被篡改 / 传输错误 |
| 0x05 | 验证码错误 | 启动充电时验证码不匹配 |
| 0x06 | 权限不足 | 普通 App 执行管理员指令 |
| 0x07 | 硬件故障 | 执行指令时硬件异常 |
三、跨平台适配要点
1. Android 端核心适配(Kotlin)
1.1 权限配置(AndroidManifest.xml)
xml
<!-- BLE核心权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
1.2 帧构建核心代码
kotlin
// CRC16计算工具
object Crc16Util {
private const val POLY = 0x8005
fun calculate(data: ByteArray): Short {
var crc = 0xFFFF.toShort()
for (b in data) {
crc = (crc xor (b.toInt() shl 8)).toShort()
repeat(8) {
crc = if (crc.toInt() and 0x8000 != 0) {
(crc.toInt() shl 1 xor POLY).toShort()
} else {
(crc.toInt() shl 1).toShort()
}
}
}
return crc.inv() // 取反
}
}
// 构建通信帧
fun buildFrame(cmdCode: Int, frameType: Byte, data: ByteArray = byteArrayOf()): ByteArray {
val frameHead = byteArrayOf(0xAA, 0x55)
val protocolVersion = 0x01.toByte()
val dataLen = (2 + data.size).toShort() // 指令码(2) + 数据域长度
val cmdCodeBytes = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(cmdCode.toShort()).array()
val dataLenBytes = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(dataLen).array()
// 计算CRC16
val checkData = byteArrayOf(protocolVersion, frameType) + dataLenBytes + cmdCodeBytes + data
val crc16 = Crc16Util.calculate(checkData)
val crc16Bytes = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(crc16).array()
val frameTail = byteArrayOf(0x55, 0xAA)
// 拼接完整帧
return frameHead + protocolVersion + frameType + dataLenBytes + cmdCodeBytes + data + crc16Bytes + frameTail
}
// 示例:构建启动充电指令帧
fun buildStartChargeFrame(): ByteArray {
// 数据域:120分钟 + 10A + 验证码0x12345678
val duration = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(120).array()
val current = byteArrayOf(10)
val verifyCode = byteArrayOf(0x12, 0x34, 0x56, 0x78)
val data = duration + current + verifyCode
return buildFrame(0x0201, 0x00, data) // 指令码0x0201,请求帧
}
2. iOS 端核心适配(Swift)
2.1 权限配置(Info.plist)
xml
<!-- BLE权限(iOS 13+) -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要蓝牙权限连接充电桩</string>
2.2 帧构建核心代码
swift
// CRC16计算
func calculateCRC16(_ data: Data) -> Data {
let poly: UInt16 = 0x8005
var crc: UInt16 = 0xFFFF
for byte in data {
crc ^= UInt16(byte) << 8
for _ in 0..<8 {
crc = (crc & 0x8000) != 0 ? (crc << 1) ^ poly : crc << 1
}
}
crc = ~crc // 取反
return Data(bytes: [UInt8(crc & 0xFF), UInt8(crc >> 8)]) // 小端序
}
// 构建通信帧
func buildFrame(cmdCode: UInt16, frameType: UInt8, data: Data = Data()) -> Data {
let frameHead = Data([0xAA, 0x55])
let protocolVersion: UInt8 = 0x01
let dataLen = UInt16(2 + data.count) // 指令码(2) + 数据域长度
let cmdCodeData = withUnsafeBytes(of: cmdCode.littleEndian) { Data($0) }
let dataLenData = withUnsafeBytes(of: dataLen.littleEndian) { Data($0) }
// 计算CRC16
var checkData = Data()
checkData.append(protocolVersion)
checkData.append(frameType)
checkData.append(dataLenData)
checkData.append(cmdCodeData)
checkData.append(data)
let crc16Data = calculateCRC16(checkData)
let frameTail = Data([0x55, 0xAA])
// 拼接完整帧
var frame = Data()
frame.append(frameHead)
frame.append(protocolVersion)
frame.append(frameType)
frame.append(dataLenData)
frame.append(cmdCodeData)
frame.append(data)
frame.append(crc16Data)
frame.append(frameTail)
return frame
}
// 示例:构建握手指令帧
func buildHandshakeFrame() -> Data {
let appVersion = "V1.0.0".data(using: .utf8)!
return buildFrame(cmdCode: 0x0001, frameType: 0x00, data: appVersion)
}
四、完整交互示例(启动充电)
1. 交互流程
预览
查看代码
充电桩App充电桩App扫描BLE设备(名称前缀:NewEnergy_)广播BLE信号(含设备ID)发起BLE连接确认连接成功握手指令(0x0001)+ App版本V1.0.0响应握手 + 设备ID(12345678) + 协议版本(01) + 状态(空闲)核心状态查询(0x0101)响应状态 + 空闲 + 电量100% + 功率0W + 温度25℃启动充电(0x0201)+ 120分钟 + 10A + 验证码12345678响应启动成功 + 订单号87654321上报帧 + 充电中 + 功率3000W + 温度30℃停止充电(0x0202)+ 订单号87654321响应停止成功 + 充电量5kWh + 时长60分钟
sequenceDiagram
participant App
participant 充电桩
# 连接阶段
App->>充电桩: 扫描BLE设备(名称前缀:NewEnergy_)
充电桩->>App: 广播BLE信号(含设备ID)
App->>充电桩: 发起BLE连接
充电桩->>App: 确认连接成功
# 业务交互
App->>充电桩: 握手指令(0x0001)+ App版本V1.0.0
充电桩->>App: 响应握手 + 设备ID(12345678) + 协议版本(01) + 状态(空闲)
App->>充电桩: 核心状态查询(0x0101)
充电桩->>App: 响应状态 + 空闲 + 电量100% + 功率0W + 温度25℃
App->>充电桩: 启动充电(0x0201)+ 120分钟 + 10A + 验证码12345678
充电桩->>App: 响应启动成功 + 订单号87654321
# 充电过程
充电桩->>App: 上报帧 + 充电中 + 功率3000W + 温度30℃
App->>充电桩: 停止充电(0x0202)+ 订单号87654321
充电桩->>App: 响应停止成功 + 充电量5kWh + 时长60分钟
充电桩App充电桩App扫描BLE设备(名称前缀:NewEnergy_)广播BLE信号(含设备ID)发起BLE连接确认连接成功握手指令(0x0001)+ App版本V1.0.0响应握手 + 设备ID(12345678) + 协议版本(01) + 状态(空闲)核心状态查询(0x0101)响应状态 + 空闲 + 电量100% + 功率0W + 温度25℃启动充电(0x0201)+ 120分钟 + 10A + 验证码12345678响应启动成功 + 订单号87654321上报帧 + 充电中 + 功率3000W + 温度30℃停止充电(0x0202)+ 订单号87654321响应停止成功 + 充电量5kWh + 时长60分钟

2. 帧数据示例(十六进制)
App 发送启动充电请求帧
plaintext
AA 55 01 00 00 07 01 02 78 00 0A 12 34 56 78 78 9D 55 AA
- 帧头:AA 55;
- 协议版本:01;
- 帧类型:00(请求帧);
- 数据长度:00 07(指令码 2 字节 + 数据域 5 字节);
- 指令码:01 02(0x0201 小端序);
- 数据域:78 00(120 分钟)、0A(10A)、12 34 56 78(验证码);
- CRC16:78 9D;
- 帧尾:55 AA。
充电桩响应启动成功帧
plaintext
AA 55 01 01 00 09 01 02 00 38 37 36 35 34 33 32 31 8A B1 55 AA
- 帧类型:01(响应帧);
- 数据域:00(成功)、38 37 36 35 34 33 32 31(订单号 87654321 的 ASCII 码);
- CRC16:8A B1。
五、测试与联调规范
- 连接测试:验证 0-10 米、遮挡场景下 Android/iOS App 与充电桩的连接成功率(≥95%);
- 指令测试:全覆盖所有指令,验证参数边界值(如充电时长 0 分钟、最大电流 30A)的响应正确性;
- 异常测试:模拟 CRC16 错误、指令码不存在、参数错误,验证充电桩返回错误码的准确性;
- 分包测试:针对 BLE,验证超过 20 字节的帧分包 / 合包解析正确性。
总结
- 本协议采用「帧头 + 版本 + 帧类型 + 长度 + 指令码 + 数据域 + CRC16 + 帧尾」的结构化帧格式,核心保障蓝牙通信的可靠性,适配 Android/iOS 双平台;
- 指令集覆盖充电桩全业务场景,关键指令(启动充电)增加验证码机制,兼顾易用性与安全性;
- 提供 Android(Kotlin)和 iOS(Swift)的核心实现代码,包含帧构建、CRC16 计算逻辑,可直接落地开发,联调需重点验证分包 / 合包和跨平台一致性。