新能源智能充电桩与 Android/iOS App 蓝牙通信协议

协议概述

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 字节时,按以下规则分包:

  1. 分包标识:每个分包首字节为「高 4 位 = 总分包数,低 4 位 = 当前分包序号(从 0 开始)」;
  2. 合包逻辑:接收端按序号拼接所有分包,验证帧头 / 帧尾后再解析完整帧;
  3. 超时:接收首个分包后 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。

五、测试与联调规范

  1. 连接测试:验证 0-10 米、遮挡场景下 Android/iOS App 与充电桩的连接成功率(≥95%);
  2. 指令测试:全覆盖所有指令,验证参数边界值(如充电时长 0 分钟、最大电流 30A)的响应正确性;
  3. 异常测试:模拟 CRC16 错误、指令码不存在、参数错误,验证充电桩返回错误码的准确性;
  4. 分包测试:针对 BLE,验证超过 20 字节的帧分包 / 合包解析正确性。

总结

  1. 本协议采用「帧头 + 版本 + 帧类型 + 长度 + 指令码 + 数据域 + CRC16 + 帧尾」的结构化帧格式,核心保障蓝牙通信的可靠性,适配 Android/iOS 双平台;
  2. 指令集覆盖充电桩全业务场景,关键指令(启动充电)增加验证码机制,兼顾易用性与安全性;
  3. 提供 Android(Kotlin)和 iOS(Swift)的核心实现代码,包含帧构建、CRC16 计算逻辑,可直接落地开发,联调需重点验证分包 / 合包和跨平台一致性。
相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
茶底世界之下1 天前
为什么预览、录制、离线导出不能共享同一个背压策略?
ios·swift
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas