☰
OCPP 1.6J充电桩通信协议全链路解析与联调避坑指南
2026/11/3 12:09:57 网站建设 项目流程

简介:面向电动汽车充电桩与中央管理系统通信开发的 OCPP 1.6J 协议学习资料,适合充电设备厂商、充电服务运营方及后端平台开发、测试人员作为入门与查阅手册。压缩包仅 1 个 PDF 文件,约 3.84MB,内容为开放充电点协议 1.6 第 2 版最终版(2017-09-28),目录覆盖范围、术语与约定、导言、功能配置文件、本地授权与离线行为、连接器编号、ID Tokens 以及消息交换等章节。读者可据此理清充电点与中央系统基于 TCP/IP 的初始化连接、认证、心跳与命令响应流程,掌握远程启停、状态上报、计费授权、加密通信和远程升级等功能的协议要求,并对照功能配置文件划分实现范围。文档结构完整、层级清晰,便于按章节检索条款,也适合作为对接联调与合规自查时的参照。目前已有 879 人学习。

1. 一张地图先铺开:OCPP 1.6J 在管什么

OCPP 1.6J 这套规范,2017-09-28 的第 2 版最终稿,第一次翻目录最容易犯懵:一边是充电点主动发起的一堆.req,一边是中央系统下发的另一堆.req,名字长得还都像。把 OCPP 1.6 的目录压扁了看,其实只有三条主线——传输层、消息层、业务层。传输层是 JSON over WebSocket,这也是 1.6J 里那个 J 的由来;消息层是 CALL、CALLRESULT、CALLERROR 三种帧加一一配对的 UniqueId;业务层才是 Core、Firmware Management、Local Auth List Management、Reservation、Smart Charging、Remote Trigger 这六个功能配置文件。谁发起、谁应答,全部落在这六个配置文件里,不存在第七种花样。

这份笔记适合已经能读懂报文、但脑子里还没形成索引的人:做充电桩固件、做后台运营系统对接、或者正准备把一套设备从私有协议迁到 OCPP 1.6J 的开发者。下面按「消息骨架 → 两个发起方向 → 易错点 → 自测清单」的顺序铺,每一章都能单独拿来做检索。

2. 消息骨架:CALL / CALLRESULT / CALLERROR 三种帧与六个功能配置文件

2.1 传输层:J 这个字母决定了后面所有报文的形态

OCPP 1.6 有两条技术路线,一条走 SOAP over HTTP,一条走 JSON over WebSocket。后缀 J 就是标明后者:JSON 编码、WebSocket 长连接、双向推送。这条选型决定了后面所有报文的样子——中央系统能主动下发指令,靠的是 WebSocket 这条一直挂着的连接,而不是充电点去定时轮询。连接建立之后,双方各自持有对端端点,任何一个 Action 都能从任意一侧发起,谁发起谁生成 UniqueId,谁接收谁原样带回。

理解这一点很关键。很多刚接触 OCPP 1.6 的人看第 4 章那一堆「由中央系统发起的操作」,会觉得是另一套接口;其实不是,它们和充电点发起的操作共用同一种帧结构,只是方向相反。真正需要单独记的只有一件事:消息的发起方决定了.req由谁填、.conf由谁回,跟业务语义无关。

2.2 三种消息类型与 UniqueId 的配对规则

OCPP 1.6J 的每一帧都是一个 JSON 数组,第一个元素是 MessageTypeId:2 表示 CALL,3 表示 CALLRESULT,4 表示 CALLERROR。CALL 是四元数组,CALLRESULT 是三元数组,CALLERROR 是五元数组。这个「数组而非对象」的设计常被吐槽不好读,但它的好处是解析极快、字段位置固定。

[ 2, "0f3a9c1e-8b2d-4a71-9d55-6c0e2f7b1a34", "BootNotification", { "chargePointVendor": "VendorX", "chargePointModel": "ModelA", "chargePointSerialNumber": "SN-0001", "firmwareVersion": "1.0.0", "iccid": "8900000000000000000", "imsi": "000000000000000", "meterType": "AC", "meterSerialNumber": "MSN-0001" } ]

