简介:面向电动汽车充电通信与充电设施开发者的ISO15118协议schema规范文件包,完整涵盖DIN70121、ISO15118-2和ISO15118-20三部分,分别对应德国早期充电通信标准、交流充电与直流充电场景,定义了V2G消息结构、数据类型、XML签名约束等核心规则,为充电桩与车载端实现跨厂商互操作通信提供标准依据。压缩包共22个文件,以20个XSD模式定义文件为主,另含1个XML实例和1个EXI编码示例,整体仅40KB,便于快速查阅与集成验证。已有200人学习下载。通过这份文件包,读者可获得三套协议完整的XSD定义、消息头/消息体/数据类型分层体系及XML签名核心模式,适合在协议栈开发、一致性测试和充电流程调试中对照使用,减少因数据结构定义偏差导致的通信故障,提升身份认证、计费、能量管理等环节的对接效率。
1. ISO15118 协议 schema 规范文件:三套 XSD 为什么是 V2G 联调的“字典”
调试 CCS 直流充电桩时,最头疼的不是电压电流,而是车插上枪之后握手失败。协议栈日志里只有一行“XML 解析错误”,人在现场根本看不出是哪个字段出了问题。后来我把抓到的报文丢进 ISO15118 协议 schema 规范文件里的 XSD 一校验,三秒钟就定位了——少了一个必填字段。这套 schema 规范文件把 DIN70121、ISO15118-2、ISO15118-20 三部分的 XSD 集合在一起,本质上是 V2G 应用层消息的唯一“字典”:消息里能出现哪些元素、字段是必填还是可选、枚举值从哪来、类型怎么派生,全部由它说了算。适合正在做 EVCC/SECC 协议栈、充电桩测试工具或者车桩互操作验证的工程师,也适合刚接手协议栈想快速看懂消息结构的人。
2. DIN70121 与 ISO15118-2、ISO15118-20 三套 XSD 的定位与差异
2.1 三份标准各管什么:协议栈里的哪一层会被 XSD 约束
先搞清楚一件事:ISO15118 协议 schema 规范文件约束的只是应用层 V2G 消息结构,不碰物理层、不碰 TCP/UDP,也不碰 TLS 握手。完整的通信协议栈从底往上大致是物理层、数据链路层、TCP/UDP、TLS、V2GTP、应用层 V2G 消息。V2GTP 负责加一个固定头(类型、长度、协议版本),把上层消息封装进 TCP 流;而 XSD 管的就是 V2GTP 头上面的那一层消息体。我做联调时最常用的判断是:如果错误信息来自 V2GTP 头解析,那是传输层问题;如果报 XML 结构错误,那才轮到 XSD 出场。
三份标准的定位完全不同。DIN70121 是德国 2012 年发布的先行标准,主要覆盖直流充电场景,消息是 XML 明文形式跑在 TLS 上。当年车桩互操作大量靠它,所以存量设备里 DIN70121 的占比不小,很多早期直流桩和旧车型到现在还在用。ISO15118-2 是 2013 到 2014 年间发布的国际版,把场景扩展到了交流充电和智能充电,增加了计费、签名计量等服务,消息结构比 DIN70121 复杂一个量级。ISO15118-20 是 2022 年前后落定的新一代版本,新增双向充放电、无线充电、BPT 等能力,消息载体从 XML 转向 JSON,但规范包里仍然保留了三套 XSD,作为结构定义和版本映射的参照基底。三份规范在 schema 层面对应各自的 XSD,互不通用。
| 规范 | 发布背景 | 主要场景 | 消息格式 | 典型现状 |
|---|---|---|---|---|
| DIN70121 | 德国先行,直流快充 | 直流充电、引导充电 | XML over TLS | 存量桩/车保有量大 |
| ISO15118-2 | 国际标准,AC/DC 全覆盖 | 交流/直流、智能充电、计费 | EXI 编码 XML | 当前主流量产方案 |
| ISO15118-20 | 新一代标准 | 双向充放电、无线充电、V2G | JSON 为主,XSD 辅助映射 | 新项目选型方向 |
顺带一提,ISO15118-2 里 XML 消息在传输时普遍用 EXI 压缩编码,而不是明文 XML。这一点在联调中坑过不少人:抓包看到的是二进制流,直接拿去用 XSD 校验当然失败。后面第 4 章我会专门展开这个链路。这里先记住结论:三套 XSD 不是三个版本的同义词替换,而是三套独立的消息字典,字段名、命名空间、枚举值都有差异。
2.2 从 DIN70121 到 15118-20:消息集是“继承 + 扩展”还是“推倒重来”
从消息流程看,三个版本共享同一个大框架,这也是很多人误以为 XSD 能通用的原因。典型会话都包含会话建立、服务发现、充电参数协商、功率控制、会话停止这几个阶段。DIN70121 里的流程大概是 SessionSetup、ChargeParameterDiscovery、PowerDelivery、SessionStop 这一串;ISO15118-2 在中间插入了 ServiceDiscovery、PaymentDetails、MeteringReceipt 等环节;ISO15118-20 又把双向充电参数塞进了充电参数协商阶段。整体是“继承主干、不断分叉”的关系。
但具体到 XSD 里的元素定义,绝不是简单加字段。举一个我印象很深的例子:15118-2 里 PaymentDetailsReq 引入了 DIN70121 完全没有的计费身份和签名相关字段,而且这些字段被包在复杂的 choice 结构里,选 A 就不能选 B,校验失败时提示位置经常偏离真实问题。到了 15118-20,双向充电带来了 EVPowerSchedule、PowerTolerance 这类新对象,连 ChargeParameterDiscovery 的层级都被重排。换句话说,15118-20 中双向充电相关字段是新增,但也顺手把旧字段重新归组,属于局部重构。
所以实际开发里常见的做法是:协议栈里同时保留三套 XSD,按会话协商出的协议版本路由到对应 XSD 做校验。路由的判断依据不光是 V2GTP 头里的协议版本字段,还要看根元素的命名空间。DIN70121 的命名空间以 DIN 70121 为标识,15118-2 和 15118-20 的命名空间各自独立,同一会话中如果报文根元素带错了命名空间,XSD 校验一定报 not declared。我一般会先在代码里写一个小函数,读 V2GTP 头版本号和根元素命名空间,输出一个枚举常量,再拿这个常量去查表拿 XSD 文件路径。版本路由是这套规范落地时第一个要设计好的点。
3. 拿到三套 XSD 后怎么读、怎么校验、怎么生成代码
3.1 先按依赖顺序梳理 XSD 文件引用链:从 DataTypes 读到 MsgDef
XSD 文件的拆分方式在不同发布包里会有差异,但常见做法是把一套规范按职责切成四个粒度:DataTypes、MsgHeader、MsgBody、MsgDef。名称可能不同,职责基本一致,拿到手先按这个思路归类,比逐个文件打开看快得多。DataTypes 里放枚举和基础复杂类型,比如 ChargeProgress 的枚举、digest 类型、证书相关结构;MsgHeader 定义消息头,包含 SessionID、签名、协议版本这些公共信息;MsgBody 定义各条消息的 Req/Res body 复杂类型;MsgDef 是顶层定义,用 choice 把 Header 和 Body 组装成一条完整消息。
| 文件角色 | 内容 | 被谁引用 |
|---|---|---|
| DataTypes | 枚举、基础类型、公共结构 | 所有其他文件 |
| MsgHeader | 消息头结构、签名、SessionID | MsgDef |
| MsgBody | 各消息的 Req/Res body | MsgDef |
| MsgDef | 顶层消息 choice 定义 | 外部校验入口 |
读文件时不要按文件名顺序读,要按依赖顺序读。打开任意一个 XSD 先看根节点的 xsd:import 和 xsd:include:同命名空间用 include,跨命名空间用 import。常见的依赖链是 DataTypes 被 Header 和 Body 引入,Header 和 Body 再被 MsgDef 引入。从 DataTypes 开始读,再看 Header 和 Body,最后看 MsgDef 的顶层 choice,这样能在一个小时左右理清一套 XSD 的全貌。
这里有个关键操作:拿到包之后先把目录结构原样保存,不要自己重命名文件,也不要按个人习惯改成单文件。XSD 里 include 和 import 是相对路径解析的,一旦移动文件,校验器报的“Failed to load schema”会让你误以为是 schema 本身有问题。我踩过这个坑,后来任何规范包都是先进 git 再动手,保留原始目录树是第一原则。
3.2 用 XSD 校验一条 V2G 消息的最小流程
联调现场校验消息,我一般按四步走。第一步,从抓包数据里拿到应用层 payload;如果跑 15118-2,需要先把 EXI 流解码成明文 XML,只有 DIN70121 这类明文场景能直接进校验。第二步,确认根元素和命名空间,选对 XSD;拿 15118-2 的 XSD 去校验 DIN 报文,报错会多到没法看。第三步,执行校验命令。第四步,读错误信息,定位到具体元素和行号。
校验命令用 xmllint 最直接:
xmllint --noout --schema V2G_CI_MsgDef.xsd message.xml前半部分是关闭文档输出,只报错误;后半部分是执行 schema 校验。xmllint 会在终端打出类似element ChargeParameterDiscoveryReq: Schemas validity error : Element '{}SessionID': Missing child element(s). Expected is ( SessionID )这样的信息,里面带元素名和缺失的字段,比协议栈日志里那句“XML 解析错误”有用太多。
参数说明:--noout是不输出原始 XML 内容,否则合法消息会把整条报文打在终端上,很占屏幕;--schema指定 XSD 文件路径。如果校验器报加载 schema 失败,先去查 include/import 相对路径,八成是文件没按原始目录放。xmllint 只能管 XML,JSON 消息要换 jq 或专门的 JSON Schema 校验器,但那属于 15118-20 的工具链,后面第 6 章再提。
3.3 用 XSD 生成协议栈骨架:工具选型与必调参数
XSD 不只是用来校验报文,更大的价值是生成代码。生成协议栈消息模型时,常见做法是先选目标语言再选定工具,Java 方向用 JAXB 的 xjc,C++ 方向用 CodeSynthesis XSD/e,Python 方向用 xsdata。这三个工具我都用过,生成效果差异不大,坑都在命名空间和文件依赖处理上。
xjc -p com.vendor.v2g -d gen V2G_CI_MsgDef.xsd -extension-p指定 Java 包名,多套规范共用生成代码时绝对要分开,否则 DTO 类名直接冲突;-d是输出目录;-extension让 JAXB 兼容 XSD 里不太好映射的构造,比如 choice 嵌套。C++ 方向我常用:
xsdcxx cxx-tree --generate-serialization V2G_CI_MsgDef.xsd--generate-serialization会额外生成序列化和反序列化代码,跑车桩互测时省去手写 XML 编解码的功夫。Python 方向更轻:
xsdata generate V2G_CI_MsgDef.xsd --package v2g--package指定生成模块前缀,避免三套规范生成文件混在一起。
参数层面的经验是:生成前确认 XSD 的依赖文件都在当前目录或者能被 include/import 路径找到。用在线转换工具看起来很省事,但命名空间经常被压平,code generator 一跑就翻车,我后来再没用过在线生成器。生成了代码也别直接信任,拿第 3.2 节的校验命令跑一遍真实报文,确认序列化结果和 XSD 定义一致再进协议栈。
4. 把 XSD 嵌进联调链路:校验时机、EXI 解码与抓包验证
4.1 V2G 消息在 TLS 会话里是“先解码再校验”
ISO15118 的联调链路比普通 XML 接口多一层:15118-2 的 XML 消息在传输时用 EXI 编码压缩,DIN70121 大多是明文 XML,而 15118-20 直接换了 JSON。这意味着在协议栈里不能拿到 payload 就去做 XSD 校验,必须先判断编码格式,再决定解码路径。
常见做法是定义一个消息解码管线:第一步从 TCP 流里按 V2GTP 头解出 payload;第二步读 V2GTP 头里的协议版本字段,判断是 DIN70121、15118-2 还是 15118-20;第三步按版本走对应解码器——DIN 直接出明文 XML,15118-2 走 EXI 解码器出 XML,15118-20 走 JSON 解析器;第四步才是 XSD 校验。很多桩端协议栈在这个环节翻车,并不是 XSD 写错了,而是 EXI 解码失败后把乱码当成 XML 送去校验,报错自然无法定位。EXI 解码器依赖 XSD 生成的 grammar 文件,所以在 15118-2 场景里,XSD 的职责从“校验字典”扩展成了“编解码基准”。
我一般在联调现场先问一句:你们抓到的包是明文还是 EXI?只要看到抓包文件里 payload 开头不是<而是二进制头,就别急着用 xmllint。先把 EXI 流转成 XML,再进校验。这一步是新手最容易忽略的,也是协议栈兼容性测试里最常见的超时原因之一。
4.2 联调现场的三组验证手段:校验器、EXI 互测和抓包还原
联调时我习惯把验证手段分成三组,分别对应结构正确性、编解码一致性和现场复现,三组互相补充,单一手段很难覆盖所有问题。
| 验证手段 | 常用工具 | 验证目的 |
|---|---|---|
| Schema 结构校验 | xmllint、oXygen、XMLSpy | 消息是否符合 XSD 定义 |
| EXI 编解码互测 | OpenEXI、自研 grammar 测试 | 编解码器与 XSD 是否一致 |
| 抓包还原 | Wireshark V2GTP 解析、TLS 密钥导出 | 现场复现并还原明文消息 |
第一组是基础,适合验证单条消息结构。第二组是 15118-2 特有,把 EXI 编码出的二进制流再解码回 XML,和原始 XML 逐字段比对。第三组是现场问题复现的关键:Wireshark 能解析 V2GTP 头,但 TLS 加密后的报文是黑匣子,需要在测试台配置导出 TLS 会话密钥,或者让桩端临时关闭 TLS 做明文抓包——后者在实验室环境常见,但不能用于正式互操作测试。
我之前遇到过一个诡异问题:同一台车在 A 桩握手正常,在 B 桩反复超时。用 Wireshark 抓包导出 TLS 密钥后还原明文,发现 B 桩在 EXI 解码阶段把 SessionID 的字节序多处理了一位,导致后续所有消息里的 SessionID 对不上,通信状态机直接卡死。这个问题的根因根本不在 XSD,但没有 XSD 生成的标准 grammar 做基准,EXI 解码是否正确也无从谈起。所以联调工具链里始终保持一份最新 XSD,是排查这类问题的前提。
5. XSD 应用避坑:从命名空间路由到大小写敏感的 5 个典型问题
5.1 用 15118-2 的 XSD 校验 DIN 报文,报错多到无法看
现象:车桩互测时,抓到的报文明明结构完整,但用 15118-2 的 XSD 一校验,错误列表超过几十条,几乎每个元素都报 not declared。
原因:DIN70121 和 ISO15118-2 是两套独立规范,根元素命名空间不同,消息名和字段层级也有差异。15118-2 的 XSD 里根本没有 DIN 的命名空间声明,更不会有对应的元素定义,自然全盘报错。
解决:校准入口要先做版本路由。可以从 V2GTP 头的协议版本字段或根元素的命名空间判断当前会话属于哪套规范,再选择对应 XSD。我这里会把三套 XSD 的路径配成映射表,而不是在代码里硬拼字符串;宁可多写一个函数,也不要在联调现场人工选文件。
5.2 校验报错位置离真实问题很远,总是提示在 choice 分支尽头
现象:一条 ChargeParameterDiscoveryReq 校验失败,xmllint 报错指向消息末尾的某个元素,但实际问题是中间某个必填字段漏了。
原因:V2G 消息里大量使用 choice 和 sequence 嵌套,XSD 校验器在回溯时会把失败点推进到最后无法匹配的分支,而不是直接指向漏掉的字段。这是 XSD 本身的结构特性,不是工具 bug。
解决:别只看首条错误,用xmllint --noout --schema XSD message.xml 2>&1 | head -50把前几十条错误全部拉出来,按元素路径排序比对;或者先解析成 DOM,按错误信息里的元素路径逐层往上查,找到最先断掉的那个必填项。经验是:最靠前的 schema 错误往往不是根因,根因通常藏在“缺失子元素”这种信息里。
5.3 枚举值对不上,实际枚举比 XSD 里多了一个带前缀的奇怪值
现象:代码里按 XSD 的枚举定义写死了几个值,但实际报文里出现的值不在枚举列表里,校验却通过了,运行时业务逻辑反而抛异常。
原因:XSD 里有 complexType 的 restriction 派生链。有些枚举被定义在 DataTypes 的 simpleType 里,但外层 complexType 通过 restriction 重新约束,导致实际允许的值范围跟“肉眼看到的枚举”不一样。另一个常见原因是 internalType 这类内部类型混入了公共结构。
解决:遇到枚举不一致时,直接查 XSD 里的派生链,找到最底层的 simpleType 看枚举来源。日常维护可以在生成代码时加一个 XML 枚举校验器,把 XSD 里所有 simpleType 枚举导出成清单,写进单元测试。这个做法能提前暴露 schema 更新导致的枚举漂移。
5.4 只拷了一个 XSD 文件进工程,运行时报 Failed to load schema
现象:工程师为了省事,只把 MsgDef 一个文件复制进工程目录,运行时直接报 Failed to load schema,找不到 DataTypes。
原因:XSD 通过 include 和 import 引用了同目录下的其他 XSD,被引用的文件必须按相对路径存在。只拷单文件等于撕掉了文件间依赖链。
解决:把整套 XSD 按原始目录结构提交到仓库。注意一点:不要为了“整洁”把所有文件平铺到一个目录,include 的路径解析虽然在某些工具里会自动搜索当前目录,但换一个工具链就翻车。保留原始目录结构和文件名,是最省心的做法,也符合拿到规范包后“保留原样”的第一原则。
5.5 元素名大小写不对,校验报 not declared
现象:报文里把 PaymentDetails 写成了 paymentdetails,XSD 校验直接报元素未定义。这个错误在视觉上不明显,因为 XML 报错信息不会主动提示大小写。
原因:XML 元素名严格区分大小写,V2G XSD 里元素名按驼峰定义,写错大小写等于引用了一个不存在的元素。
解决:校验报文前先做一个大小写检查,或者直接用代码生成的序列化器来构造消息,不要手拼 XML。手拼消息时常见错误还有把 SessionID 写成 SessionId,这类问题在工具链里加一个 schema 校验步骤就能拦住。
6. 进阶:用“最小消息集”做三套 XSD 的兼容回归自测
联调进入尾声时,我习惯做一套“最小消息集”回归:从每套规范里挑三条代表性消息,构造最小合法样本,再分别用三套 XSD 校验,把通过与否的结果固定成基线。消息选型可以固定为会话建立、充电参数协商、会话停止这三个最核心节点,例如 SessionSetupReq、ChargeParameterDiscoveryReq、SessionStopReq。每个样本只保留 XSD 里的必填字段,可选字段一律不填,这样一旦 schema 更新影响必填约束,基线测试会立刻报错。
把这些样本放在测试仓库的一个固定目录里,按规范版本分三个子目录,比如 DIN70121、ISO15118_2、ISO15118_20。每次协议栈版本迭代或者 schema 文件更新,先跑一遍基线校验,再跑增量用例。你在第 3.2 节里用的那条 xmllint 命令,稍微包装一下就能变成回归脚本:遍历目录下所有 XML,逐个执行校验,把返回值非零的文件名和错误信息收集成报告。15118-20 的 JSON 消息则用对应的 JSON Schema 校验器,放到同一套脚本里。
这套基线最大的价值是帮你快速判断“是谁变了”。有一次我们升级协议栈后老车型握手失败,测试组先去查代码改动,查了半天没结果;我把上个月的最小消息集跑了一遍,发现 15118-2 的 ChargeParameterDiscoveryReq 校验失败,追下去才发现是 schema 更新后枚举值多了一个新选项,而旧车型报文里用的是旧枚举。没有基线,这个问题的定位时间大概是三小时起步。
我现在每接手一个新规范版本,第一件事就是把上一版的最小消息集丢进新 schema 里跑一遍,看它报多少个错。报错越少,说明兼容性越好;报错多也别慌,逐条对照新 XSD 的字段路径,比从文档开头读起快得多。这套方法救过我很多次,也希望帮到你。
本文还有配套的精品资源,点击获取