明说·协议课 第 16 讲|OCPP 2.1 怎样控制 V2X 与 DER
本讲的规范语义依据是OCPP 2.1 Edition 2 Part 2 — Specification与OCPP 2.1 Edition 2 Errata 2026-06,重点核对 Q03~Q12、R01~R05 以及 K 功能块中的 Charging Profile 求值和离线规则;传输行为对照 Edition 2 Part 4,认证与测试边界对照 Edition 2 Part 5、Part 6。报文结构使用资料集中的 Part 3 JSON Schema 校验,但该 Schema 包仍标注 Edition 1;凡结构与 Edition 2 或 Errata 的语义存在差异,以 Part 2 与 Errata 为准。车—桩边界回指ISO 15118-20:2022。协议事实核对时间为 2026 年 9 月。第 15 讲结束时,车—桩已经交出一份持续变化的EnergyTransferEnvelope。它告诉充电站,这辆车选中了哪种 BPT 服务,额定边界在哪里,当前还能充多少、放多少,出行需求又给控制留下了多大空间。走到这一步,上层终于可以提出一个负向功率目标,但新的问题也从这里开始:这个目标由谁计算,设备为什么有时不按它执行,平台失联以后它还能活多久?我们沿用前两讲的联调推演。20:58,CSMS 为 EVSE 1 安装一份CentralSetpoint=-20 kW的 V2X Profile,Charging Station 返回Accepted。21:03,并网点附近的电压进入 Volt-Watt 曲线的调节区间,设备降低反向功率并发送NotifyDERAlarmRequest。21:05,Charging Station 与 CSMS 的连接中断;设备在maxOfflineDuration内继续求值原 Profile,但 DER 曲线与车辆实时包络仍然有效。21:07,离线宽限到期,设备切换到较低stackLevel的ChargingOnlyProfile。此时,CSMS 页面如果仍把 20:58 的回执当成当前状态,就会继续显示“正在按 -20 kW 执行”。这不是某个客户现场的事故记录,而是一组用于审查协议状态的时间线。它把一个容易被忽略的事实摆到了台前:网络上最后一条成功指令,可以和设备此刻的控制结果完全不同。指令没有被篡改,设备也未必故障;只是目标来源、Profile 有效性、本地 DER 规则和离线状态已经共同改变了实效结果。OCPP 2.1 的 V2X 控制,不能只保存“平台最后下发了多少”。我们还要保存谁产生目标、谁根据本地输入求值、哪些约束正在覆盖原计划,以及失联后这份计划何时失效。四件事合在一起,才是一份可求值、可失效、可恢复的控制合同。一、-20 kW被接受以后,控制链才刚刚开始在单向 Smart Charging 里,我们已经见过Accepted的边界:设备能够处理 Profile,不等于车辆一定会按目标取电。进入 V2X,这个边界更容易被放大,因为功率有了两个方向,目标来源也不再只有 CSMS。设备既可能执行一条预先排好的 Charging Schedule,也可能持续接收动态设定值;目标还可能来自 OCPP 之外的 External System,或者直接由 Charging Station 根据频率和上游电表计算。开篇那条时间线里,至少有六个不同事实:时点协议或现场事实当时最多能确认什么不能顺手推出什么20:58:00SetChargingProfileRequest携带CentralSetpoint=-20000 WCSMS 表达了一个负向目标车辆已经放电20:58:00.1SetChargingProfileResponse.status=AcceptedCharging Station 已成功处理该消息该 Profile 当前一定取得实效20:58:04transactionInfo.operationMode=CentralSetpoint,设备侧出现反向功率当前交易进入相应模式,EVSE 侧开始观察到执行PCC 一定向公共电网外送21:03:00NotifyDERAlarmRequest(controlType=VoltWatt)Volt-Watt 控制正在改变原有运行结果Charging Station 发生故障21:05:00Charging Station 检测到离线设备开始按本地时刻计算离线时限CSMS 知道设备精确切换秒数21:07:00较低栈级 Profile 成为实效项原 V2X Profile 已按离线规则让位原 Profile 已从设备存储中删除OCPP 2.1 的SetChargingProfileResponse对Accepted的说明很克制:它表示 Charging Station 能够成功处理这条消息,但不保证计划会被逐字逐项执行,因为设备还可能需要考虑其他约束。对平台来说,这句话不能只留在接口文档里。命令状态至少要分成“请求得到响应”“Profile 已安装或可报告”“当前求值选中”“实效目标改变”“物理功率被观察到”。第 14 讲已经把能力、授权、协商、激活和测量分开;本讲不再重走那条链。我们从Activated往下追,专门回答一份已进入 Charging Station 的 V2X Profile 怎样取得、失去和恢复控制资格。二、operationMode声明的不是业务名称,而是控制来源OCPP 2.1 在ChargingSchedulePeriodType中增加operationMode。这个字段最值得我们关注的地方,不是它多了几个枚举,而是它把每个时段的控制责任写进了 Profile:这个时段是普通充电,还是要跟踪 CSMS 的设定值;目标来自外部系统,还是由 Charging Station 使用本地频率或电表求出。operationMode一共有八个枚举值。字段缺省时按ChargingOnly处理,但工程上不宜因为有默认值就长期省略它;显式记录能减少日志解释和版本兼容中的歧义。operationMode目标或边界从哪里来主要在哪一侧求值这个模式最容易写错的地方ChargingOnlyCSMS 的普通 Profile 或设备默认行为Charging Station它不是反向功率模式,不能带放电限额和 V2X 目标CentralSetpointCSMS 的计划或动态更新Charging Station 按时段执行setpoint必须存在,负值表示放电,但仍受上下限和其他约束ExternalSetpointOCPP 之外的 External SystemCharging Station 接收并投影外部目标外部目标仍不得越过 Profile 合并后的边界ExternalLimitsOCPP 之外的 External SystemCharging Station 在外部边界内运行limit/dischargeLimit是边界,不是必须跟踪的目标CentralFrequencyCSMS 根据频率信号计算CSMS它是中心侧频率支持,不是 R 功能块的 DER 下垂曲线LocalFrequency本地频率、基线与频率—功率曲线Charging Station本地求值时不依赖 CSMS 持续发送直接setpointLocalLoadBalancing上游电表和阈值Charging Station电表边界、方向和新鲜度错一个,计算方向就可能反掉IdleCSMS 要求本时段不充不放Charging Station 与 EV不能把它简化成setpoint=0Idle值得单独说一下。Q10 要求该时段不出现limit、dischargeLimit、setpoint和setpointReactive及其 L2/L3 变体;若同时提出preconditioningRequest=true,车辆可以为了电池预处理而消耗能量。设备支持SmartChargingCtrlr.SupportsEvseSleep时,evseSleep=true还可以关闭与本次交易相关的功率电子模块,并在TransactionEventRequest.evseSleep中报告睡眠状态。可见,Idle描述的是运行意图和设备状态,不只是一个数值为零的功率点。模式表解决了“谁来算”的问题,却没有说明目标怎样送达。对CentralSetpoint来说,OCPP 2.1 又提供了计划式与动态式两种交付方法,二者共享 Profile 求值规则,但时间语义不同。三、Dynamic Profile 改变的是更新方式,不会取消 Profile 求值Q03 允许 CSMS 在TxProfile或TxDefaultProfile的一个或多个时段里使用CentralSetpoint。适用时段必须携带setpoint,还可以给出非负的limit和非正的dischargeLimit,把目标限制在允许的充放电区间内。负setpoint表示 EV 向 EVSE 侧放电;它描述的是这份 Charging Schedule 中的方向,不能直接替代 EVSE 出口或 PCC 测量。如果目标提前可知,我们可以用普通Absolute、Recurring或RelativeProfile 表达多个时段。若目标要持续刷新,Q04 使用chargingProfileKind=Dynamic:一份 Dynamic Profile 只允许一个 Charging Schedule,Schedule 里也只有一个 Period。CSMS 不需要反复重装整份 Profile,只更新这个时段里的设定值或限额。下面是一份裁剪后的 OCPP-J 示例。时间采用 UTC,数值只用于讲清消息结构和离线语义;MessageId、交易 ID 与chargingProfile.id分开保存。[ 2, "761b0f1352c74acc95d1e7269192bf81", "SetChargingProfile", { "evseId": 1, "chargingProfile": { "id": 16021, "stackLevel": 3, "chargingProfilePurpose": "TxProfile", "chargingProfileKind": "Dynamic", "transactionId": "tx-20260903-74128", "validFrom": "2026-09-03T12:58:00Z", "validTo": "2026-09-03T14:00:00Z", "maxOfflineDuration": 120, "invalidAfterOfflineDuration": true, "dynUpdate