这段是一个典型的 CALL 帧。第一位2是消息类型;第二位是 UniqueId,由发起方生成,接收方在应答里必须一字不差地带回来,配对全靠它;第三位是 Action 名,必须是规范里定义过的字符串,大小写敏感;第四位是 payload,字段名同样大小写敏感。对应的应答帧是[3, "0f3a9c1e-...", {"status": "Accepted", "currentTime": "2024-05-01T08:00:00Z", "interval": 300}],只有三位,没有 Action 名——因为请求方靠 UniqueId 就知道这是哪条请求的回执。

CALLERROR 的五位分别是消息类型 4、UniqueId、错误码、错误描述、错误详情。错误码的枚举是固定的:NotImplemented、NotSupported、InternalError、ProtocolError、SecurityError、FormationViolation、PropertyConstraintViolation、OccurenceConstraintViolation、TypeConstraintViolation、GenericError。联调时看到NotImplemented,八成是对方没实现这个 Action;看到FormationViolation,先去查字段类型,别急着怀疑网络。

提示:UniqueId 用 UUID 是最省事的做法,别用自增数字。多个连接并发、重连后重发同一个 UniqueId 的场景下,自增数字很容易撞车,排查起来非常费劲。

2.3 req 与 conf 的字段约束:必填、可选与向前兼容

规范里每一条消息都标了字段的必填性。以 BootNotification.req 为例,chargePointVendor和chargePointModel是必填,其余可选;但「可选」不等于「可以乱填」,字段一旦出现,类型和长度约束就生效。真正容易忽略的是兼容性约定:接收方遇到自己不认识的字段,必须忽略而不是报错。这条约定是 OCPP 1.6 能跨厂商互操作的地基,实现时如果解析器写成严格模式、多一个字段就抛异常,跟别家对接时一定会踩坑。

反过来,发送方也不能指望对方一定读得懂扩展字段。想传私有数据,规范给的正道是 DataTransfer,而不是往标准消息里硬塞自定义键。这一点后面第 4 章还会提到。

2.4 六个功能配置文件:支持即声明的潜规则

OCPP 1.6J 把能力切成六个功能配置文件,设备支持哪个就实现哪个,中央系统通过 GetConfiguration 和实际调用去探测。这不是强制的握手协议,而是一种约定俗成的能力声明方式。

功能配置文件覆盖的主要 Action典型用途
CoreBootNotification、Heartbeat、Authorize、StartTransaction、StopTransaction、MeterValues、StatusNotification充电桩最基础的一整套,缺一不可
Firmware ManagementUpdateFirmware、FirmwareStatusNotification、GetDiagnostics、DiagnosticsStatusNotification远程升级与诊断包上传
Local Auth List ManagementSendLocalList、GetLocalListVersion离线刷卡授权名单
ReservationReserveNow、CancelReservation预约锁定某个连接器
Smart ChargingSetChargingProfile、ClearChargingProfile、GetCompositeSchedule功率调度与限流
Remote TriggerTriggerMessage让充电点主动补发某条消息

这张表建议背下来。联调时对方说「我们只支持 Core」,你就知道远程启停、固件升级、智能充电这些全都别指望,得换方案而不是继续调。

2.5 配置键:不改代码就能改行为的那些开关

OCPP 1.6J 里有一批标准配置键,用 GetConfiguration 读、ChangeConfiguration 写,行为立刻改变。它们是最容易被低估的一部分,因为很多现场问题根本不是协议实现错了,而是配置键的值不对。

配置键作用常见取值
HeartbeatInterval心跳间隔,单位秒300
MeterValueSampleInterval周期性电表采样间隔,单位秒60
MeterValuesSampledData需要上报的 measurand 列表Energy.Active.Import.Register,Power.Active.Import
MeterValuesAlignedData对齐到整点/整刻上报的 measurandEnergy.Active.Import.Register
NumberOfConnectors连接器数量1 或 2
LocalAuthorizeOffline离线时是否允许本地授权true
AllowOfflineTxForUnknownId未知 idTag 是否允许离线起充false
AuthorizationCacheEnabled是否启用授权缓存true
StopTransactionOnInvalidId收到无效 idTag 是否停止交易true
ConnectorPhaseRotation三相相序映射RST

