OCPP1.6 消息事件 JSON 格式解析与本地模拟器实战

发布时间:2026/10/16 12:49:11
OCPP1.6 消息事件 JSON 格式解析与本地模拟器实战
简介面向欧标充电桩开发与测试人员的OCPP1.6协议消息事件JSON格式参考包适合从事充电桩通信调试、后台对接及协议学习的工程师使用。包内共78个JSON文件压缩包约28KB全部为协议原文定义的JSON结构覆盖BootNotification、Authorize、StartTransaction、MeterValues、RemoteStartTransaction、SetChargingProfile、FirmwareStatusNotification等核心消息事件每个事件均包含request与response两种格式并标注必要字段与可选字段。OCPP采用一问一答的通信模式读者可依据这些JSON模板模拟发送数据包还原充电桩注册、鉴权、启停充电、计量上报、远程控制及固件升级等完整业务流程也可用于接口联调与字段校验。目前已有5462人学习下载适合需要快速查阅OCPP1.6消息结构、搭建模拟桩或排查协议字段问题的开发者参考。1. 欧标充电桩 OCPP1.6 消息事件 JSON 格式从抓包到跑通本地模拟器第一次接触欧标桩的兄弟大概率会被一串串 JSON 报文整懵。充电桩和后台之间到底在聊什么插枪、刷卡、启动、计量、结束每一步都对应一条 OCPP1.6 消息而消息体就是 JSON。这个项目把 OCPP1.6 里常见的消息事件按 JSON 格式整理出来配合一个能在 Linux 上跑起来的本地模拟器让你不用真桩也能把 Charge Point 和 Central System 的对话走一遍。适合谁做充电桩固件、做后台运营平台、做协议对接测试的开发者尤其是需要拿真实报文对照调试的人。它解决的不是“协议是什么”而是“报文长什么样、怎么发、怎么收、怎么改”。2. 协议栈与目录结构OCPP1.6 在 Linux 上怎么落地2.1 为什么是 OCPP1.6 而不是 2.0.1欧标交流桩目前存量最大的还是 OCPP1.6J也就是 JSON over WebSocket 这一套。2.0.1 虽然引入了 Device Model 和更规范的类型系统但现网大量后台和桩端固件仍以 1.6 为主。这个项目选 1.6 作为基准意味着你拿到的报文可以直接和大多数运营平台对接不需要做版本翻译。核心通信模型是充电桩作为 WebSocket 客户端主动连到中央系统CSMS的/ocpp/路径之后所有消息都是 JSON 数组形如[2, uniqueId, action, payload]。其中2表示 CALL3表示 CALLRESULT4表示 CALLERROR。这个数字前缀是 OCPP-J 的帧类型标识不是消息序号很多新手第一次看会误以为是消息 ID。2.2 目录结构一份能直接对照的工程骨架项目在 Linux 下的目录组织大致如下我按实际拆包习惯整理成表方便你定位每个文件的作用路径作用ocpp16/messages/按 action 分类的 JSON 样例如 BootNotification、Heartbeat、StartTransactionocpp16/schemas/各消息的 JSON Schema用于校验字段类型和必填项simulator/cp/充电桩侧模拟器负责发起连接、发送 CALL、处理 CALLRESULTsimulator/csms/中央系统侧模拟器负责接收 CALL、返回 CALLRESULT、下发远程指令config/WebSocket 地址、ChargePointId、心跳间隔等配置logs/按日期切分的报文日志方便抓包对照这个结构的好处是消息定义和模拟器分离。你改协议字段只动messages/和schemas/改行为逻辑只动simulator/。我一般会先把messages/里的样例过一遍再去看模拟器怎么组装这些字段最后跑起来抓日志验证。2.3 核心消息类型与 JSON 字段速查OCPP1.6 的消息按方向分两类桩到后台CP→CSMS和后台到桩CSMS→CP。下面这张表列出最常打交道的几条字段名和类型都来自项目里的 schemaAction方向关键字段说明BootNotificationCP→CSMSchargePointVendor, chargePointModel, firmwareVersion桩上线第一件事后台返回心跳间隔HeartbeatCP→CSMS无按后台下发间隔发送维持连接StatusNotificationCP→CSMSconnectorId, status, errorCode枪口状态变化如 Available、ChargingStartTransactionCP→CSMSconnectorId, idTag, meterStart, timestamp刷卡启动后台返回 transactionIdMeterValuesCP→CSMSconnectorId, transactionId, meterValue周期上报电量sampledValue 里带 measurandStopTransactionCP→CSMStransactionId, meterStop, timestamp, reason结束充电reason 区分 Local/Remote/EmergencyStopRemoteStartTransactionCSMS→CPidTag, connectorId后台远程启动桩回 Accepted 后走本地启动流程ResetCSMS→CPtypeHard 或 Soft桩需回应并执行重启注意meterValue是一个数组里面每个元素带timestamp和sampledValue数组。sampledValue里的measurand决定这个值是电量、电压还是电流不写默认是能量。很多对接问题就出在 measurand 没对齐。3. 跑起来本地模拟器启动与报文抓取3.1 环境准备与依赖安装项目跑在 Linux 上Python 3.8 即可。WebSocket 库用websocketsJSON 校验用jsonschema。我一般会建一个干净的 venv避免和系统包打架# 创建虚拟环境并激活 python3 -m venv venv source venv/bin/activate # 安装依赖版本按项目 requirements.txt 来 pip install websockets jsonschema # 确认目录结构完整 ls ocpp16/messages simulator/cp simulator/csms config如果websockets装不上先看 Python 版本是否低于 3.8再确认 pip 源是否可用。Linux 下常见问题是系统自带 Python 缺少ssl模块导致 wss 连不上这时需要装libssl-dev后重新编译 Python或者先用 ws 明文调试。3.2 启动 CSMS 与 CP 模拟器先起中央系统再起充电桩。两个进程可以开两个终端也可以后台跑。CSMS 默认监听0.0.0.0:9000CP 连ws://127.0.0.1:9000/ocpp/CP001# 终端1启动中央系统模拟器 python simulator/csms/server.py --host 0.0.0.0 --port 9000 # 终端2启动充电桩模拟器指定 ChargePointId python simulator/cp/client.py --url ws://127.0.0.1:9000/ocpp/CP001 --cp-id CP001启动后 CP 会先发 BootNotificationCSMS 返回status: Accepted和interval: 30。接着 CP 按 30 秒发 Heartbeat同时发一条 StatusNotification 把 connectorId1 置为 Available。你可以在logs/下看到按时间命名的日志文件每条报文都带方向标记。3.3 用日志对照 JSON 报文日志里一条完整的 StartTransaction 交互长这样我截取关键部分// CP - CSMS [2, 1001, StartTransaction, { connectorId: 1, idTag: 04A1B2C3D4, meterStart: 0, timestamp: 2024-06-01T10:00:00Z }] // CSMS - CP [3, 1001, { transactionId: 10001, idTagInfo: { status: Accepted } }]uniqueId这里用的是1001实际项目里建议用 UUID 或递增数字只要同一连接内不重复即可。meterStart单位是 Whtimestamp必须是 ISO8601 UTC 格式带Z后缀。后台返回的transactionId是后续 MeterValues 和 StopTransaction 的关联键丢了它整条充电会话就对不上账。3.4 触发远程指令与状态流转CSMS 侧可以主动下发 RemoteStartTransaction模拟后台远程启动。项目里提供了一个简单的交互入口在 CSMS 终端按提示输入即可。下发后 CP 会回 CALLRESULT然后走本地 StartTransaction 流程。整个状态流转是Available → Preparing → Charging → Finishing → Available。每个状态变化都对应一条 StatusNotificationerrorCode在正常时是NoError异常时按 OCPP 定义填GroundFailure、OverCurrentFailure等。提示调试时把心跳间隔调大比如 300 秒减少日志噪音。改config/下的heartbeat_interval即可不用动代码。4. 核心模块拆解消息组装、校验与收发链路4.1 消息组装从 Python 字典到 OCPP 帧CP 侧发消息的核心逻辑在simulator/cp/client.py里我把它简化成下面这段保留关键注释import json import asyncio import websockets class ChargePoint: def __init__(self, cp_id, url): self.cp_id cp_id self.url url self.msg_id 0 def _next_id(self): # 同一连接内唯一即可这里用递增数字 self.msg_id 1 return str(self.msg_id) async def send_call(self, ws, action, payload): # OCPP-J CALL 帧[2, uniqueId, action, payload] unique_id self._next_id() frame [2, unique_id, action, payload] await ws.send(json.dumps(frame)) # 等待对应的 CALLRESULT实际项目里要做超时和错误处理 resp await ws.recv() return json.loads(resp) async def start(self): async with websockets.connect(self.url) as ws: # 上线先发 BootNotification boot_payload { chargePointVendor: VendorX, chargePointModel: ModelA, firmwareVersion: 1.0.0 } result await self.send_call(ws, BootNotification, boot_payload) # result[2] 是 payload取心跳间隔 interval result[2].get(interval, 30) # 后续按 interval 发 Heartbeat此处省略循环send_call把 action 和 payload 包成标准帧unique_id用来匹配请求和响应。真实场景里不能像这样简单recv一次就完事因为后台可能同时下发指令消息会交错。常见做法是维护一个pending字典按 uniqueId 存 Future收到 CALLRESULT 或 CALLERROR 时再唤醒对应的等待者。4.2 JSON Schema 校验把字段错误拦在发送前项目在ocpp16/schemas/下为每条消息准备了 schema。发送前校验一遍能避免很多低级错误比如connectorId写成字符串、timestamp格式不对。校验逻辑大致如下import json import jsonschema def validate_message(action, payload, schema_dirocpp16/schemas): # 按 action 加载对应 schema 文件 schema_path f{schema_dir}/{action}.json with open(schema_path, r) as f: schema json.load(f) # 校验失败会抛 ValidationError捕获后打印具体字段 jsonschema.validate(instancepayload, schemaschema) return Truejsonschema.validate会检查必填字段、类型、枚举值。比如StatusNotification的status只允许Available、Preparing、Charging、SuspendedEVSE、SuspendedEV、Finishing、Reserved、Unavailable、Faulted这几个值写错了直接报错。我一般会在 CI 里跑一遍所有样例消息的校验防止改 schema 时漏掉某个字段。4.3 CSMS 侧的消息分发与响应CSMS 收到 CALL 后要根据 action 路由到不同处理函数再回 CALLRESULT。核心分发逻辑在simulator/csms/server.pyasync def handle_message(ws, raw): frame json.loads(raw) msg_type frame[0] if msg_type 2: # CALL需要处理并返回 CALLRESULT _, unique_id, action, payload frame handler ACTION_MAP.get(action) if handler: result_payload handler(payload) # 回帧[3, uniqueId, payload] await ws.send(json.dumps([3, unique_id, result_payload])) else: # 未知 action 回 CALLERROR await ws.send(json.dumps([4, unique_id, NotSupported, fUnknown action {action}, {}])) elif msg_type 3: # CALLRESULT匹配 pending 请求此处省略 passACTION_MAP是一个字典把 action 名映射到处理函数。BootNotification返回Accepted和心跳间隔StartTransaction返回transactionId和idTagInfo。未知 action 必须回 CALLERROR错误码用NotSupported否则桩端会一直等响应直到超时。4.4 计量数据上报MeterValues 的组装细节MeterValues 是字段最多的一条容易写错。项目里的样例把sampledValue拆得很细{ connectorId: 1, transactionId: 10001, meterValue: [ { timestamp: 2024-06-01T10:05:00Z, sampledValue: [ { value: 1200, measurand: Energy.Active.Import.Register, unit: Wh }, { value: 230.5, measurand: Voltage, unit: V }, { value: 6.2, measurand: Current.Import, unit: A } ] } ] }measurand不写时默认是Energy.Active.Import.Register单位默认 Wh。unit也要和 measurand 匹配电压用 V电流用 A功率用 W。后台按transactionId聚合这些值算电费如果 measurand 写错账单就对不上。我见过把Voltage写成voltage的schema 校验直接拦下省了后面扯皮。5. 避坑与排查对接 OCPP1.6 时最容易翻车的几件事5.1 现象桩连不上后台WebSocket 握手 404原因通常是路径不对。OCPP1.6 规定 CP 连到 CSMS 的/ocpp/路径下后面跟 ChargePointId比如ws://host:9000/ocpp/CP001。有些后台实现要求带尾斜杠有些不要求但路径前缀/ocpp/不能少。解决方法是先用wscat或浏览器控制台手动连一下确认路径和端口。如果后台用了反向代理还要检查代理是否透传了 Upgrade 头。5.2 现象BootNotification 发出后收不到响应连接被断原因可能是chargePointVendor或chargePointModel为空或者firmwareVersion格式不被后台接受。OCPP1.6 对这几个字段只要求 string但部分后台会做非空校验。解决方法是先看 CSMS 日志有没有收到帧收到了就看返回的 CALLERROR 错误码。如果是FormationViolation说明 JSON 结构不对重点检查是不是把 payload 直接当成了整个帧发出去。5.3 现象MeterValues 上报后后台不记账原因多半是transactionId对不上或者measurand和后台预期不一致。StartTransaction 返回的transactionId必须原样带到后续 MeterValues 和 StopTransaction 里。如果中间重连了transactionId 不能丢。解决方法是把 transactionId 存在会话上下文里每次上报前打印出来和后台日志比对。另外确认unit是否和后台配置一致有的后台只认 kWh你发 Wh 就会差 1000 倍。5.4 现象远程指令下发后桩没反应原因可能是 CSMS 发的 CALL 帧里uniqueId和桩端 pending 的冲突或者桩端没有正确处理 CSMS 发起的 CALL。OCPP 是双向的后台也能主动发 CALL桩端必须能接收并回 CALLRESULT。解决方法是检查桩端 WebSocket 接收循环是否只处理了 CALLRESULT 而忽略了 CALL。正确做法是收到帧后先看frame[0]是 2 就当 CALL 处理是 3 才当响应处理。5.5 现象心跳间隔不生效桩一直按默认值发原因是没有从 BootNotification 的响应里取interval。后台返回的 payload 里interval单位是秒桩端要动态更新定时器。如果桩端写死了 30 秒后台改成 60 秒也不会生效。解决方法是在 BootNotification 响应处理里更新心跳任务并处理status不是Accepted的情况比如Pending时要重试Rejected时要停止上线流程。6. 二次开发改协议字段、加消息类型与自动化验证拿到这个项目后最常见的改造是加一条自定义消息或改现有字段。我一般按这个顺序来先改ocpp16/schemas/下的 schema再改ocpp16/messages/里的样例最后动simulator/里的处理逻辑。三步缺一不可否则校验和实际行为会对不上。加一条新消息比如DataTransfer先在 schema 里定义vendorId、messageId、data三个字段data用 string 类型。然后在 CSMS 的ACTION_MAP里注册处理函数返回status: Accepted。CP 侧调用send_call(ws, DataTransfer, payload)即可。注意DataTransfer是 OCPP1.6 里预留的扩展点后台不认识时可能回UnknownMessageId这属于正常行为不要当成 bug。改现有字段时最容易踩的坑是改了 schema 但忘了改样例导致 CI 校验失败。我习惯在messages/目录下加一个validate_all.py遍历所有 JSON 文件跑一遍 schema 校验import json import glob import jsonschema for msg_file in glob.glob(ocpp16/messages/*.json): action msg_file.split(/)[-1].replace(.json, ) with open(msg_file) as f: payload json.load(f) with open(focpp16/schemas/{action}.json) as f: schema json.load(f) try: jsonschema.validate(payload, schema) print(f{action}: OK) except jsonschema.ValidationError as e: print(f{action}: FAIL - {e.message})这个脚本跑一遍几秒钟就能发现字段类型、必填项、枚举值的问题。我一般把它挂在 pre-commit 里改完 schema 自动跑省得手动核对。另一个实用改造是加报文录制和回放。在 CP 和 CSMS 的收发函数里各加一行写日志把[方向, 时间戳, 原始帧]存成 JSONL。回放时按时间戳顺序重发就能复现一整条充电会话。这个技巧在排查偶发问题时特别管用比反复插枪刷卡高效得多。从那以后我每次改 OCPP 相关代码都强制先跑一遍 schema 校验再跑模拟器确认报文格式和状态流转都对得上才敢往真桩上刷。希望帮到你。本文还有配套的精品资源点击获取