资讯动态

OCPP1.6 JSON消息事件全解析:充电桩上云与平台远程控制实战

发布时间:2026/10/20 13:13:23 来源:尧图企业网站定制
简介针对欧标充电桩 OCPP1.6 通信协议的开发与调试这套资源整理了协议中全部消息事件的 JSON 格式定义覆盖请求与响应两个方向并逐一标注必要字段与可选字段适合充电桩研发、测试及运维人员快速理解一问一答的通信机制。压缩包共 78 个 JSON 文件整体仅 28KB体量轻巧但内容完整既可直接查阅每条消息的后台字段也可在 Linux 环境下配合脚本快速解析或作为报文模拟器收发数据包的格式模板。目前已有 5467 人学习下载。借助这些定义能模拟从 BootNotification 启动注册、Authorize 鉴权、StartTransaction 启充、MeterValues 实时计量到 StopTransaction 结束充电的完整流程同时覆盖远程控制、充电配置、诊断与固件升级等扩展消息几乎所有 OCPP 标准消息都给出请求与响应的 JSON 结构。联调中遇到字段缺失、类型不符或错误码不明时也能依据这些定义快速定位问题提高协议对接效率。1. 欧标充电桩 OCPP1.6 消息事件 JSON格式从「桩上云」到「平台控桩」的最后一公里做充电桩运营平台最怕的不是桩本身坏而是桩和平台之间「失联」——电价配好了、远程启动指令发出去了结果桩没反应后台也看不到任何充电记录。这时候排查的第一个问题往往是你到底有没有把 OCPP 1.6 的消息事件按 JSON 格式发上来OCPP 1.6 的 JSON 版本也叫 OCPP 1.6 J是当前欧标充电桩和充电运营平台CSMS之间最常见的通信协议它把充电桩的启动、停止、计费、告警、心跳全部抽象成一组结构固定的 JSON 消息事件。对 Linux 环境下的开发者来说理解这几十个消息事件比啃几百页的协议 PDF 更实用。这篇笔记会把消息帧结构、生命周期事件、远程控制事件、工程落地的模块划分和避坑记录一起拆开作为你二次开发或对接平台的第一份实弹参考。2. OCPP 1.6 J 的消息骨架Call/CallResult/CallError 与三类核心事件2.1 消息帧结构三个数组元素承载全部语义OCPP 1.6 J 的每条消息都是 JSON 数组不是 JSON 对象。数组固定三到四个元素消息类型 ID、唯一消息 ID、动作名仅请求有、负载。消息类型 ID 有三种2 代表请求Call3 代表应答CallResult4 代表错误CallError。例如 BootNotification 请求[2, 12345678-1234-1234-1234-1234567890ab, BootNotification, { chargePointVendor: 某充电桩厂商, chargePointModel: 某型号, chargePointSerialNumber: SN20240001, chargeBoxSerialNumber: SN20240001, firmwareVersion: 1.0.0, iccid: , imsi: , meterType: , meterSerialNumber: }]关键点第二个元素的唯一消息 ID 是字符串由发起方生成应答时必须原样返回。第三个元素只在请求中出现应答数组只有三个元素类型、ID、负载。很多人第一次解析时把应答当成对象处理结果 JSON 序列化直接报错。实际开发时建议先用 Python 的json.loads拿到数组再按索引拆字段而不是直接转字典。负载字段由动作名决定以 BootNotification 为例充电桩厂商、型号、序列号是必填项固件版本和电表信息按实际情况填。平台收到后回 CallResult包含statusAccepted / Pending / Rejected、interval心跳间隔秒数、currentTime服务器时间。注意currentTime是 ISO 8601 格式带时区的字符串不是时间戳否则后端解析会翻车。2.2 充电生命周期事件从 BootNotification 到 StopTransaction一次完整的充电流程至少涉及六个消息事件BootNotification注册、Heartbeat心跳、StatusNotification状态变更、Authorize鉴权、StartTransaction启动充电、MeterValues电表值、StopTransaction停止充电。它们的先后顺序是刚性的平台会按状态机校验消息的时序。比如还没收到 StartTransaction就先来了 StopTransaction平台会直接拒绝并记录协议错误。StartTransaction 的请求负载包含idTag用户卡号或用户标识、meterStart启动时电表读数、timestamp启动时间、connectorId充电接口编号。平台应答时返回transactionId这个 ID 后续所有消息都要带。StopTransaction 的负载里必须带transactionId、meterStop、timestamp、reason停止原因如 Local / Remote / EmergencyStop。MeterValues 是这里最值得细看的消息它负责上报充电过程中的电表读数。负载里meterValue是一个数组每个元素包含timestamp和sampledValuesampledValue又是数组每项包含value数值、measurand测量项如 Energy.Active.Import.Register、unit单位如 Wh 或 kWh、context采样上下文。常见错误是把value写成字符串 12.345而协议要求是数值类型另一个是把measurand拼错或者漏了unit平台解析时直接丢弃该采样点。2.3 远程控制事件RemoteStartTransaction 与 RemoteStopTransaction远程启动和远程停止是平台下发到桩端的两个核心控制消息。RemoteStartTransaction 的负载包含connectorId和idTag部分场景还带chargingProfile充电参数配置。桩收到后会先判断当前状态下能不能启动然后先本地鉴权再启动充电最后回一个 CallResultstatus字段返回 Accepted 或 Rejected。这里有个协议时序细节平台发 RemoteStartTransaction 后桩启动充电紧接着桩会向平台发 StatusNotification状态从 Preparing 变成 Charging然后发 StartTransaction。平台端排查问题时常犯的错误是只盯着 RemoteStartTransaction 的回执没看后续的状态流。回执 Accepted 只代表桩接受了指令不代表充电已经建立。真正判断充电是否建立要看 StatusNotification 的状态变化和 StartTransaction 是否到达。RemoteStopTransaction 的负载只有一个transactionId桩收到后执行停止动作平台收到的确认应该是一个 CallResult然后桩再上报 StopTransaction。如果你在对接平台时发现「停止指令发了桩也停了但后台订单没关闭」大概率是 StopTransaction 没有带正确的transactionId或者平台没做幂等处理同一个 transactionId 收到了多次 StopTransaction。2.4 配置与状态事件ChangeConfiguration、GetConfiguration、StatusNotification运行期配置变更主要靠 ChangeConfiguration平台下发配置和 GetConfiguration平台查询配置。负载里的key是配置项名value是配置值。OCPP 1.6 定义了一批标准配置键比如HeartbeatInterval心跳间隔、MeterValueSampleInterval电表值采样间隔、ConnectionTimeOut连接超时、AuthorizeRemoteTxRequests远程启动是否要求本地鉴权等。自定义配置键容易踩坑不是所有平台都允许任意键对接前要确认双方配置字典一致。StatusNotification 是桩向平台上报当前状态的频繁消息。status字段的取值有 Available、Preparing、Charging、SuspendedEVSE、SuspendedEV、Finishing、Reserved、Unavailable、Faulted。每个状态转换都有业务含义比如 SuspendedEV 表示车端暂停车上的按钮SuspendedEVSE 表示桩端暂停桩内部逻辑。平台端做订单计费、状态展示都要依赖这套状态机如果桩上报的状态顺序错乱平台侧的充电记录可能出现「充电中直接跳到已结束」的诡异现象。3. Linux 端落地把 OCPP 1.6 消息事件做成可维护工程3.1 模块划分链路层、消息层、业务层三层设计在 Linux 上做充电桩的 OCPP 对接我一般不会把协议解析和业务逻辑写在一个文件里。常见的做法是先按三层拆链路层负责 WebSocket 连接管理与心跳消息层负责 JSON 帧的组包、拆包、消息 ID 管理业务层负责把消息事件映射到具体的充电业务动作。这样拆的好处是换个平台对接时只改消息层的配置不用动业务层。以 Go 语言为例链路层用一个websocket.Conn封装消息层定义一个Message结构体业务层注册各自的 handler。伪代码如下type Message struct { MessageTypeID int json:messageTypeId UniqueID string json:uniqueId Action string json:action Payload json.RawMessage json:payload }代码说明MessageTypeID对应 2/3/4 三种类型UniqueID用于关联请求和应答Action只在请求中使用Payload用json.RawMessage延迟解析。后续拿到消息时先判MessageTypeID再按Action分发。这样做有几个实际好处消息 ID 的生成与校验集中在消息层业务 handler 拿到的都是解析好的结构体出错时能定位到是链路层断了还是业务层逻辑错了。3.2 JSON 字段与数值类型最容易被忽略的规范映射OCPP 1.6 J 的 JSON 字段命名全部是驼峰式比如chargePointVendor、meterStart、transactionId。Go 结构体标签里要显式写好 JSON 名否则默认会把字段名原样序列化结果平台收到ChargePointVendor这种首字母大写的字段名直接拒绝。数值类型是另一个高频翻车点。OCPP 1.6 规范里meterStart、meterStop、value都是数值类型。有些桩的固件把电表读数拼成字符串上报比如meterStart: 123456这在 JSON 层面不报错但平台在按数值解析时会出错或者丢精度。比如 12.345 和 12.3450 在 JSON 数值里是等价的但字符串 12.3450 和 12.345 不是同一回事。建议在消息层统一做类型清洗。比如用 Python 的 pydantic 或 Go 的json.Number把负载里的数值字段全部转成float64再进业务层。比较实际的做法是写一个校验函数专门检查meterStart、meterStop、value是否为数值类型不是就直接丢弃并记录错误日志而不是带病上报给平台。3.3 心跳与离线重连保活机制与队列策略OCPP 1.6 J 的心跳机制是桩启动后发 BootNotification平台回了interval字段秒数桩必须在interval秒内发一次 Heartbeat平台收到后回当前时间。如果桩没有在间隔内发心跳平台可以主动断开连接。实际运行中平台返回的interval不一定靠谱有的平台直接回 0这时候桩端要设一个默认值常见做法是 60 秒。离线重连是工程实践里的重点。WebSocket 断线后桩端不能无限重连否则会形成「重连风暴」——每次断开所有桩同时重连平台直接被请求打挂。我一般会做指数退避第一次断开等 5 秒第二次 10 秒第三次 20 秒最多等 60 秒直到连接建立为止。同时断线期间的本地事件如充电状态变化、电表值采样要进本地队列重连成功后按顺序补报。补报不能无脑全发要先看消息的时效性。StatusNotification 可以补报最近一次状态但 MeterValues 如果堆积太多平台端可能按timestamp过滤掉过期数据补报的意义不大。实际处理经验是MeterValues 只保留最近 5 分钟内的采样点超过的丢弃避免重连后发出一大包垃圾数据。3.4 数据落库充电记录用 SQLite 兜底就算平台端暂时不可用桩本地的充电记录也不能丢。在 Linux 桩端设备上SQLite 是最常见的落库方案不用额外起服务断电重启也能恢复。我一般会设计两张表一张charging_transactions存订单主档一张meter_values存采样明细。CREATE TABLE charging_transactions ( transaction_id TEXT PRIMARY KEY, connector_id INTEGER, id_tag TEXT, meter_start REAL, meter_stop REAL, start_time TEXT, stop_time TEXT, reason TEXT, synced INTEGER DEFAULT 0 ); CREATE TABLE meter_values ( id INTEGER PRIMARY KEY AUTOINCREMENT, transaction_id TEXT, timestamp TEXT, value REAL, unit TEXT );SQL 说明transaction_id用平台返回的值作为主键一张订单只对应一条记录synced字段标记该订单是否已成功上报到平台。上报表设计为unique(transaction_id, timestamp)可以避免重连后重复上报同一笔数据。落库时机要注意先落库再回执还是先回执再落库。如果桩先回 CallResult 给平台再写数据库此时断电平台以为桩收到了桩自己反而丢了记录。正确顺序是先写库、再回 CallResult。这条规则在我实际对接多个平台时反复出现过问题后面避坑章节还会专门展开。4. 平台接入配置把消息事件接到你的 CSMS4.1 WebSocket 端点与连接参数配置OCPP 1.6 J 默认走 WebSocket平台的服务端点形如ws://平台地址/ocpp/设备序列号或wss://平台地址/ocpp/设备序列号。桩端连接时URL 路径里通常要带设备标识平台用这个标识来识别是哪台桩发来的消息。常见的对接反而是在这里出问题平台文档给的是wss://example.com/ocpp桩端却只配了wss://example.com缺少/ocpp路径结果是 WebSocket 握手能成功但平台侧找不到对应的桩直接断开。连接建立之后平台和桩之间靠 WebSocket 的 Ping/Pong 来探测链路是否存活。WebSocket 协议本身有 Ping/Pong 帧OCPP 1.6 的 Heartbeat 是在其之上的应用层心跳两者都有。有的桩会把这两个搞混以为发了 WebSocket Ping 就不用发 OCPP Heartbeat结果平台在 OCPP 层面超时把连接断开。实际配置建议WebSocket Ping 用 30 秒间隔OCPP Heartbeat 用平台返回的interval如果平台没明确返回默认 60 秒。日志里同时记录 Ping/Pong 和 OCPP Heartbeat 的时间戳排查断线时能分清是链路层问题还是应用层问题。4.2 mTLS 双向认证与证书链校验欧标充电桩对安全要求普遍高于国内的家用桩很多平台强制要求 mTLS双向 TLS 认证——桩和平台都要验证对方的证书。桩端需要预置一个客户端证书client certificate和对应的私钥平台侧预置服务端证书。连接时除了正常的 TLS 握手平台还要校验桩的客户端证书桩也要校验证平台的证书链。在 Linux 桩端证书文件一般放在/etc/ocpp/certs/目录下配置项包括cert_path、key_path、ca_path。常见错误是证书格式不对平台给的是 PEM 格式桩端却按 DER 读或者证书链不完整只放了叶子证书没放中间 CA。排查这类问题最直接的方式是用 openssl 命令验证桩端证书和平台证书是否匹配openssl s_client -connect 平台地址:443 -CAfile /etc/ocpp/certs/ca.pem -cert /etc/ocpp/certs/client.pem -key /etc/ocpp/certs/client.key命令说明-CAfile指定平台证书链的根 CA-cert指定桩端客户端证书-key指定对应私钥。如果输出里有Verify return code: 0 (ok)说明证书链路正常如果出现证书未知或主机名不匹配大概率是证书域名或 IP 和平台配置的对不上。私钥权限是另一个常被忽略的细节。私钥文件必须是桩运行用户可读且不应该被其他用户读取建议chmod 600。有些团队为了省事把私钥权限设成 644进程跑起来没问题但审计时会被平台侧的安全检查直接刷掉对接一天才发现是权限问题。4.3 关键配置项对照从桩端文档到平台配置对接不同平台时双方配置项经常对不上。常见的是平台文档里写的配置键名和协议标准键名有出入比如标准名为AuthorizeRemoteTxRequests平台文档写成authorizeRemoteTx桩端按标准发平台不认远程启动直接失败。我把通常情况下最常碰到的配置项整理成一张对照表配置键标准含义常见平台文档写法建议值HeartbeatInterval心跳间隔秒heartbeat.interval60MeterValueSampleInterval电表采样间隔秒meter.sample.interval120ConnectionTimeOut连接超时秒connect.timeout60AuthorizeRemoteTxRequests远程启动是否鉴权auth.remote.requiredfalseLocalAuthListEnabled是否启用本地鉴权列表local.auth.enabledtrue提示对接前先拿平台测试环境的配置文件逐项比对不要直接上生产尤其不要想当然认为平台会用协议标准名。这个对照表是从协议标准键出发整理的实际以平台文档为准。5. 避坑实际跑 OCPP 1.6 J 常见的 5 个翻车现场5.1 连接秒断WebSocket 握手成功连接的路径少了后缀现象桩端 WebSocket 握手成功但几秒后平台主动断开重试若干次仍然如此。原因连接 URL 缺少/ocpp/路径段。桩端只配置了wss://平台地址平台按路径wss://平台地址/ocpp/桩号来路注册设备URL 不匹配导致平台无法识别桩身份。解决查看平台文档中的 WebSocket 端点 URL 格式桩端配置文件里补上完整路径。改完后再看日志确认握手请求的 URL 带上了/ocpp/后缀。从那以后我每次配新平台第一件事就是先打印完整的 WebSocket 握手 URL。5.2 StartTransaction 被拒绝idTag 没走授权流程现象平台能收到 StatusNotification但 StartTransaction 返回的状态是 Rejected。原因OCPP 1.6 默认要求先走 Authorize 或本地鉴权列表平台才认可用户标识。桩端直接跳过鉴权流程平台侧找不到对应的授权记录自然拒绝启动。解决确认桩端是否在启动前执行了 Authorize 或本地鉴权列表检查。如果平台配置允许不鉴权启动需要把相关配置设为 false 并在平台侧绑定认证规则。常见做法是保留 Authorize 流程在用户刷卡后先将idTag上报做鉴权等平台返回 Accepted 后再启动充电。5.3 MeterValues 乱码电表读数类型混用现象平台能收到消息但充电记录里的电量数据异常比如出现 0.0 或数值跳变。原因桩端上报的value字段类型不统一。网络采集模块把采样值转成了字符串或单位混用如有的采样点按 Wh 上报有的按 kWh 上报平台侧按统一单位换算时出现混乱。解决在桩端消息层统一采样值的数值类型转换value字段一律转成数值unit字段按协议标准枚举归一化。单位换算不在桩端做只把原始值按规范的计量单位上报让平台端负责换算。另外在告警页面对 MeterValues 做单元测试用固定读数验证数值和单位是否符合预期。5.4 重连风暴断网恢复后平台被所有桩同时打过来现象平台在恢复供电或网络恢复后瞬间收到大量 BootNotification 和 HeartbeatCPU 飙升部分消息直接超时丢弃。原因桩端重连策略用的是固定间隔比如每 5 秒重连一次同时恢复的桩数量多对平台形成瞬时洪峰。解决重连间隔采用指数退避同时增加随机抖动让不同桩的重连时间错开。代码实现时维护一个退避计数器每次重连失败后加倍直到最大值并设上限比如从 5 秒退避到 60 秒封顶。另在恢复后分批上报本地缓存消息避免一次性全量补报。5.5 断电丢数据先回执再落库的糟糕顺序现象桩端重启后平台显示订单已结束但桩侧数据库中查不到对应的 charging_transactions 记录结算时出现数据不一致。原因桩端在收到平台 CallResult 后就标记任务完成此时尚未把事务持久化到 SQLite。断电或进程被杀时内存里的订单丢失而平台端的记录还在。解决把落库时机前置先写入 SQLite 并提交事务再回 CallResult 给平台。断线重连时以本地事务状态为准synced0的订单重发已经是synced1的直接忽略。从那以后我每次做充电桩相关上报逻辑都会确认「先落库、后回执」这条顺序没有被绕过。6. 用最小工具验证消息事件Python 模拟器与离线分析6.1 Python 模拟器不接真实桩先跑通协议对接平台时不一定有真实充电桩随时可用。我习惯用一段 Python 脚本模拟桩端行为先把协议流程跑通。最小模拟器只做三件事连接 WebSocket、发 BootNotification、周期性发 Heartbeat 和 MeterValues。import asyncio, json, websockets async def send_boot_and_heartbeat(ws): # 先发 BootNotification平台回 interval 后按此间隔发心跳 boot [2, boot-001, BootNotification, { chargePointVendor: DemoVendor, chargePointModel: DemoModel, chargePointSerialNumber: SN-DEMO-001 }] await ws.send(json.dumps(boot)) resp json.loads(await ws.recv()) interval resp[2].get(interval, 60) while True: await asyncio.sleep(interval) await ws.send(json.dumps([2, hb-001, Heartbeat, {}])) await ws.recv() async def main(): uri wss://平台地址/ocpp/SN-DEMO-001 async with websockets.connect(uri) as ws: await send_boot_and_heartbeat(ws)代码说明[2, boot-001, BootNotification, {...}]按消息帧结构组包第二个元素是消息 ID接收平台回执后从负载里取interval如果平台没返回则默认 60 秒。这个模拟器适合验证平台侧是否正确解析 BootNotification 和 Heartbeat也能用来验证平台答应的连接路径。6.2 日志分析优先用 grep 和 jq 从消息流里找问题对接平台时最有效的排障方式不是看平台端后台而是直接看桩端的消息日志。把所有收发消息按行写入日志文件用 grep 和 jq 做离线分析。比如查某个 transactionId 对应所有消息grep 交易ID /var/log/ocpp/ocpp.log | jq .[2]说明grep先筛出包含目标交易 ID 的行jq .[2]取出 JSON 数组的第三个元素动作名就能看这条流水线上进过了哪些事件。排查「平台没收到某消息」时直接比对桩端日志的发送记录和平台端日志的接收记录一分钟能定位是桩端没发还是网络层丢了还是平台端解析失败。6.3 进阶加事件回调钩子为运维监控留后门模拟器跑通之后可以再往外扩一步把消息收发做成事件回调方便以后接告警、日志上报。实现思路不复杂在消息层加一个on_message回调把解析好的消息结构体和原始 JSON 一起抛出去。以后想统计心跳成功率、上报延迟只需要监听这个回调完全不用动协议核心逻辑。拿真实桩调试时我习惯把 MeterValues 的采样值和提交时间都记录在同一行日志里方便按时间线回放充电过程。遇到平台端电量对不上账的问题直接拉一段日志出来对照比在桩上抓包快得多。希望这篇笔记能帮你少踩一些我已经踩过的坑对接顺利。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