前四个是现象级问题的源头:心跳间隔设成 0 会导致连接被判定超时,采样间隔设得太密会造成上行流量暴涨,采样数据列表里漏了Power.Active.Import就画不出功率曲线。这些都不需要改固件,属于运维层面能解决的范畴。

3. 充电点发起侧:从 BootNotification 到 StopTransaction 的完整链路

3.1 BootNotification:开机注册与心跳间隔的协商

充电点上电后的第一条业务消息就是 BootNotification,报上厂商、型号、序列号、固件版本,中央系统回 status、currentTime 和 interval。三个状态各有含义:Accepted 是正常放行;Pending 表示后台还在审核这台设备,充电点应该按 interval 继续发心跳等结果;Rejected 是明确拒绝,充电点不该无限重试,而应进入不可用状态。

这里有个高频遗漏点:currentTime不是摆设。规范要求充电点用中央系统下发的时间校准本地时钟,因为后面所有 MeterValues 的 timestamp、所有 StopTransaction 的时间都要求是 UTC 带 Z 后缀。设备本地时钟漂移几分钟,后台账单的时间轴就会错位。interval则是中央系统指定心跳周期,充电点必须以这个值为准,而不是继续用自己烧录的默认值。

3.2 Heartbeat 与 StatusNotification:连接器状态机怎么收敛

Heartbeat 是最简单的一条消息,req 无字段,conf 带 currentTime。它的作用是双向的:告诉中央系统「我还活着」,同时让充电点有机会持续校时。心跳丢了不代表交易断了,很多实现把心跳失败直接当成断连处理,导致正在充电的会话被误判结束,这是典型的过度反应。

StatusNotification 才是状态机的主角,每个连接器一条,字段是 connectorId、errorCode、status、timestamp。connectorId 为 0 表示整个充电点,1 到 N 表示具体连接器。status 的枚举值必须记准:

status含义
Available空闲,可接受新交易
Preparing车已插入但未开始充电
Charging正在充电
SuspendedEVSE充电桩侧暂停供电
SuspendedEV车辆侧暂停取电
Finishing交易结束中,等待收尾
Reserved已被预约锁定
Unavailable不可用,需人工介入
Faulted故障

SuspendedEVSE 和 SuspendedEV 的区别最常被写错:前者是桩主动停(比如智能充电限到 0),后者是车主动停(电池满了或车端策略)。后台如果靠状态判断充电是否正常,这两个搞反会得出完全相反的结论。

注意:每次连接器状态变化都要主动上报,不能等中央系统用 TriggerMessage 来问。靠问才报的实现,在 TriggerMessage 没实现的对端那里会直接变成哑巴。

3.3 Authorize 与 ID Token:授权链路的五种结局

刷卡的瞬间,充电点发 Authorize.req,带 idTag。应答的 idTagInfo 里 status 有五种:Accepted、Blocked、Expired、Invalid、ConcurrentTx。ConcurrentTx 表示这个 idTag 已经有一个进行中的交易,用于防止同一张卡在两把枪上同时起充。这个状态在单枪桩上几乎见不到,一旦上了多枪桩就是必测项。

授权链路有三层兜底,顺序不能乱:先查本地授权列表,再查授权缓存,最后走在线 Authorize。离线时按 LocalAuthorizeOffline 和 AllowOfflineTxForUnknownId 两个配置键决定放不放行。授权缓存里存的 idTagInfo 带 expiryDate,过期就得重新在线问。很多现场问题出在缓存永不过期上——后台已经把某张卡拉黑,桩这边还在拿旧缓存放行,事后对账才发现。

3.4 StartTransaction:meterStart、reservationId 与 transactionId 的落点

车辆开始取电时,充电点发 StartTransaction.req,核心字段是 connectorId、idTag、meterStart、timestamp,如果这笔交易来自预约,还要带 reservationId。meterStart 是起点的电表读数,单位 Wh,这个值决定了整笔交易的计量基准,填错就是账单错误,没有后悔药。

