新能源智能充电桩与 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 计算逻辑,可直接落地开发,联调需重点验证分包 / 合包和跨平台一致性。
相关推荐
JMchen1232 小时前
自定义View性能优化:从60fps到120fps的进阶之路
android·经验分享·性能优化·kotlin·自定义view
vistaup3 小时前
DevEco Studio 鸿蒙 HAR本地引入相互依赖问题解决
android·华为·harmonyos
常利兵3 小时前
Android 开发秘籍:用Tint为Icon动态变色
android
这个人中暑了4 小时前
iOS 查看手机udid
ios·智能手机
奔跑吧 android4 小时前
【车载audio】【CarAudioService 05】【车载 Android 系统调试深度指南:解析 dumpsys car_service】
android·audio·audioflinger·aosp15·车载音频·车载audio·car_service
shuangrenlong4 小时前
androidstudio gradle文件报红
android
Digitally4 小时前
如何通过蓝牙将 iPhone 上的照片传输到 Android
android·ios·iphone
文件夹__iOS4 小时前
iOS 网络安全认证:Token / MD5 / RSA 简明指南
安全·web安全·ios
常利兵4 小时前
Android Intent.setAction失效报错排查与修复全方案
android