在充电平台中,OCPP 1.6 解决的是充电设备与中心系统之间的通信问题,但“收到一个电量值”并不等于“能够证明该电量值没有被修改”。当计量数据需要用于账单核验、争议处理或审计时,还需要一条从计量设备到验证端的完整证据链。
OCMF(Open Charge Metering Format)提供了一种可读的签名计量数据格式。它把表计读数、时间、用户标识、设备身份和交易阶段组织成 JSON 数据,再由计量侧对原始数据进行数字签名。OCPP 1.6 则可以利用 SignedData 格式承载这段数据。
本文以 OCMF 1.0.1 Draft 为参考,说明如何在现有 OCPP 1.6 平台中完成 OCMF 数据的接入、存储、校验和业务关联。文中的设计重点是工程实现,不替代具体地区的计量法规、型式批准或合规认证要求。
一、先明确两个协议的职责
OCPP 1.6 和 OCMF 处在不同层次:
- OCPP 1.6 负责设备与平台之间的消息交互,包括启动交易、上传采样值和结束交易;
- OCMF 负责描述可验证的计量记录,并通过数字签名证明记录在签名后未被修改;
- TLS 保护数据传输过程,OCMF 签名保护计量数据本身,两者不能相互替代。
平台接入时应保留两条数据链。普通的 Raw 数值用于实时功率曲线、告警和运营统计;SignedData 用于账单证据、离线验签和争议复核。不要因为已接入签名数据,就删除原有的普通采样值。
二、OCMF 数据结构
一条 OCMF 记录由三个部分组成,使用竖线分隔:
OCMF|<待签名数据 JSON>|<签名信息 JSON>
下面是一条经过简化的示例。签名值仅用于展示结构,不代表真实计算结果。
OCMF|{"FV":"1.0","GI":"MeterGateway","GS":"GW00001","PG":"T1001","MS":"METER00001","IS":true,"IL":"TRUSTED","IF":["OCPP_AUTH_TLS"],"IT":"ISO14443","ID":"04A1B2C3D4E5F6","CT":"CBIDC","CI":"CP001 1","RD":[{"TM":"2026-09-16T10:30:00,000+0800 S","TX":"B","RV":1256.32,"RI":"1-0:1.8.0","RU":"kWh","RT":"AC","EF":"","ST":"G"}]}|{"SA":"ECDSA-secp256r1-SHA256","SE":"hex","SM":"application/x-der","SD":"3045..."}
常用字段可以分为六组:
| 分组 | 字段 | 含义 |
|---|---|---|
| 格式与设备 | FV、GI、GS、GV |
格式版本、签名网关标识、序列号和软件版本 |
| 分页 | PG |
记录序号,T 表示交易上下文,F 表示非交易计量上下文 |
| 表计 | MV、MM、MS、MF |
表计厂商、型号、序列号和固件版本 |
| 用户关联 | IS、IL、IF、IT、ID |
用户是否关联、可信级别、关联方式、标识类型和标识值 |
| 充电点 | CT、CI |
充电点标识类型和标识值 |
| 读数 | RD |
一个或多个带时间、交易阶段、数值、单位和状态的读数 |
RD 数组中的关键字段包括:
TM:ISO 8601 风格的时间和时间状态,状态S表示已同步,U表示未知或未同步;TX:交易阶段,B为开始,C为充电中,E为结束,T为费率切换,X为发生异常;RV:读数值;RI:OBIS 标识,例如有功输入电能;RU:单位,通常为Wh或kWh;EF:不可用于结算的量,例如E表示能量异常,t表示时间异常;ST:表计状态,G表示正常,其他值可表示超时、断开、被替换或读数错误等状态。
OCMF 允许同一签名记录包含多个读数,也允许后续读数省略与前一读数相同的部分字段。因此,平台展示数据时可以做字段继承,但验签时必须使用设备上传的原始文本。
三、OCPP 1.6 的承载方式
OCPP 1.6 的 MeterValues 和 StopTransaction 都可以携带 MeterValue,其中 SampledValue.format 支持 Raw 和 SignedData。接入 OCMF 时,推荐在同一采样时刻同时上传普通值和签名值。
1. 交易开始
StartTransaction.req 只有整数类型的 meterStart,不能直接承载 OCMF 字符串。设备应先完成开始读数并保留 OCMF 记录,在收到平台返回的 transactionId 后,立即通过 MeterValues.req 补传交易开始记录。
[
2,
"msg-1002",
"MeterValues",
{
"connectorId": 1,
"transactionId": 78231,
"meterValue": [
{
"timestamp": "2026-09-16T02:30:00.000Z",
"sampledValue": [
{
"value": "1256320",
"context": "Transaction.Begin",
"format": "Raw",
"measurand": "Energy.Active.Import.Register",
"unit": "Wh"
},
{
"value": "OCMF|{...}|{...}",
"context": "Transaction.Begin",
"format": "SignedData",
"measurand": "Energy.Active.Import.Register",
"unit": "kWh"
}
]
}
]
}
]
2. 交易过程
周期采样、费率切换或异常事件通过 MeterValues.req 上送。普通运营曲线可以保持较短周期;签名记录则根据计量要求在交易开始、结束、费率切换和必要的中间节点生成。
配置项可按业务要求设置。例如,15 分钟对齐采样可以使用:
ClockAlignedDataInterval = 900
MeterValuesAlignedData = Energy.Active.Import.Register
这只是常见配置,不应把 15 分钟固定写入平台逻辑。实际间隔需要根据计费规则、设备能力和当地要求确定。
3. 交易结束
结束记录优先放入 StopTransaction.req.transactionData,同时保留 meterStop 供原有业务逻辑使用。平台应允许签名数据与停止消息异步到达:设备断网重传时,消息顺序可能与理想流程不同。
[
2,
"msg-1088",
"StopTransaction",
{
"transactionId": 78231,
"timestamp": "2026-09-16T03:20:00.000Z",
"meterStop": 1278450,
"reason": "Local",
"transactionData": [
{
"timestamp": "2026-09-16T03:20:00.000Z",
"sampledValue": [
{
"value": "OCMF|{...}|{...}",
"context": "Transaction.End",
"format": "SignedData",
"measurand": "Energy.Active.Import.Register",
"unit": "kWh"
}
]
}
]
}
]
四、平台总体设计
平台可以拆分为五个处理环节:
充电设备
-> OCPP 接入层
-> 原始报文与 OCMF 证据存储
-> OCMF 解析及验签服务
-> 交易关联与完整性检查
-> 账单、审计和证据导出
OCPP 接入层
接入层完成 OCPP Schema 校验、设备身份认证、消息去重和应答。检测到 format=SignedData 后,只做长度限制和基础格式检查,然后把 value 原样交给证据存储。接入层不应对 OCMF 字符串执行格式化、转义还原后的二次拼接或 JSON 规范化。
原始证据存储
建议同时保留以下三类内容:
- 完整 OCPP 原始报文;
- 从
SampledValue.value提取的 OCMF 原始字符串; - 解析结果、验签结果和所用公钥版本。
原始证据应采用只追加或具备防篡改能力的存储策略。数据库中的解析字段用于查询,不能替代原文。
验签服务
验签服务负责格式识别、字段校验、公钥检索、密码学验证和结果归档。建议使用成熟密码学库,不自行实现椭圆曲线运算。
交易完整性服务
单条签名验证成功,只能说明该条记录没有被修改。平台还必须检查一组记录是否构成完整交易,包括开始和结束标记、分页连续性、设备身份一致性、时间状态以及异常标志。
五、数据表设计
下面给出一组精简的数据模型,实际字段可按现有平台调整。
CREATE TABLE signed_meter_record (
id BIGINT PRIMARY KEY,
charge_point_id VARCHAR(64) NOT NULL,
connector_id INT NOT NULL,
transaction_id BIGINT,
ocpp_message_id VARCHAR(64) NOT NULL,
received_at TIMESTAMP NOT NULL,
ocmf_raw TEXT NOT NULL,
ocmf_sha256 CHAR(64) NOT NULL,
format_version VARCHAR(16),
pagination VARCHAR(64),
gateway_serial VARCHAR(128),
meter_serial VARCHAR(128),
signature_algorithm VARCHAR(64),
verify_status VARCHAR(32) NOT NULL,
verify_reason VARCHAR(256),
public_key_id BIGINT,
UNIQUE (charge_point_id, ocmf_sha256)
);
CREATE TABLE meter_public_key (
id BIGINT PRIMARY KEY,
identity_type VARCHAR(32) NOT NULL,
gateway_serial VARCHAR(128),
meter_serial VARCHAR(128),
charge_point_ref VARCHAR(128),
algorithm VARCHAR(64) NOT NULL,
public_key_data TEXT NOT NULL,
valid_from TIMESTAMP NOT NULL,
valid_to TIMESTAMP,
status VARCHAR(16) NOT NULL
);
ocmf_sha256 主要用于幂等去重和证据定位,不等同于 OCMF 的签名验证结果。公钥表需要保留有效期和历史版本,否则设备换表或换密钥后将无法复核旧交易。
六、验签流程
第一步:保留原文并切分区段
确认字符串以 OCMF| 开头,并按协议边界分离待签名数据和签名信息。因为 OCMF 禁止在区段内部使用竖线,所以可以据此识别边界。应限制总长度、JSON 深度和数组大小,避免异常报文占用过多资源。
第二步:解析但不重建待签名内容
将待签名数据解析为对象,用于字段校验和业务查询;与此同时,记录它在原字符串中的精确字节范围。真正计算摘要时使用原始区段,不使用 JSON.stringify 或其他序列化结果。
下面的伪代码强调了这一点:
VerificationResult verify(String ocmfRaw) {
OcmfSections sections = splitWithoutReformatting(ocmfRaw);
OcmfPayload payload = jsonParser.parse(sections.payloadJson());
OcmfSignature signature = jsonParser.parse(sections.signatureJson());
validateRequiredFields(payload, signature);
PublicKey key = keyRepository.findEffectiveKey(
payload.gatewaySerial(),
payload.meterSerial(),
payload.chargePointIdentity(),
payload.readingTime()
);
byte[] signedBytes = sections.payloadJson()
.getBytes(StandardCharsets.UTF_8);
return cryptoProvider.verify(
signature.algorithmOrDefault(),
key,
signedBytes,
decodeSignature(signature)
);
}
具体签名输入范围应以设备实现所遵循的 OCMF 版本及互操作测试结果为准。平台上线前必须用设备厂商提供的正向和反向测试向量确认:少一个空格、改变字段顺序或改变数字表示后,验证应当失败。
第三步:选择算法并解码签名
OCMF 1.0.1 Draft 列出了多种 ECDSA 曲线和 SHA-256 组合。未提供 SA 时需要按对应版本的默认值处理;未提供 SE 时默认按十六进制解码;SM 的默认签名结构为 ASN.1 DER。
工程中常见的错误是把 DER 编码的 ECDSA 签名当成固定长度的 r || s。两者不是同一种编码,必须根据 SM 和设备约定正确解码。
第四步:定位公钥
公钥不应直接信任同一条待验证消息中的临时值。OCMF 草案要求公钥通过独立渠道与表计、签名网关或充电点建立关联。平台可以按以下组合查找:
- 表计内置签名模块:使用
MS; - 单枪网关:使用
GS或MS; - 一个网关管理多个充电点:使用
GS + MS; - 已建立充电点映射:使用
CT + CI。
导入公钥时需要校验来源、算法、启用时间和停用时间。任何人工替换都应留下审计记录。
第五步:验证交易完整性
密码学验签成功后,再执行交易级规则:
- 第一条有效记录包含
TX=B; - 最后一条有效记录包含
TX=E,或更具体的本地结束、远程结束、异常结束代码; PG在同一交易上下文中连续递增,不重复、不倒退;- 所有记录来自相同的签名源和表计;
- 读数时间不倒退,时间状态满足计费要求;
- 结束读数不小于开始读数,且差值与账单电量在允许精度内一致;
EF、ST或TX=X出现时,交易进入人工复核或异常处理流程;- OCMF 内部身份、OCPP 连接和平台交易三者的映射一致。
只有“单条验签通过”和“交易完整性通过”同时成立,平台才能把记录标记为可用于后续结算核验。
七、状态与异常处理
建议把验签结果设计成明确的状态机,而不是一个布尔值:
| 状态 | 含义 | 建议处理 |
|---|---|---|
PENDING |
等待解析或等待公钥 | 异步重试,不阻塞 OCPP 应答 |
VALID |
签名与字段校验通过 | 继续交易完整性检查 |
INVALID_SIGNATURE |
密码学验证失败 | 保留原文,禁止自动用于可信结算 |
KEY_NOT_FOUND |
找不到有效公钥 | 检查设备资产和密钥登记 |
FORMAT_ERROR |
结构或必填字段错误 | 记录设备、固件和错误位置 |
INCOMPLETE_CHAIN |
开始、结束或分页不完整 | 等待迟到消息,超时后转异常 |
METER_ERROR |
表计状态或错误标志异常 | 按业务规则复核或终止结算 |
OCPP 请求的接收结果和 OCMF 验签结果应分开处理。平台可以先正常响应 OCPP 消息,再异步验签;否则密码学计算、公钥服务抖动或批量补传可能拖慢设备通信线程。
八、幂等、补传与顺序问题
充电设备断网后可能重复发送同一条消息,也可能把结束记录先于某些中间记录送达。平台不应依赖数据库自增主键判断业务顺序。
推荐使用 chargePointId + SHA-256(ocmfRaw) 作为签名记录的幂等键,并保留 OCPP uniqueId 作为消息层追踪信息。交易内顺序以 PG 和读数时间共同判断。对于暂时缺少开始记录或公钥的交易,可以进入等待状态,在补传数据到达后重新执行完整性检查。
需要注意,PG 是签名源维护的连续计数,不一定等于 OCPP 的 transactionId。两者只能建立关联,不能互相替代。
九、安全与隐私设计
OCMF 可能包含 RFID UID、账户标识或其他用户关联信息。平台接入后应执行以下控制:
- 原始证据仅向必要的服务账号开放;
- 查询页面默认对
ID脱敏,避免在日志中输出完整标识; - 密钥、验签记录和人工处理记录均纳入审计;
- 设置原始报文大小、字段长度和解析深度上限;
- 对验签服务进行资源隔离,避免构造报文影响 OCPP 主链路;
- 按适用的数据保留要求设置归档与删除策略。
需要区分“脱敏展示”和“修改证据”。原始 OCMF 字符串不能被改写;脱敏只应发生在查询视图或导出副本中。
十、测试方案
上线前至少覆盖以下测试:
正常场景
- 交易开始、中间采样和结束记录全部验签成功;
- 同一
MeterValue同时包含Raw和SignedData; StopTransaction.transactionData正常入库;- 十六进制和 Base64 签名按声明正确解码;
- 设备换表或换密钥后,历史交易仍能使用旧公钥复核。
篡改场景
- 修改一个读数数字;
- 改变数字的小数位表示;
- 增删空格或调整 JSON 字段顺序;
- 替换表计序列号;
- 使用错误公钥或失效公钥。
以上情况均应导致验签失败或明确的密钥错误,且原始数据仍可追溯。
链路异常场景
- OCPP 消息重复发送;
- 中间分页缺失或重复;
- 结束消息先到、中间消息后到;
- 平台重启后继续处理待验签记录;
- 公钥服务暂时不可用;
- 时间未同步、表计断开、读数错误或交易异常结束。
十一、分阶段落地建议
第一阶段先完成透明接入:识别 SignedData、原样存储 OCMF、建立设备和表计映射,不改变现有结算逻辑。
第二阶段接入公钥管理和异步验签,形成可查询的验证结果,同时通过影子模式比较 OCMF 电量与现有 meterStart/meterStop 的差异。
第三阶段加入交易完整性规则、异常工作流和证据导出。经过足够的设备互操作测试后,再决定哪些业务环节可以依赖验证结果自动执行。
结语
在 OCPP 1.6 平台中接入 OCMF,难点不在于增加一个 SignedData 字段,而在于保护原始字节、可靠管理公钥,并从多条记录中还原完整交易。一个稳健的实现应同时保留普通计量值与签名证据,把“消息接收成功”“单条签名有效”和“交易证据完整”设计成三个独立判断。
只要平台从一开始就坚持原文不可改写、公钥来源可信、分页连续可检查、异常状态可追溯,后续扩展账单核验、争议处理和审计能力时就不需要重新改造底层数据链路。
参考资料
- Open Charge Alliance:《Open Charge Point Protocol 1.6》
- SAFE / DKE:《Open Charge Metering Format,Version 1.0,Revision 1.0.1 Draft》
- IEC 62056-6-1、IEC 62056-6-2:OBIS 相关标准