中央系统应答里的 transactionId 是整笔交易的主键,后续所有 MeterValues 和最终的 StopTransaction 都要带上它。这里最容易出问题的场景是重发:充电点发了 StartTransaction 但没收到应答,重发之后中央系统生成了两个 transactionId,桩只认了后一个,前一个就成了孤儿交易挂在后台。规范给的解法是中央系统对同一 connectorId 上的重复 StartTransaction 做幂等处理,返回同一个 transactionId。做后台的如果没实现这一层,对接方越多,脏数据越多。

3.5 MeterValues:sampledValue 的采样配置与 transactionId 绑定

MeterValues 是整条链路里字段最多、最容易被写残的一条。它既可以带 transactionId(交易中采样),也可以不带(空闲时的电表读数)。payload 里是一组 meterValue,每个 meterValue 带 timestamp 和一组 sampledValue。

[ 2, "41b7d2aa-6c33-4f19-9a80-2e5f7c9d1b60", "MeterValues", { "connectorId": 1, "transactionId": 8801, "meterValue": [ { "timestamp": "2024-05-01T20:15:00Z", "sampledValue": [ { "value": "12345.6", "context": "Sample.Periodic", "format": "Raw", "measurand": "Energy.Active.Import.Register", "unit": "Wh" }, { "value": "7.2", "context": "Sample.Periodic", "measurand": "Power.Active.Import", "unit": "kW", "phase": "L1" } ] } ] } ]

逐项说明:connectorId必须对应真实连接器;transactionId在交易中必填,且必须等于 StartTransaction.conf 返回的那个值;timestamp是 UTC 带 Z;每个 sampledValue 里value是字符串形式的数值,measurand缺省时按Energy.Active.Import.Register处理,unit缺省是 Wh,context缺省是Sample.Periodic。context的取值还有 Transaction.Begin、Transaction.End、Sample.Clock、Interruption.Begin、Interruption.End、Trigger、Other,分别对应起止点、整刻对齐采样、中断前后和手动触发。

采样策略由三个配置键联合决定:MeterValueSampleInterval 控制周期,MeterValuesSampledData 控制采哪些量,MeterValuesAlignedData 控制哪些量要卡在整点整刻上报。三者叠加,才是一台桩真实的上报行为。只改一个,往往看不到预期效果。

3.6 StopTransaction:reason 枚举与 meterStop 的账目意义

交易结束时发 StopTransaction.req,字段是 transactionId、meterStop、timestamp、reason,idTag 可选。reason 的枚举值不少,每个都对应一种真实场景:Local、Remote、EmergencyStop、EVDisconnected、HardReset、SoftReset、PowerLoss、Reboot、DeAuthorized、UnlockCommand、Other。

reason 填错不会导致协议报错,但会让后台的统计口径全歪。比如车端拔枪应该是 EVDisconnected,桩端远程停止是 Remote,急停按钮是 EmergencyStop,把三者统一写成 Other,运营报表里就分不清「用户正常结束」和「设备异常中断」。Central 侧的 StopTransaction.conf 可以带 idTagInfo,用于告诉充电点这张卡在结束时是否仍然有效,这个字段常被忽略,但它是处理欠费卡的最后一次机会。

4. 中央系统发起侧:远程控制、配置管理与智能充电下发

4.1 远程启停与预约:RemoteStartTransaction 到 ReserveNow 的时序

RemoteStartTransaction.req 带 idTag 和可选的 connectorId,如果 connectorId 省略,由充电点自己挑一把可用的枪。应答 status 只有 Accepted 和 Rejected 两种,但 Accepted 只代表「收到并接受指令」,不代表「已经开始充电」——真正的开始信号是充电点后续发出的 StartTransaction.req。把这两件事混为一谈,是后台状态机写错的高发区。

RemoteStopTransaction.req 带的是 transactionId 而不是 connectorId,这个设计是刻意的:交易才是主体,枪只是载体。应答同样是 Accepted / Rejected。

ReserveNow.req 的字段更多:connectorId、expiryDate、idTag、reservationId、parentIdTag。reservationId 由中央系统分配,在充电点范围内唯一,取消预约时用 CancelReservation.req 按这个 id 撤销。expiryDate 到期后,充电点应该把连接器从 Reserved 状态释放回 Available 并上报 StatusNotification。规范允许充电点提前拒绝一个预约(比如该枪正在使用),返回 Occupied。

