明说·协议课 第 1 讲|充换电协议的五层地图:先看谁和谁说话
近来有些时间,我决定开一个专栏,专门讲一讲新能源汽车充换电领域的通信协议。也借这个机会,把这些年做协议栈积累下来的一些认识和教训,尽量系统地梳理出来。未必全面,也不敢说都对;对我自己而言,也算回头看看之前做的一些具体工作。开讲之前,先不急着翻规范,先讲讲我对充换电协议一些浅薄的认识。算时间,我做充换电云平台有些年头了,对协议的认识大多来自项目实践。真正让我头疼的,说实话,往往不是某个字段该填什么,而是两份各自成立的协议交到一起以后,系统究竟该听谁的。单看一份规范,多数报文都有定义,字段、方向和触发条件也能查到;真正进入生产环境,问题却常常出在规范没有替你回答的地方:一个编号在哪个范围内唯一,一次“接受”究竟表示收到、校验通过,还是已经执行;两个版本里的同名对象是否还指同一件事;协议留下的实现空间,又该由设备、平台还是项目约定来收口。这里一旦含糊,代价通常不由写规范的人承担,而由做平台、接设备和对账的人承担。这些问题见得多了,我慢慢意识到,读协议不能只盯着字段。我更关心的是:它把事实、控制和责任分别交给了谁。字段可以查,协议的边界却要把通信两端、编号层级和成功语义摆清楚以后,才看得见。这也是我决定按通信关系来编这个专栏,而不是按协议名称逐个介绍的原因。开栏说明:这个专栏想讲清什么充换电这行有个挺特别的现象:不少规范原文可以直接取得,中英文讲解也不算少,可很多材料仍停在字段罗列,或者把几个版本的说法揉在一起。你能查到 OCPP 1.6 的BootNotification有哪些字段,却未必知道手上那台桩按哪个版次实现;你也能找到一张列着 OCPP、OCPI、ISO 15118 的表,却未必看得出它们连接的根本不是同一组端点。到了平台同时接两代协议、跨运营商结算或者承接电网调节时,真正决定架构的,恰恰是这些边界。这个专栏想补的就是这一段:按责任层次讲清每套协议连接谁、管到哪儿,关键报文为什么这样设计,以及协议没有规定的地方该怎样建立工程约定。规范翻译我不做,字段全表在官方文档里,照抄一遍没有价值;合规咨询也不是这个专栏的工作,法规在这里主要用来解释协议为什么会长成现在的样子。还有两条规矩想先说清,它们决定了这个专栏是不是有长期参考价值。一是每讲都锚定版本,因为协议在演进、标准在更新,不带版本的技术判断很快就会漂,根据之前的一些工作经历和当下新能源出海业务的蓬勃发展,我会多讲一些类似 OCPP 这些国际通用的通信协议;二是把“规范明确规定”“我参与项目时的实现”“根据机制作出的工程判断”分开写。核不到一手来源的事实就留白,规范没有承诺的能力也不替它承诺。这两条看上去是在约束我如何写作,实际是替你省事:你能据此判断哪些结论可以直接作为设计输入,哪些还需要回到自己的设备、固件和项目合同里再核一遍。本讲依据:OCPP 1.6 Edition 2、OCPP 2.0.1 Edition 2、OCPP 2.1 Edition 1;OCPI 2.2.1、2.3.0;ISO 15118-2:2014、ISO 15118-20:2022;GB/T 27930-2023、GB/T 44130.1-2024 / .2~.5-2025、T/CEC 102.1~102.4-2016、T/CEC 102.1~102.10-2021;《充电桩与云快充服务平台交互协议》V1.6、V2.1.0。核对时间为 2026 年 8 月。一、六把枪只有两个编号,平台模型少了一层先讲一个我之前在开发协议栈时遇到的一个问题。那是一个客户定制项目,我们需要为客户实现一套充电桩协议栈,用来接入群充设备。第一版平台的设备模型按“一把枪一个编号,编号在同一充电站内唯一”来设计。接入第一类设备时,这套模型一直正常,直到后来接入一台包含三个充电单元、每个单元两把枪的设备。这台设备的编号规则不同:充电枪编号不是在整座充电站内唯一,而只在各自的充电单元内唯一,每个单元都从 1 开始编号。于是,六把枪在报文里呈现为 1、2、1、2、1、2。问题出在平台适配层:它在转换设备标识时丢掉了“充电单元”这一维,仍然直接把充电枪编号当作主键。结果,两把属于不同充电单元的枪,就这样落进了同一条记录。麻烦在于,这类错误在联调阶段很难发现。联调往往先测一把枪,编号没有机会相撞,报文一收一回也都符合规范;真正上线以后,平台按这个错误主键完成路由,停止指令最终落到了另一个充电单元的同号枪上。被误中的车提前停了,原本要停止的那辆车却还在充,两个会话的状态、电量与账单随之错位。整条链路没有语法错误,也没有出现能直接指向模型问题的超时或异常;问题最后是在对账时才现形,中间隔多久,取决于运营商多久做一次核对。这次故障当然有代码层的直接原因,但更早的根因在模型:建模时把一个只在特定父级对象下成立的局部编号,误当成了稳定且全局唯一的资产身份。团队手里的协议清单列了名称、版本和功能,却没有写清这个编号属于哪一层、在哪个上级对象下唯一。缺掉的这一维并不会真的消失,它只会换一种方式回来,以状态覆盖、指令串路由和对不上的账单出现,而且往往专挑上线之后。从那以后,我看协议时会先问一件很朴素的事:报文里的这个对象,究竟是“谁下面的谁”?这不是咬文嚼字。数据库的唯一约束怎么建、缓存键怎么拼、下行命令怎么路由、故障最终由谁负责,都藏在这个问题里。所以,这一讲真正要说明的不是“协议可以分成五类”,而是:分层不是知识分类,而是责任边界。合同当然可以约定甲乙双方各自负责什么,但运行中的系统仍要靠协议对象、编号层级、状态来源和确认语义来落实这份责任;模型如果先把这些差异抹平,合同写得再清楚,也不能阻止两条数据在数据库里撞到一起。二、先把一次充电摊开,五段通信才会显形为了把这件事讲清楚,先设一个贯穿全专栏的场景:一辆电动汽车开进一座光储充站,车主用第三方 App 扫码启动一台直流桩,这个 App 不属于本站运营商;充电期间站内正在削峰,结束后还要跨运营商结算。之所以把条件设得这么满,是因为一次看似普通的“扫码到出账”,只有在这些约束同时出现时,藏在背后的五段通信才会全部显形。枪插上以后,车和充电设备先建立车—桩通信,完成握手、充电参数交换与功率控制;在支持即插即充的体系里,还会涉