OCPP 1.6J解析:充电桩通信协议、消息机制与互操作实践 简介OCPP 1.6J 学习资料面向充电桩研发、测试及运营平台开发者围绕开放充电点协议 1.6J 版2017-09-28 最终版梳理充电点与中央管理系统之间的通信规范帮助读者理解远程控制、状态上报、计费授权、安全通信与软件更新等核心机制。压缩包内仅含 1 个 PDF 文件大小约 3.84MB内容以协议正文和目录结构为主涵盖术语约定、功能配置文件、操作一般视图、本地授权与离线行为、事务消息、连接器编号及 ID Tokens 等章节便于按规范逐章查阅。目前已有 879 人学习适合作为协议入门、接口联调与功能对照的案头参考也可辅助开发人员核对消息流程和业务边界。1. 从一个插枪瞬间说起OCPP 1.6J 到底是什么把充电枪插进车里屏幕亮起“正在充电”。就这几秒钟桩和几公里外的后台已经对过好几句话这把枪归哪个口、这张卡有没有余额、这次充电记在谁头上、功率给到多少。两边要能对上话得讲同一种语言OCPP 1.6J 就是这门语言的语法书。OCPP 是 Open Charge Point Protocol 的缩写中文一般叫“开放充电点协议”管的是充电桩和中央管理系统之间怎么通信。1.6 是版本号末尾那个 J 不是“加强版”的意思它指这套消息用 JSON 格式承载、跑在 WebSocket 上。搞清这一层你再看那些 .req 和 .conf就不会觉得是一堆天书字段而是一次次有来有回的对话。2. 为什么充电桩需要一个“共同语言”协议存在的理由2.1 一根桩的活儿光靠它自己干不完先说清楚充电桩到底要干什么。它要认出用户刷卡或扫码的那串编号是不是有效要记录这次充了多少度电、从几点几分开始要在功率器件过热时把电流降下来要在后台要求时重启或者升级固件还要在断网时决定“放行还是拒绝”。这些事没有一件能只靠桩自己拍板。最典型的是授权。卡是运营商发的有效名单在后台桩本地最多存一份缓存。桩刷卡时把 idTag 发给后台问一句“这人能用吗”后台回一句“能用”或者“这张卡被拉黑了”桩才知道该不该合闸。再比如计费桩只知道自己输出了多少电钱怎么算、算给谁是后台的事。于是问题来了桩是 A 厂造的后台是 B 公司开发的两边凭什么能对话如果每个厂商都自己定义一套报文运营商后台就得为每个品牌单独写一套对接装十种桩就要维护十套接口。这不是钱的问题是根本维护不过来。协议要解决的就是把这个 N×N 的对接矩阵压成一个 1×1大家都按同一份规范说话。打个比方这有点像插座的国标。电器厂不用管你家的电线是谁布的只要插头符合标准就能通电。充电桩和后台之间也需要这样一个“插头形状”插上去就能互相听懂。2.2 “开放”两个字的分量互操作性换来了什么OCPP 名字里“开放”两个字不是形容词装饰。它意味着规范是公开的任何厂商、任何后台开发者都能拿到同一份文档去实现不需要加入某个联盟、不需要为接口付授权费也不会被某一家绑定。这一点直接决定了充电网络的组网方式。想象一个运营商在城市里布了几百个站点设备可能分三批采购来自不同厂商。如果有开放协议这三批桩都能连到同一套后台运营人员在一个界面上看所有站点的状态一张卡在所有桩上都能刷。这种“混装还能统一管”的能力行业里叫互操作性。没有它每换一个供应商就等于重建一次后台前面的投入全打水漂。设备厂这边也轻松不用为每个客户定制一套私有协议把规范实现一遍谁来对接都能连。代价是规范本身必须写得非常细——细到某个字段是可选的还是必填的、某个状态码在什么条件下返回、时间戳用什么格式。这份文档几百页的体量很大一部分就花在“把话说死”上因为只要留一处含糊不同厂商就会各走一边。2.3 协议只规定怎么说不规定怎么做这里有个初学者最容易踩的认知坑以为符合 OCPP 就等于“互通无忧”。协议规定的是报文格式、字段含义、状态码取值它不规定桩内部用哪颗芯片、界面长什么样、后台数据库怎么建表。这跟 HTTP 规定了请求和响应的格式、却不规定网站长什么样是一个道理。所以两家都宣称支持 1.6J实际对接时仍然可能翻车。常见的情况是规范里某个字段标了可选A 厂不发B 厂后台却默认它一定存在一收就报错或者某个状态码规范允许有几种返回两边对“什么情况该返回哪种”理解不一致。这类问题不是协议写错了而是协议留了余地实现方各走一边。对比项私有协议OCPP 1.6J谁能实现厂商自己或授权方任何拿到规范的团队换设备供应商后台通常要重做对接一般不需要改后台规范详细程度自己够用就行必须把边界写死扩展方式随时私改走厂商自定义数据通道互操作风险低就一家存在需要联调验证这也解释了为什么规范里专门留了一个“厂商自定义数据传输”的口子。标准没覆盖的需求比如某家想传自己的电表诊断信息不用改协议塞进这条通道里对方不认识就回一个“未知厂商”不影响主流程。留下这个口子是因为写规范的人很清楚不可能把所有厂商的需求都预见到堵死不如留门。3. 翻开规范看骨架OCPP 1.6J 里到底写了什么3.1 六个功能配置文件哪些必须会哪些可以选修规范不是要求所有设备把几百页全实现。它把功能切成六个“配置文件”行业里直接叫 profile核心、固件管理、本地授权列表管理、预约、智能充电、远程触发。每个 profile 是一组消息的集合设备实现哪些就在配置里声明哪些。核心 profile 是地基授权、启动通知、心跳、状态通知、仪表值、启动和停止交易都在里面任何一台想接入后台的桩都绕不开。剩下的按需选能远程升级固件就声明固件管理支持预约就声明预约支持按功率曲线限流就声明智能充电。这个设计的巧妙之处在于它让“最小可用”成为可能。一台只做最基本功能的交流慢充桩实现核心 profile 就能上线一台带功率调度能力的直流快充桩再把智能充电加上。后台也不需要假设所有桩都全能它可以先查一遍对方支持什么再决定发哪些指令。规范里对应的配置键会列出设备支持的 profile 清单后台读一次就心里有数。对开发者来说这意味着学习路径可以分段。先把核心 profile 的十几条消息吃透就能看懂绝大多数现场问题智能充电那部分等真要做功率调度时再啃也不迟。不必一上来就从头到尾通读那样容易在中途放弃。3.2 两个方向的十九加十谁先开口说话规范把所有操作按“谁发起”分成两半这个划分方式很值得注意因为它决定了网络结构。一半是充电点发起的也就是桩主动找后台。包括授权请求、启动通知、数据传输、诊断状态通知、固件状态通知、心跳、仪表值、启动交易、状态通知、停止交易。另一半是中央系统发起的后台主动找桩取消预约、更改可用性、更改配置、清除缓存、清除充电配置文件、数据传输、获取复合调度、获取配置、获取诊断、获取本地授权列表版本、远程启动交易、远程停止交易、立即预约、复位、发送本地授权列表、设置充电配置文件、触发消息、解锁连接器、更新固件。数量上后台能发起的更多但这不意味着桩是被动的一坨铁。注意一个细节连接是由桩主动建立的。桩上电后先连上后台之后后台才能反过来下发指令。也就是说物理上桩是客户端后台是服务端但业务上双方都能主动发起操作。这个“双向”特性是 1.6J 区别于很多单向上报协议的地方也是它能在运营场景里用得开的原因。发起方典型操作说明充电点BootNotification上电报到报厂商、型号、固件版本充电点Authorize刷卡后问后台这张卡能不能用充电点StartTransaction / StopTransaction交易开始与结束的记账点充电点MeterValues充电过程中周期上报电量中央系统RemoteStartTransaction后台替用户发起一次充电中央系统SetChargingProfile下发功率曲线限制充电功率中央系统UpdateFirmware推送固件升级中央系统Reset软重启或硬重启看懂这张表现场排查就有方向了如果桩一直不报到问题在连接和启动通知如果卡刷了没反应先看授权请求有没有发出去、后台回了什么状态。3.3 一问一答的消息对每个 .req 都有一个 .conf 等着规范里每条消息都成对出现一个 .req 请求一个 .conf 响应。Authorize.req 发出去必然有一个 Authorize.conf 回来StartTransaction.req 发出去回来的 .conf 里带着后台分配的 transactionId。这种一问一答的设计让发起方不用猜结果。响应的核心通常是状态字段。以授权为例回来的东西里有一个 idTag 的信息块里面最关键的是状态接受、被拉黑、过期、并发冲突、未知。桩看到“接受”就合闸看到其他值就走拒绝流程。再比如远程启动交易后台发指令过去桩回的响应里状态可能是接受、拒绝、未支持——后台据此判断这次远程操作成没成。为什么非要设计成严格的一问一答而不是“发出去就不管了”因为充电这件事有物理后果。后台说“给这辆车充电”桩必须先确认自己真的能充枪插好了、继电器正常、没有故障再把结果告诉后台。如果后台发完就当成功用户站在桩前面却发现没反应这种体验是灾难。所以规范宁可多一次往返也不省这一步确认。提示状态字段的取值集合是规范定死的实现时不要自己发明新的状态值。遇到规范没覆盖的情况用“拒绝”或“故障”这一类通用值把细节放到厂商自定义数据里。3.4 一次充电的完整时序从启动通知到停止交易把消息按时间排一遍整条链路就清楚了。桩上电先建立连接然后发启动通知报上厂商名、型号、固件版本。后台回一个响应里面带着两个关键信息心跳间隔和当前时间。心跳间隔告诉桩多久发一次心跳当前时间用来校准桩的时钟——很多桩没有可靠的实时时钟靠这一步对齐。接着桩发状态通知说自己现在是“可用”。用户插枪桩再发一次状态通知变成“准备中”。刷卡桩发授权请求后台回“接受”。桩发启动交易请求带上连接器编号、卡号、起始电表读数和时间戳。后台回一个 transactionId从这一刻起这次充电有了一个全局唯一的编号。充电过程中桩周期上报仪表值每条带着采样时间、测量类型比如有功电能、数值、单位。这些数据是事后结算和纠纷排查的依据。用户拔枪桩发停止交易请求带上 transactionId、结束电表读数、结束时间和停止原因。后台回执交易闭环。桩最后再发一次状态通知回到“可用”。阶段消息关键字段上电BootNotification.req厂商、型号、固件版本待机StatusNotification.req状态 可用插枪StatusNotification.req状态 准备中刷卡Authorize.req卡号开始StartTransaction.req连接器、卡号、起始读数、时间戳充电中MeterValues.req采样值、测量类型、单位拔枪StopTransaction.req交易号、结束读数、停止原因收尾StatusNotification.req状态 可用这条链路上任何一环断了都是一次现场故障。桩不报到多半是网络或地址配错授权一直不返回看后台服务是否可达交易开始了但仪表值没上来查采样配置。把这些消息名和字段记住排查时就有一条清晰的线而不是到处翻日志猜。3.5 智能充电与本地授权规范里最容易被跳过的那几页规范里有两块内容初学者容易翻过去实际部署时却处处要用。第一块是智能充电。一个站里十几根快充桩同时满功率工作对电网的冲击很大很多场地合同里就限定了总容量。智能充电就是用来处理这件事的后台下发一条充电配置文件规定在什么时间段、功率上限是多少。配置文件分三个层级用“用途”字段区分。整桩级的上限管整个充电点不管你接了几把枪默认级的上限管单把枪的默认功率事务级的上限只管当前这一次充电。三个层级可以同时存在靠一个叫堆叠级别的数字决定谁优先。听起来绕实际逻辑很直白场地总容量是硬约束单枪默认值是常规安排某辆车临时需要特殊处理就单独下一条。第二块是本地授权列表。前面说过刷卡要问后台那断网了怎么办规范给了一条路后台可以提前把一份授权名单推到桩里桩存在本地。联网时用后台断网时查本地。名单有版本号后台可以全量覆盖也可以只推增量。配合几个配置键还能决定断网时对未知卡是放行还是拒绝、允许充多少电就强制停止。注意离线行为不是一个开关而是一组配置键的组合。上线前务必把这几个键的取值和运营策略对一遍否则会出现“断网后全场停摆”或者“断网后谁都能白充”两种极端。这两块内容之所以重要是因为它们直接对应真实运营中的两个痛点电网容量不够和网络不可靠。规范把它们写进正文说明起草者是从现场经验出发的不是纸上谈兵。4. 五个最容易搞混的地方OCPP 1.6J 的常见误区4.1 误区一1.6J 是 1.6 的“新版本”易混点看到 1.6 后面挂个 J很自然理解成 1.6 的升级版功能更多。为什么混版本号加字母的组合在软件行业里常表示修订或增强直觉上会往“更强”上靠。再叠加日常说法里“1.6J 比 1.6 新”这种口头简化误解就被固化了。怎么区分J 表示的是传输方式不是功能层级。OCPP 1.6 这份规范本身定义了两套传输绑定一套用 SOAP over HTTP一套用 JSON over WebSocket。带 J 通常指后者。两者的业务消息、字段含义、状态码是同一套差别在怎么把这些消息装进网络包里。所以当你看到某设备“支持 1.6J”它说的是通信载体不是它比“1.6”多了什么业务能力。做选型时如果对方说“我们支持 1.6J所以功能更全”这句话本身就露了底。4.2 误区二以为 OCPP 管计费和收钱易混点规范里有交易、有电表读数、有卡号看起来像一套完整的计费系统。为什么混交易这个词太容易让人联想到订单和金额而规范里确实又有“启动交易”“停止交易”这样的操作名光看字面很难不往钱上想。怎么区分OCPP 管的是“充了多少电、什么时候充的、这张卡被允许了”这些事实的传递它不定义电价、不生成账单、不接支付渠道。钱怎么算、优惠怎么叠、发票怎么开全在后台的业务系统里。规范里连金额字段都没有它只保证后台能拿到足够准确的计量数据去算钱。把 OCPP 当计费协议会在做需求评审时反复找不到对应消息最后被迫自己造字段造出来的东西别人又不认。4.3 误区三充电点、连接器、事务混着叫易混点日常说话时“这个桩”“这把枪”“这单”经常混用。为什么混它们在物理上确实长在一起一根桩带几把枪一把枪一次只服务一单。口语里省略掉量词指代就模糊了。怎么区分充电点是整台设备是连接的最小单位连接器是桩上的一个物理接口编号从 1 开始事务是一次充电过程从启动交易到停止交易有自己的交易号。规范里连接器编号有一个特殊值 0表示“整个充电点”而不是某把枪下发整桩级指令时会用到。搞不清这三个词看规范里的字段说明会一直绕因为你不知道该把参数填到哪一层。4.4 误区四预约和充电配置文件是一回事易混点两者都跟“提前安排”有关都是后台下发。为什么混语义上都带一点“预留”的味道而且实际项目里这两个功能经常同时出现在需求文档的同一页。怎么区分预约解决的是“这个接口在某段时间留给某个人”本质是占用权的分配消息是立即预约和取消预约。充电配置文件解决的是“这段时间功率能开到多大”本质是能量约束消息是设置和清除配置文件。一个管资格一个管功率可以同时存在某把枪被预约了同时它还受整桩功率上限约束。混为一谈的后果是做权限需求时翻到功率相关的消息越看越不对。4.5 误区五心跳就是“我还活着”的状态上报易混点心跳听起来就是定期报个平安和状态通知差不多。为什么混两个消息都是周期性的都跟“设备在不在线”有关运维看监控大盘时经常只盯一个在线率。怎么区分心跳的响应里带后台当前时间它的实际作用之一是让桩校准时钟顺便让后台知道连接还活着状态通知报的是连接器的具体状态比如可用、准备中、充电中、故障、离线只在状态变化时发。把两者混淆的后果是以为心跳间隔可以随便设很长结果桩时钟漂移导致交易时间戳错乱事后再去对账时数据全对不上这种问题排查起来相当费劲。5. 消息长什么样从三个 JSON 数组看懂一次远程启动5.1 三种帧CALL、CALLRESULT、CALLERROR1.6J 跑在 WebSocket 上连上之后双方发的每条消息都是一个 JSON 数组第一个元素是数字标明这是哪种帧。建立连接时桩会在握手阶段声明自己用哪个子协议版本服务端认可后才开始收发。这一步没谈拢后面什么消息都发不出去所以现场遇到“连上了但不通”先看这一层。类型 2 是 CALL发起一次调用后面跟着消息唯一编号、操作名、负载。类型 3 是 CALLRESULT表示成功响应带着同一个编号和返回负载。类型 4 是 CALLERROR表示失败除了编号还有错误码、错误描述和详细信息。// CALL发起调用2 表示这是一次请求 [2, 1001, Heartbeat, {}] // CALLRESULT成功响应3 表示结果编号必须与请求一致 [3, 1001, {currentTime: 2024-05-20T08:30:00.000Z}] // CALLERROR失败响应4 表示出错 [4, 1002, NotSupported, Action not implemented, {}]数组第二个位置的编号是配对用的。请求方发出去一个编号响应回来必须带同一个编号否则分不清这是回哪一条。规范要求这个编号在一段时间内唯一实际实现里用递增数字或者随机字符串都行。第三个位置在请求帧里是操作名在错误帧里是错误码。错误码的取值也是规范定死的比如未实现、不支持、内部错误、协议错误、安全错误、格式违规等等不要自己发明。提示CALLERROR 里的错误描述是给人看的程序判断要靠错误码。联调时经常见到两边对错误码的语义理解不一致本文还有配套的精品资源点击获取