4.2 配置与诊断:GetConfiguration / ChangeConfiguration / GetDiagnostics / DataTransfer

GetConfiguration.req 可以带一组 key,也可以不带(返回全部已知键)。应答里除了 configurationKey 数组,还有一个 unknownKey 数组,列出对方不认识的那些键。这个字段很有用:对接新设备时先读一遍,能立刻看出它到底实现了多少标准配置。

ChangeConfiguration.req 的应答有四种:Accepted、Rejected、RebootRequired、NotSupported。RebootRequired 是最容易被忽略的一个——它明确告诉你「值我收下了,但要重启才生效」。后台如果只看「不是 Rejected 就当成功」,就会出现配置写进去了、行为没变、查半天代码的场面。

GetDiagnostics.req 给一个上传地址,充电点把诊断包传上去后发 DiagnosticsStatusNotification,状态是 Idle、Uploading、Uploaded、UploadFailed。这条链路依赖桩侧有稳定的上传能力,现场网络差的时候 UploadFailed 是常态,后台要有重试策略。

DataTransfer.req 是厂商私有数据的唯一正规通道,带 vendorId、messageId 和 data,双方在 vendorId 上达成一致后才能用。不要拿它传标准消息能表达的内容,否则对接第三方后台时一定翻车。

4.3 固件升级与复位:UpdateFirmware / FirmwareStatusNotification / Reset

UpdateFirmware.req 的字段是 location、retrieveDate、retries、retryInterval。retrieveDate 是「什么时候来下载」,不是「什么时候装」,这个时间点放在未来是为了错峰。充电点下载过程中按状态发 FirmwareStatusNotification:Downloading、Downloaded、DownloadFailed、Installing、Installed、InstallationFailed、Idle。后台靠这条通知推升级进度,不能靠猜。

Reset.req 的 type 只有 Hard 和 Soft 两种。Soft 是优雅重启,要等当前交易结束;Hard 是立即重启,可能中断正在进行的充电。规范建议 Hard 只在必要时用,因为中断的交易需要额外补偿逻辑。ChangeAvailability.req 的 type 是 Inoperative / Operative,用来把某个连接器或整桩置为不可用,跟 Reset 完全不是一回事,别混用。

UnlockConnector.req 用于远程解锁电子锁,应答 Unlocked、UnlockFailed、NotSupported 三种。很多桩根本没装电子锁,返回 NotSupported 是正常的,不是故障。

4.4 本地授权列表:SendLocalList 的 updateType 与版本号

SendLocalList.req 的核心是 listVersion、updateType 和 localAuthorizationList。updateType 为 Full 时整表替换,为 Differential 时增量合并(列表中 status 为 Accepted 的条目新增或更新,其余删除)。listVersion 必须单调递增,充电点用 GetLocalListVersion 查当前版本,版本落后才接受新列表。

这里有个隐性约束:如果本地列表版本比下发的还新,充电点应该拒绝这次下发。这条规则的存在是为了防止并发下发导致旧数据覆盖新数据。做后台批量下发时,务必串行化,别开多个线程同时推。

4.5 智能充电的三层结构:ChargingProfile → ChargingSchedule → ChargingSchedulePeriod

智能充电是 OCPP 1.6J 里抽象层级最高的一块,也是最容易看晕的一块。它的模型是三层的:ChargingProfile 是外壳,ChargingSchedule 是时间表,ChargingSchedulePeriod 是分段限值。

ChargingProfile 的关键字段有四个:chargingProfileId(唯一标识)、stackLevel(优先级,数字越大优先级越高)、chargingProfilePurpose(ChargePointMaxProfile / TxDefaultProfile / TxProfile)、chargingProfileKind(Absolute / Recurring / Relative)。purpose 决定了这份配置作用在哪个层级:ChargePointMaxProfile 管整桩总功率,TxDefaultProfile 是默认给每笔交易用的,TxProfile 只作用于某一笔指定交易。

