简介:ISO15118协议作为电动汽车与充电设施(EVSE)间通信的核心国际标准,其XML Schema规范文件是保障双方数据交换一致性与互操作性的关键。这份资源完整收录了DIN70121、ISO15118-2(交流充电)与ISO15118-20(直流充电)三部分所需的XSD定义文件,面向充电桩厂商、车载端开发人员及协议测试工程师,可直接用于开发调试、报文校验与协议合规验证。压缩包共22个文件,包含20个xsd、1个xml及1个exi文件,整体仅40KB,结构完整、便于快速导入建模工具或集成至测试平台。已有200人下载学习,适合作为V2G通信开发初学者的参考蓝本,也适合有经验工程师对照排查消息格式问题。围绕DIN70121与15118-2/15118-20,资源提供了覆盖消息头、消息体、数据类型及XML签名等关键模块的schema定义,配合示例xml与exi数据,能够辅助使用者理解充电流程各阶段的消息交互,验证自身设备实现是否符合协议约束,有效缩短联调周期。
1. ISO15118的XSD规范文件:给V2G报文装一把校验的“尺子”
调试ISO15118充电通信的时候,最折磨人的不是协议里那一堆Req/Res消息,而是你无法证明自己发出去的报文“合法”。手工抓包对比、拿PDF逐字段核对、靠经验猜枚举,这套流程既慢又容易翻车。这份资源把DIN70121、15118-2、15118-20三套协议的报文结构全部固化成了XSD schema文件,每个消息类型、必填字段、取值范围都写在里面。适合三类人:写EVCC/SECC协议栈的嵌入式工程师、搭充电桩测试台架的测试开发,以及刚接手V2G项目想快速建立报文结构认知的新人。有了它,验证一条报文是否合法就是一条命令的事,不用再抱着规范文档当小说翻。
2. 三套XSD的边界:DIN70121、15118-2、15118-20到底差在哪
三份XSD不是同一个协议的三个版本,而是三代演进关系。DIN70121是德国2012年的标准草案,后来演进成ISO 15118-2,2014年正式发布;15118-20则是第二版V2G通信,把双向充电、即插即用、无线充电这些新场景纳入到消息骨架里。三套XSD的targetNamespace、消息集合、字段约束都有差异,选错了整个代码生成和测试体系都会跟着错。我见过不少项目没确认车端和桩端到底走哪个协议版本,直接拿15118-2的XSD去测15118-20的报文,结果所有用例全红。所以先讲清楚它们各自的边界。
2.1 DIN70121:直流快充的最小XSD子集
DIN70121只覆盖直流快充场景,XSD里的消息集合是整个家族里最小的。典型消息包括SessionSetup、ServiceDiscovery、ServicePaymentSelection、Authorization、ChargeParameterDiscovery、PowerDelivery、SessionStop、ChargingStatus。它没有交流相关的ChargeParameter消息,也没有MeterInfo的Receipt细粒度上报,很多安全证书相关消息在DIN里也是可选项。
如果只做老的直流快充兼容项目,拿DIN70121的XSD生成代码确实轻松,编译出来的类少一半以上。但实际落地时有两个隐患:一是国内充电桩项目通常会加自定义字段,DIN的XSD没有扩展点,硬加会破坏schema合法性;二是车端如果稍微新一点,报文中带了个15118-2新增的字段,DIN的XSD直接报“element not expected”。我一般建议,新项目即便只做直流,也直接以15118-2为基线,DIN的XSD只留作老车型兼容测试用。
2.2 ISO 15118-2:AC/DC全覆盖的消息骨架与命名空间策略
15118-2的XSD把消息体抽象成“abstract Body”加substitutionGroup机制。根元素V2G_Message固定包含Header和Body,Header里有SessionID、Timestamp、Signature等通用字段;具体的SessionSetupReq、ChargeParameterDiscoveryRes等消息元素通过substitutionGroup挂到Body上。这么设计有个好处:收包时用同一个解析入口做成多态分发,坏处是代码生成器如果没处理好,所有具体消息都会变成JAXBElement包装,调用时代码很啰嗦。
命名空间也变了,从DIN的“urn:din:70121:2012:MsgDef”换成“urn:iso:15118:2:2013:MsgDef”。命名空间一变,代码生成出来的包名、类的@XmlRootElement注解全部要跟着调整。另外15118-2的XSD里增加了CertificateInstallation、CertificateUpdate这些与即插即充凭证相关的消息类型,类数量比DIN多出一大截。生成代码前要先确认依赖的XSD文件齐全,15118-2的XSD经常引用外部SAML、XML-Signature类型定义,缺了依赖文件就会编译失败。
2.3 ISO 15118-20:双向充电、即插即充带来的XSD扩展点
15118-20是面向下一代场景的重构,XSD的targetNamespace带上了“-20”标识,消息结构也做了调整。最大的变化是支持BPT(双向功率传输),新增了PowerSchedule、PowerTolerance这类带时间序列的能量调度类型。做V2G放电项目时,必须用15118-20的XSD,因为15118-2里根本没有动态双向功率调节的消息模型。
一个容易被忽略的点是:15118-20里ServiceDiscoveryRes返回的ServiceCategoryType枚举扩展了更多选项,比如BPT相关的类别。如果拿15118-2的XSD去解析,碰到这类枚举值就是“invalid enum value”,直接解析失败。所以做新项目时,我一般直接以15118-20为基线,老的15118-2 XSD只作为兼容性验证用,避免代码里混入两套命名空间,把协议栈搞成“四不像”。
2.4 三套XSD的选择建议:按充电场景而不是按协议名称选
我整理了一张选型参考表,按实际场景选择,而不是看着协议名字猜。
| 项目场景 | 首选XSD | 理由 |
|---|---|---|
| 直流快充老桩兼容(欧标存量项目) | DIN70121 | 消息子集小,字段少,快速上手 |
| 交流+直流新国标桩、EVCC开发 | 15118-2 | 覆盖AC/DC,证书链消息完整 |
| V2G双向充电、无线充电、储能联动 | 15118-20 | 支持BPT、PnC、WPT,能量调度类型新 |
| 一致性测试与互通测试 | 三套都备 | 按被测对象的协议版本选择对应XSD |
选型的时候还要看车端和桩端实际跑的是什么版本。车端按15118-20开发,桩端固件只支持15118-2,两边的XSD都得在自己的测试环境里各备一套,不能混用。这个判断直接决定后面代码生成、报文校验、测试脚本能不能收敛。
3. 从XSD到可执行代码:生成、编译、校验的三条落地路径
XSD文件本身不是代码,但它是代码生成器和校验器的输入。这个章节讲清楚三条最常见的落地路径:Java生态用XJC生成JAXB类,Python生态用xmlschema库直接校验XML实例,以及用XMLSpy这类图形工具快速生成样例报文。每一条我都会给出命令和参数说明,以及失败时怎么定位。
3.1 生成代码前的预处理:先把XSD依赖理清
拿到XSD文件包,第一件事不是打开IDE去生成代码,而是检查文件之间的依赖关系。用文本编辑器打开每个xsd文件,看开头的import和include声明,确认三件事:一是相对路径是否完整,比如15118-2的XSD可能引用了外部类型定义文件,单独拷出一个xsd文件放到别的目录,解析就直接失败;二是同一个命名空间的多个XSD是否存在重复定义;三是两套协议之间的XSD有没有命名空间交叉引用。
我一般把文件按协议分开目录存放,结构像这样:
schema/ ├── din70121/ │ └── DIN70121.xsd ├── iso15118-2/ │ ├── iso15118_2.xsd │ └── external_types.xsd └── iso15118-20/ └── iso15118_20.xsd这样做的原因是xjc和Python的xmlschema解析import时,默认按相对路径找文件。文件被移动过目录层级,解析器就找不到依赖,报错信息又长又晦涩,最后排查半天发现只是路径不对。还有一点:不要把三份XSD全丢进同一个目录让生成器一次处理。DIN70121和15118-2虽然都定义消息结构,但分属不同命名空间,某些生成器会把它们当成重复定义,直接报“duplicate declaration”。
3.2 用Java XJC把XSD转换成JAXB类
Java生态里最常见的做法是用JAXB的xjc命令生成类。命令本身不复杂:
xjc -d src -p com.example.v2g.din DIN70121.xsd xjc -d src -p com.example.v2g.iso151182 iso15118_2.xsd第一行的-d指定生成源码输出目录,-p指定生成的包名,最后是XSD文件路径。第二行生成15118-2时包名换成不同前缀,这样两套协议生成的类不会在同一个包下冲突。如果XSD有外部依赖,把依赖文件放在同一个目录下,或者用-b参数指定绑定文件:
xjc -d src -p com.example.v2g.iso151182 -b bindings.xjb iso15118_2.xsdbindings.xjb里可以放全局绑定配置,比如把XSD的dateTime类型映射成Java的Date类型:
<jaxb:bindings version="2.1" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <jaxb:globalBindings> <jaxb:javaType name="java.util.Date" xmlType="xs:dateTime"/> </jaxb:globalBindings> </jaxb:bindings>这段绑定的作用是把XSD里的dateTime类型生成成Java的Date,而不是默认的XMLGregorianCalendar,省掉后面一堆类型转换代码。参数说明:-b可以出现多次,每个绑定文件对应一个XSD或命名空间范围;javaType的xmlType是指XSD那边的类型,name是指Java这边的目标类型。
生成完成后先编译一遍:
mvn compile如果编译报错,常见就两个原因:一是外部依赖类没生成,确认依赖的XSD都参与了编译;二是不同XSD里同名的复杂类型冲突,看报错信息里的“duplicate class”能直接定位到是哪个文件。我遇到这类问题基本都靠分目录、分包名解决,很少去改XSD本身。
3.3 用Python的xmlschema库直接校验XML实例
日常写测试脚本时,我更喜欢用Python的xmlschema库,不用生成代码就能校验报文:
import xmlschema schema = xmlschema.XMLSchema("iso15118_2.xsd") xml_path = "sample_messages/session_setup_res.xml" try: schema.validate(xml_path) print("校验通过") except xmlschema.XMLSchemaValidationError as e: print("校验失败:", e)逻辑说明:XMLSchema构造函数加载XSD文件,validate方法用这个schema校验指定的XML实例;如果报文不满足定义,会抛出XMLSchemaValidationError,异常对象里包含失败原因、节点路径和建议取值。参数说明:validate的第一个参数可以是文件路径也可以是文件句柄。
这里有个关键参数:如果报文XML实例里没有声明命名空间,或者默认命名空间和XSD的targetNamespace不一致,必须显式指定:
schema.validate(xml_path, namespaces={"": "urn:iso:15118:2:2013:MsgDef"})namespaces参数接收一个字典,""代表默认命名空间。这个参数救过我很多次,抓包工具导出的XML经常不带命名空间声明,直接用schema校验全是“element not expected”的错误,加了参数就正常了。
3.4 用XMLSpy生成样例报文做快速验证
纯代码路径够用,但排查“某个字段到底在哪个层级”的时候,我习惯用XMLSpy把XSD直接转成实例XML。具体做法是打开XSD文件,右键选择“Generate Instance from Schema”,它会让你选择根元素、是否生成可选元素、是否生成样例值。注意两个参数:一个是“Content”里选“Generate optional elements”,这样生成的样例报文会带上所有可选项,方便看到完整消息结构;另一个是“Values”里选“Random values”,会生成随机枚举值,适合拿来跑解析器冒烟测试,但不适合放在自动化断言里,因为随机值会导致断言不稳定。
XMLSpy生成的样例XML主要用途是快速看懂消息结构,不是最终交付物。把样例报文跑一遍自己的解析器,能第一时间发现代码里对某个字段类型、层级关系的理解是不是和XSD对得上。
4. 避坑指南:XSD版本、EXI编码与工具链的五个常见问题
这部分全是实操中踩过的坑,每条按现象、原因、解决三步写,对照着自己的情况排查就行。
4.1 现象:同一个报文XMLSpy能过,Python的xmlschema却报错
原因:两边解析时使用的schema文件不一样。XMLSpy会做容错处理,自动补齐命名空间默认值;xmlschema则严格要求XML实例根元素声明的命名空间与XSD的targetNamespace一致。解决:先打印XSD的targetNamespace确认,然后给validate传namespaces参数;如果还不行,把XSD依赖的import文件放到同一个目录再跑一次,排除依赖缺失。
4.2 现象:xjc生成类后编译报大量“duplicate class”错误
原因:把DIN70121和15118-2的XSD放在同一个目录,用同一条xjc命令批量生成。两套协议消息结构相似,尽管命名空间不同,生成的Java类名却相同,直接冲突。解决:分目录、分命令行生成,并指定不同包名;不要试图让一条命令吃掉所有协议。这个坑我犯过一次之后,就养成了“一个协议一个目录一个包名”的习惯,再也没有因为类冲突浪费过时间。
4.3 现象:EXI解码出来的XML用XSD校验不过,报错的都是数值字段
原因:这是最典型的版本泥潭。ISO15118-2和15118-20的传输层消息用EXI编码,EXI编码器按特定版本的XSD作为grammar来压缩标签和数值。如果车端用15118-20的XSD编码,桩端解码却用15118-2的XSD做grammar,二进制流对不上,解码出来的字段错位,数值字段最明显。解决:先确认通信双方跑的是同一个协议版本,再固定用同一版本的XSD去解码和校验。我在测试环境里专门建了一个“版本签收表”,记录每个台架用的XSD文件名和文件哈希,防止有人悄悄换了版本文件导致整套测试数据失效。
4.4 现象:校验时出现“attribute 'sessionID' not allowed”之类的报错
原因:大小写或命名空间前缀问题。V2G协议里很多属性名是驼峰写法,比如sessionID、responseCode,手写XML时写错大小写很常见;另外某个具体消息元素带了“v2g:”前缀,但实例文档里没声明这个前缀,schema校验就认为属性不在命名空间里。解决:手写XML时直接从XSD文件复制属性名,不要凭PDF里的截图打字;命名空间问题用XMLSpy的命名空间分配工具理顺根元素,再让校验器跑一遍。
4.5 现象:XSD字段定义和协议PDF对不上
原因:规范PDF有勘误和补充,XSD文件包可能是同步更新也可能滞后,部分字段在协议文本里已经废弃,但XSD里还保留着。解决:把XSD当作“机器可读的草稿”,代码实现以实测互通为准;重大差异记录下来,不要直接改XSD文件,否则后续升级对不上官方版本。协议栈开发这里多少有点“玄学”成分——XSD是工具不是权威,最终以现场交互结果为准。
5. 把XSD变成测试资产:一致性预检、异常报文与CI接入
XSD最容易被低估的价值是它能作为测试资产的基准。协议栈改了一版代码,怎么快速知道生成的报文没有回归?怎么构造一批非法报文验证容错能力?怎么让每次提交都卡住坏报文?这章讲三个可落地的用法。
5.1 用XSD对EVCC报文做一致性预检
协议栈改动后,把代码生成的XML实例写入临时目录,然后用脚本批量校验:
import xmlschema import glob schema = xmlschema.XMLSchema("iso15118_2.xsd") passed = 0 failed = 0 for xml_file in glob.glob("generated_messages/*.xml"): try: schema.validate(xml_file) passed += 1 except xmlschema.XMLSchemaValidationError as e: failed += 1 print("FAIL:", xml_file, e.reason) print(f"passed={passed}, failed={failed}")逻辑说明:glob.glob匹配指定目录下所有XML文件,逐个用同一个schema校验;校验失败时打印文件名和失败原因。参数说明:e.reason是异常对象里的简要失败描述,比看完整堆栈快得多。这个脚本比手动对报文高效太多,也是把XSD变成测试资产的第一步。
5.2 构造异常报文:缺字段、错类型、越界枚举
测试协议栈容错能力,需要故意生成非法报文。先用Python遍历XSD,找出某个消息的所有必填字段:
import xmlschema schema = xmlschema.XMLSchema("iso15118_2.xsd") root_type = schema.get_type("V2G_Message") for element in root_type.content: if element.required: print(element.name, element.type.name)逻辑说明:get_type按名字获取XSD里的类型,content是该类型的子元素列表,required标记是否必填。注意get_type的入参名要以XSD文件里的实际类型名为准,别照着协议PDF上的名字猜。打印出的每个必填元素都可以作为“破坏点”:复制一份合法报文,删掉其中一个必填字段,再跑一次schema.validate确认它确实被判非法。越界枚举同理,把一个枚举字段改成XSD里不存在的值,然后回调校验器验证。这个“先构造、再确认”的闭环能避免测试代码自己写错,把假阳性用例带到测试集里。
5.3 把XSD校验放进CI:每次提交都跑一轮schema check
把校验脚本接进CI最实在的做法是写成pytest用例,而不是纯shell脚本。示例:
import pytest import glob import xmlschema schema = xmlschema.XMLSchema("iso15118_2.xsd") @pytest.mark.parametrize("xml_file", glob.glob("generated_messages/*.xml")) def test_message_schema(xml_file): schema.validate(xml_file)注意一个坑:glob.glob在导入阶段执行,如果新生成的报文文件在pytest收集用例时还没落盘,这条用例就漏掉了。我一般用一个fixture动态生成用例,确保每次运行都能扫到最新文件。CI里这个步骤放在编译之后、集成测试之前,一旦schema校验失败直接中断构建,不让坏报文流到后面的测试环境。这个习惯帮我拦截过很多次“代码看着没问题,报文结构已经改坏了”的回归。
6. 进阶技巧:从XSD反推消息树,沉淀成内部速查表
协议字段多起来以后,最怕的是“我知道有这个字段,但想不起来它在哪个消息的哪个层级下”。翻PDF太慢,看XSD源码又不够直观。我写了个脚本遍历XSD,把消息类型和子元素层级打成一棵缩进树:
import xmlschema schema = xmlschema.XMLSchema("iso15118_20.xsd") def walk_type(t, depth=0): if t is None: return content = getattr(t, "content", None) if content: for key, value in content.items(): if hasattr(value, "type"): print(" " * depth + f"{key}: {value.type.name}") walk_type(value.type, depth + 1) for name, elem in schema.elements.items(): if "ChargeParameter" in name or "PowerSchedule" in name: print(f"== {name} ==") walk_type(elem.type, 1)逻辑说明:walk_type递归遍历复杂类型的子元素,用缩进展示层级;外层只筛选包含“ChargeParameter”或“PowerSchedule”的消息,避免打印整个协议的全部结构。注意不同版本的xmlschema库属性名可能有差异,先在自己的环境里跑一下dir(schema)确认属性名再写遍历逻辑。
脚本输出后,把关心的业务消息整理成速查表:
| 消息 | 方向 | 关键必填字段 | 常见枚举/取值 |
|---|---|---|---|
| ChargeParameterDiscoveryReq | EV→EVSE | EVMaximumPowerLimit、EVMaximumCurrentLimit、EVMaximumVoltageLimit | DC/AC分别不同子元素 |
| PowerScheduleReq | EV→EVSE | PowerSchedule、TimeAnchor | 时间间隔按秒 |
表里的字段按项目实际需要增减。这个速查表比翻协议PDF快得多,开会讨论字段时直接看这张表就能对齐。从那以后,我每次拿到新版本协议包,都强制走一遍XSD树梳理和报文校验闭环,再决定要不要动代码。这套流程帮我少踩了好几个版本混用的坑,希望帮到你。
本文还有配套的精品资源,点击获取