返回博客
教程10 分钟阅读

OCPP 智能充电:SetChargingProfile 实战

充电配置是 OCPP 1.6 里被误解最深的一块。堆叠层级、用途、计划时段和复合计划——每一个到底在做什么,附带报文。

I
Infinite Service Team

智能充电(Smart Charging)功能档是 OCPP 1.6 不再是一个简单请求/响应协议的地方。SetChargingProfile 有嵌套结构、三种不同用途、一套堆叠模型,以及一组很容易在细处弄错的配置合并规则。

它也是大多数实现可以一直不管、蒙混过去的那部分规范——直到某个电网运营商要求他们削减负荷,这件事才突然变得紧急。

一份充电配置长什么样

一份充电配置回答的是一个问题:*这台充电桩被允许输出多大电流或功率,以及在什么时候?* 结构里的每一样东西都在为这件事服务。

[2, "msg-01", "SetChargingProfile", {
  "connectorId": 1,
  "csChargingProfiles": {
    "chargingProfileId": 100,
    "stackLevel": 0,
    "chargingProfilePurpose": "TxDefaultProfile",
    "chargingProfileKind": "Absolute",
    "chargingSchedule": {
      "chargingRateUnit": "A",
      "chargingSchedulePeriod": [
        { "startPeriod": 0,     "limit": 32.0 },
        { "startPeriod": 21600, "limit": 16.0 },
        { "startPeriod": 64800, "limit": 32.0 }
      ]
    }
  }
}]

这份配置说的是:一开始 32 A,六小时后降到 16 A,十八小时后回到 32 A。startPeriod 是相对于计划开始时刻的秒数,既不是钟表时间,也不是时长。

三种用途互不可换

chargingProfilePurpose 决定这份配置作用在什么东西上,大部分混乱都从这里开始。

ChargePointMaxProfile 是整台充电桩的物理上限。它设在 connectorId: 0 上,因为它描述的是站点而不是某个插口。任何东西都不能超过它——那是硬件以及它背后供电能力的极限。

TxDefaultProfile 是交易的默认配置。可以设在某个具体接口上,也可以设在 connectorId: 0 上,让所有接口共用同一个默认值。它作用于那些自身没有配置的交易,包括将来的交易。

TxProfile 作用于一笔具体的、正在进行的交易。它需要 transactionId,并且在那笔交易结束时被丢弃。要削减一次正在进行的会话,用的就是它。

实际后果是:给一笔已经停止的交易发 TxProfile,不会产生你会注意到的错误——充电桩收下它,然后扔掉。

堆叠不是相加

stackLevel 是一个整数,数值大的胜出。这是人们最常读错的一条规则。

处于不同堆叠层级的配置不会相加、取平均或混合。在同一种用途之内,层级最高的那个有效配置会完整替换它下面的所有配置。stackLevel 2 的配置不是在 stackLevel 1 之上再加一道约束,而是取代它。

跨用途之间的叠加是另一回事,那一层确实表现为上限:实际生效的限值,是适用的 ChargePointMaxProfile、胜出的 TxDefaultProfile 和胜出的 TxProfile 三者中最低的那个。所以用途之间互相设上限,而同一用途内的堆叠层级之间互相覆盖。

一个常见的部署套路:

  • stackLevel 0 —— TxDefaultProfile,日常运行限值
  • stackLevel 1 —— TxDefaultProfile,由电价驱动的夜间计划
  • stackLevel 2 —— TxProfile,运营方针对某一次会话的削减

把层级 2 的配置移除,层级 1 的计划就恢复。没有任何东西需要把它替换掉的内容再重述一遍。

Absolute、Recurring、Relative

chargingProfileKind 决定 startPeriod: 0 是什么意思。

Absolute 把计划锚定在 startSchedule 这个显式时间戳上。用于一次性的窗口:今晚 18:00 到 20:00 之间削减。

Recurring 按天或按周重复,由 recurrencyKind 控制。startSchedule 设定相位——也就是周期从每天的哪个时刻开始。用于电价场景。

Relative 把 startPeriod: 0 锚定在交易开始的那一刻,并且完全忽略 startSchedule。用于取决于会话已进行多久、而不是取决于一天中时刻的行为:第一个小时满电流,之后降下来。

一个常犯的错误是发 Absolute 却不带 startSchedule。规范要求 Absolute 和 Recurring 都必须带它,而各家充电桩的做法不一:有的拒掉这份配置,有的悄悄把它当成 Relative。

安培还是瓦特,以及为什么这要紧

chargingRateUnit 取 A 或 W,而充电桩并不被要求同时支持两者。对 ChargingScheduleAllowedChargingRateUnit 做一次 GetConfiguration,就能知道哪些可用。

交流充电下,换算取决于相数,而配置本身并不携带相数。单相 230 V 上的 32 A 大约是 7.4 kW;同样的 32 A 在三相上大约是 22 kW。如果你的后端按瓦特思考而充电桩按安培工作,那么关于相数的假设就藏在你代码的某个地方,而它必须是显式的。

计划时段里的 numberPhases 在省略时默认为 3。在单相安装上这个默认值是错的,由它算出来的限值会高出三倍。

核实充电桩实际做了什么

SetChargingProfile 会回 Accepted、Rejected 或 NotSupported。Accepted 的意思是充电桩把这份配置存下来了——不代表它当前生效,也不代表它产生了你想要的限值。

要看所有配置合并之后的结果,用 GetCompositeSchedule:

[2, "msg-02", "GetCompositeSchedule", {
  "connectorId": 1,
  "duration": 86400,
  "chargingRateUnit": "A"
}]

充电桩会返回它实际会遵循的那份扁平化计划,所有配置都已经解算完毕。这是检验你堆叠逻辑唯一可靠的办法,也是当一次削减看起来没生效时第一个该伸手去拿的工具。

如果复合计划和你预期的不一样,常见原因有三个:堆叠层级低于某个已经装上去的配置;TxProfile 的 transactionId 和正在进行的交易对不上;或者有一个 ChargePointMaxProfile 在上面压着你。

清除配置

ClearChargingProfile 是按过滤条件删除,不是只按 ID 删除。每个字段都是可选的,而全部省略就会清掉所有东西:

[2, "msg-03", "ClearChargingProfile", {
  "connectorId": 1,
  "chargingProfilePurpose": "TxProfile"
}]

这会清掉 1 号接口上的 TxProfile,而默认配置保持不动。发一个 {} 会清掉这台充电桩上的每一份配置——偶尔这正是你想要的,更多时候它是一次故障。

怎么测

智能充电很难拿真实硬件来测,因为有意思的情况都跟时间有关——一个在跨天边界上出问题的周期性配置、一次在会话中途到达的削减、一份只有在三个配置叠起来之后才算错的复合计划。

这正是值得放到仿真里去跑的那类逻辑。SimPilot 可以把一个会话一直挂着,让你对着它安装、堆叠和清除配置;而 TestPilot 则按规范检查一台充电桩自身对智能充电功能档的处理。完整的消息集记录在我们的 OCPP 1.6 参考文档里。

相关文章