[ 2, "9c1d4e77-2a0b-4f6e-b3aa-71d0c5e8f210", "SetChargingProfile", { "connectorId": 1, "csChargingProfiles": { "chargingProfileId": 101, "transactionId": 8801, "stackLevel": 3, "chargingProfilePurpose": "TxProfile", "chargingProfileKind": "Absolute", "validFrom": "2024-05-01T00:00:00Z", "validTo": "2024-05-02T00:00:00Z", "chargingSchedule": { "startSchedule": "2024-05-01T20:00:00Z", "duration": 7200, "chargingRateUnit": "W", "minChargingRate": 1500, "chargingSchedulePeriod": [ { "startPeriod": 0, "limit": 7000, "numberPhases": 1 }, { "startPeriod": 3600, "limit": 3500, "numberPhases": 1 } ] } } } ]

这段的意思是:给连接器 1 上的交易 8801 下发一份 TxProfile,优先级 3,绝对时间从 20:00 开始,总时长 7200 秒。第一个小时限 7000 W,第二个小时降到 3500 W,最低充电功率 1500 W。startPeriod是相对 startSchedule 的秒偏移,limit的单位由chargingRateUnit决定,可以是 W 也可以是 A;用 A 的时候numberPhases才有意义。duration省略表示一直有效到 validTo。

提示:SetChargingProfile 的应答只有 Accepted、Rejected、NotSupported 三种。Accepted 不代表桩真的按这个功率在充,验证方法是紧接着发 GetCompositeSchedule 看实际生效的调度。

4.6 复合调度:GetCompositeSchedule 与 stackLevel 的优先级规则

GetCompositeSchedule.req 带 connectorId、duration 和可选的 chargingRateUnit,应答返回一段时间内实际生效的复合调度曲线。它的价值在于「求交集」:ChargePointMaxProfile 给整桩一个上限,TxDefaultProfile 给每笔交易一个默认值,TxProfile 给当前交易一个特定值,三者按 stackLevel 取高优先级,再和物理能力取小,最终才是真正下发给车辆的目标功率。

优先级规则要记牢:同一 purpose 下 stackLevel 高的覆盖低的;purpose 之间 TxProfile 优先于 TxDefaultProfile,两者都受 ChargePointMaxProfile 的全局约束。chargingProfileKind 为 Recurring 时还要看 recurrencyKind 是 Daily 还是 Weekly,配合 validFrom / validTo 计算周期。这部分光看文档容易绕晕,最有效的办法是构造三份不同 purpose 的 profile 下发,然后用 GetCompositeSchedule 把结果打出来对照,两三次就通了。

5. 易错点盘点:五个最容易在联调现场翻车的地方

5.1 时间戳:本地时间直接塞进 timestamp

易漏点:MeterValues、StartTransaction、StopTransaction、StatusNotification 里的 timestamp 全部要求 UTC 带 Z 后缀,很多实现直接取设备本地时间格式化后填进去。

为什么漏:设备本地时间看起来「也是时间」,单机测试时后台和桩在同一时区,看不出问题;一旦跨时区部署,账单时间整体偏移几个小时,而且偏移量随夏令时变化,非常隐蔽。

怎么补:BootNotification 应答里的 currentTime 是唯一可信源,收到后用它对本地时钟做偏移校正,之后所有时间戳统一用 UTC 生成再序列化。自测时把设备时区故意改成 UTC+8 和 UTC-5 各跑一遍,对比后台入库时间,两次一致才算过。

5.2 StopTransaction 的 idTag 缺省:离线交易收尾时填了空

易漏点:StopTransaction.req 的 idTag 是可选字段,离线起充的交易在结束时往往拿不到有效 idTag,实现里就干脆不填。

为什么漏:字段可选,协议不报错,联调时一切正常。但后台对账时需要靠 idTag 关联用户,缺了就只能靠 transactionId 反查,一旦 StartTransaction 那侧也没记全,这笔交易就成了无主账。

怎么补:约定一条规则——只要 StartTransaction 时带了 idTag,StopTransaction 就必须带同一个值;离线场景下即使授权已失效也要原样回填。后台侧同时保留 transactionId 到 idTag 的映射表,做双保险。

5.3 ChangeConfiguration 返回 Accepted ≠ 已生效

易漏点:把 Accepted 当成「配置已生效」,忽略 RebootRequired 这个返回值。

为什么漏:四种返回值里三个看起来都像成功,代码里往往写成if status != "Rejected"就继续往下走。

怎么补:把 RebootRequired 单独分支处理,记录下来并在合适的时机触发 Soft Reset。改完之后用 GetConfiguration 回读一次做确认,别信单次写入的返回值。批量改配置时更是如此,几百台桩里总有几台返回的是 RebootRequired。

5.4 预约的生命周期:expiryDate 过期后忘了释放连接器

易漏点:ReserveNow 下发后,连接器进入 Reserved 状态,expiryDate 到期却没人把它改回 Available。

为什么漏:预约的成功路径太顺了——下发、接受、用户来了开始充电,一切正常。只有用户没来这一条分支容易被漏掉,而这恰恰是最高频的场景。

怎么补:充电点侧必须有一个到期定时器,到点自动释放并发 StatusNotification。后台侧同时做一层兜底,定期扫描过期预约并主动发 CancelReservation。两边的 reservationId 要对得上,否则取消会失败。

5.5 智能充电的 stackLevel 与 purpose 组合:优先级用反了

易漏点:以为 stackLevel 数字越小优先级越高,或者以为 TxDefaultProfile 能覆盖 TxProfile。

为什么漏:很多系统里「层级」是越小越优先,直觉迁移过来就错了。OCPP 1.6J 明确规定 stackLevel 数值越大优先级越高,且 TxProfile 的优先级高于 TxDefaultProfile。

怎么补:把规则写成一句话贴在代码注释里——「同 purpose 比 stackLevel,大者胜;跨 purpose 时 TxProfile > TxDefaultProfile,两者都被 ChargePointMaxProfile 压着」。验证手段只有一个:下发三份 profile,用 GetCompositeSchedule 打曲线,肉眼比对拐点位置。

6. 复习清单:按报文顺序走一遍的自测表

这套协议光看目录记不住,最有效的复习方式是照着一条完整链路把报文走一遍,每一步都问自己「这条消息的必填字段是什么、应答有哪些状态、失败了看哪里」。下面这张表可以直接当自测清单用,能全部答上来,基本就算过关了。

环节自测问题答上来的标准
连接建立WebSocket 建连后第一条业务消息是什么BootNotification,且能说出三个 status 的含义
心跳interval 以谁给的为准以 BootNotification.conf 的 interval 为准
状态SuspendedEVSE 和 SuspendedEV 的区别桩侧暂停 vs 车侧暂停
授权ConcurrentTx 在什么场景出现同一 idTag 已有进行中交易
起充StartTransaction.conf 里哪个字段是主键transactionId
计量MeterValues 的 context 有哪些取值至少说出 Sample.Periodic、Transaction.Begin、Transaction.End、Sample.Clock
停充reason 至少说出五种Remote、Local、EVDisconnected、EmergencyStop、PowerLoss
远程RemoteStart 返回 Accepted 代表什么只代表指令被接受,不代表已开始充电
配置ChangeConfiguration 四种返回值Accepted、Rejected、RebootRequired、NotSupported
智能充电三个 purpose 的优先级关系TxProfile > TxDefaultProfile,均受 ChargePointMaxProfile 约束

走完这张表,还有一个动作值得养成习惯:随便挑一条消息,打开规范原文,只看它的「必填字段」和「枚举取值」两段,不从头读到尾。OCPP 1.6J 的目录结构就是按功能配置文件和 req/conf 对齐的,找一条消息的约束比翻任何二手资料都快。

我自己的血泪经验是:每接一台新桩,先不急着测业务,把 Boot → Heartbeat → StatusNotification(Available) → Authorize → StartTransaction → MeterValues → StopTransaction → StatusNotification(Finishing → Available) 这条最小链路抓一遍报文存下来,后面任何异常都拿这份基线去 diff。从那以后我每次对接新设备都强制走一遍这个流程,比事后翻日志省太多时间。这份 OCPP 1.6J 规范原文加消息字段对照,我平时就放在手边当字典查,需要的时候翻出来对着报文核字段。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询