资讯动态

ISO 15118-20充电通信协议:从建链到即插即充的落地指南

发布时间:2026/10/15 8:39:29 来源:尧图企业网站定制
简介ISO 15118-20:2022 是国际标准化组织发布的道路车辆与电网通信接口标准聚焦第二代网络层和应用层需求面向电动汽车充电系统研发、测试认证及标准合规人员用于解决不同品牌车辆与充电设施之间的互联互通与安全通信问题。该PDF为英文原版完整标准共1个文件压缩包约34.74MB正文涵盖范围、规范性引用文件、术语定义、缩略语、OSI服务约定、需求结构、XML模式图及附录等内容便于按章节查阅网络层和应用层的详细技术要求。目前已有157人参与学习下载适合作为充电协议开发、V2G技术研究、充电桩兼容性测试及标准解读的权威依据。标准中明确规定了网络层的数据传输方式、路由选择策略、错误检测机制以及应用层的身份验证、计费、充电状态监控等交互流程并给出了消息序列与数据格式约束对提升充电效率、保障通信安全、支撑智能电网和可再生能源利用具有直接指导意义。1. 先泼一盆冷水ISO 15118-20 不是升级是重写同样插上充电枪有的车和桩等十几秒才能开始传功率有的几秒内就完成了鉴权和参数协商。差异不在充电功率而在车和桩之间的通信接口。ISO 15118-20-2022 正是这个接口的第二代标准它把第一代 15118-2 里的 UDP 传输协议整个换掉改成 TCP TLS并且把即插即充Plug Charge、双向充电BPT、无线充电WPT这些功能全部收编进同一套消息体系。对做充电桩、车载充电控制器EVCC和充电运营平台的工程师来说这不是一次跟进是通信链路和证书体系的推倒重来。这篇笔记会把协议栈、建链流程、证书配置、BPT 参数和联调踩坑按落地顺序讲清楚适合刚从 15118-2 迁过来的人也适合第一次接触这个标准但已经摸过 CCS 充电逻辑的嵌入式开发者。2. 建链时序从 CP 状态到 SDP 再到 V2GTP20 比 2 快在哪2.1 15118-20 的协议栈与前代到底差了几层老标准里车和桩的消息走的是自定义的 V2TPVehicle to Grid Transfer Protocol加上 UDP可靠性靠应用层自己保证。15118-20 直接把这一层砍掉改为在 IPv6 之上用 TCP 承载 V2GTP 报文TLS 1.2 负责加密封装。表面看只是换了个传输载体实际影响很大TCP 的窗口机制解决了大消息分片和重传问题TLS 让证书链校验不再依赖自定义安全机制IPv6 让多接口设备比如一个桩带两把枪能靠链路本地地址精确区分会话。层次ISO 15118-2ISO 15118-20应用层基于 V2G 消息的 XML Schema同左但新增 BPT 和 WPT 消息类型安全层可选 TLS兼容自定义签名强制 TLS 1.2 及以上证书链由 V2G Root CA 签发传输层V2TP over UDPV2GTP over TCP网络层IPv6可选 IPv4IPv6 为主链路本地地址必须支持物理层HomePlug Green PHYHomePlug Green PHY与 PLC 桩兼容从协议栈能直接得到一个结论15118-20 联调时抓包工具必须能解析 TCP 和 TLS不能只盯着 UDP 15118 端口。下面讲建链时我会把每一层对应到你能抓到的报文上。2.2 SLAC 与 SECC 发现PWM 握手之后车和桩怎么认出彼此充电桩的 CPControl Pilot信号从 12V 降到 9V 之后车和桩进入通信阶段。但这只是允许通信车并不知道对面是哪把枪、哪台桩。于是先走 SLACSignal Level Attenuation Characterization车和桩在电力线通信PLC上互相发送衰减特性报文用信号衰减量来唯一确认物理连接关系并生成一对匹配的网络密钥。这一步做完两边的 HomePlug Green PHY 层才建立起一个加密的 AVLINK。我一般会在 SLAC 阶段就打开 Wireshark 抓 PLC 侧报文确认 CM_SLAC_MATCH 消息里携带的 NID 和 NMK 是否一致。如果 SLAC 没完成后续的 SDPSECC Discovery Protocol请求根本不会发出来但很多工程师此时已经在查 TCP 层了方向就错了。SLAC 耗时的常见参数是超时值车端默认 30 秒内必须收到桩端的匹配确认桩端如果接入多个 PLC 设备这个时间会被拉长。2.3 SDP 请求与响应的抓包解析固定端口 15118SLAC 完成后EVCC 会向链路本地地址的 UDP 15118 端口广播一个 SDP Request请求内容包括协议版本号2 代表 15118-20和期望的传输类型TCP 还是 TLS。桩端收到后返回 SECC 的 IPv6 地址、TCP 端口和协议版本。整个交换非常短但这是最容易抓空的一段报文因为 UDP 包只有十几个字节很多人在抓包时把它当成背景噪声过滤掉了。下面这段代码是一个用 Python 手工构造 SDP Request 并解析响应的最小脚本适合在还没有接入完整协议栈时先验证桩端的 SDP 服务是否存活。import socket import struct SDP_PORT 15118 # ISO 15118-20 在 SDP Request 里上报的版本号是 2 # transport 取值: 1 TCP, 2 TLS def build_sdp_request(protocol_version2, transport1): # SDP Request 只有两个字段版本号 期望的传输协议 return struct.pack(!BB, protocol_version, transport) def discover_secc(ipv6_link_local): sock socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) sock.settimeout(5) # 目标地址是桩端的链路本地地址不是组播地址 sock.sendto(build_sdp_request(), (ipv6_link_local, SDP_PORT)) data, addr sock.recvfrom(4096) # 响应体: 版本(1字节) 保留位(1字节) SECC地址长度(1字节) # SECC地址 端口(2字节) 传输协议(1字节) offset 3 addr_len data[2] secc_ip socket.inet_ntop(socket.AF_INET6, data[offset:offset addr_len]) offset addr_len secc_port struct.unpack(!H, data[offset:offset 2])[0] return secc_ip, secc_port if __name__ __main__: # 这里的 fe80:: 地址要替换成桩端实际链路本地地址 ip, port discover_secc(fe80::xxxx:xxxx:xxxx:xxxx) print(fSECC IP: {ip}, TCP port: {port})这段代码的价值在于把 SDP 响应结构拆开看了前 3 字节是版本、保留位和地址长度之后才是地址和端口。参数里最值得注意是transport如果写成 1后续就要连 TCP 端口写成 2 就要连 TLS 端口。很多自定义实现把这两个值写反导致 EVCC 拿到的端口一直连不上。这个脚本 5 秒内没有响应基本可以断定桩端 PLC 层或 SLAC 层没准备好不用继续查上层。2.4 V2GTP 头格式TCP 之上还留了一个老朋友进入 TCP 连接后每一条 V2G 应用消息前面仍然带一个 V2GTP 头包含版本号、有效负载类型和长度。格式上和 15118-2 里的头很像但承载从 UDP 换成了 TCP 字节流所以你必须处理 TCP 粘包一次 read 可能只有半个 V2GTP 头下一次 read 可能带了三条完整消息。常见做法是维护一个环形缓冲先解析头部的长度字段再按长度读取完整的负载。这个细节不处理解析出来的 XML 永远是乱码而且时好时坏属于典型玄学故障。import socket import struct class V2GTPStreamParser: def __init__(self): self.buffer b def feed(self, data: bytes): self.buffer data messages [] while len(self.buffer) 8: # V2GTP 头固定 8 字节 version, inverted_version, payload_type, length struct.unpack( !BBHL, self.buffer[:8] ) if version ! 2 or version ! (~inverted_version 0xFF): # 版本校验失败说明缓冲区里混入了非 V2GTP 数据 self.buffer self.buffer[1:] continue if len(self.buffer) 8 length: break # 半包等下一次 socket 数据 payload self.buffer[8:8 length] messages.append((payload_type, payload)) self.buffer self.buffer[8 length:] return messages def reset(self): self.buffer b这个解析器的关键点有两个第一V2GTP 头里的长度字段是负载长度不含头本身所以总长度是8 length第二版本字段和它的反码是同时出现的专门用来做字节序和同步校验。我见过不止一次有人不看反码位直接拿版本号当帧头结果消息一多就错位。解析器里length是H即 16 位最大 65535 字节足够容纳一条完整的 XML 消息。3. TLS 1.2 与即插即充证书链不对功能再全也白搭3.1 PnC 的证书体系一根 Root 管两级即插即充不是车插上就充而是 EVCC 在 TLS 握手阶段出示合同证书Contract Certificate桩端验完证书后直接启动充电不需要用户扫码或刷卡。合同证书的签发链是三级的V2G Root CA 在顶层中间是 OEM 或充电运营商的子 CA最底下才是每辆车、每张合约对应的合同证书。普通 x.509 证书也能用但标准推荐 ECC P-256理由是证书体积小、验签速度快在车载硬件上省电。证书链校验有个容易被忽略的点桩端必须能拿到根证书但不一定需要持有一整条中间链。EVCC 在 TLS 握手时要把自己证书和中间 CA 证书一起发过来桩端用本地存的 Root CA 去验证。如果 EVCC 只发了叶子证书桩端本地又没有中间 CA校验一定失败但报错信息很笼统联调时容易被当成网络问题。3.2 EVCC 侧证书生成与 TLS 会话配置EVCC 侧的私钥和 CSR 通常在产线预置开发阶段自己生成就行。下面是生成 ECC 私钥、签发 CSR、并验证整条链的三条命令开发环境里可以反复用。# 生成 P-256 椭圆曲线私钥 openssl ecparam -name prime256v1 -genkey -noout -out evcc_contract.key # 用私钥生成证书签名请求CN 里带上唯一的车辆或合约标识 openssl req -new \ -key evcc_contract.key \ -out evcc_contract.csr \ -subj /CNEVCC-CONTRACT-0001/ODemoEV/CCN # 验证合同证书是否满足 V2G Root CA 的信任链 openssl verify \ -CAfile v2g_root_ca.pem \ -untrusted intermediate_ca.pem \ evcc_contract.pem-untrusted参数很关键它表示 intermediate CA 证书不是本地的信任锚只是辅助验证用的中间证书。如果openssl verify最终打印OK说明这条链从合同证书到根证书是完整的。TLS 配置侧EVCC 至少要启用 TLS 1.2并且 cipher suite 里要包含 ECDHE_ECDSA_AES128_GCM_SHA256 这类支持 ECC 的组合不要用 RSA 证书否则桩端会直接拒绝握手。3.3 EIM 与 PnC 的选择逻辑不是每次都要证书15118-20 里鉴权走两种路径EIMExternal Identification Means比如手机 App 远程启动和 PnC即插即充。EIM 模式下 EVCC 不主动出示合同证书桩端靠外部系统确认用户身份PnC 模式下 EVCC 必须在 TLS 手里发送证书链。这个选择发生在应用层的 Authorization 消息里但证书准备在 TCP 连接建立时就要做。场景鉴权路径TLS 要求适合场景私人桩车辆固定PnCEVCC 必须发送完整证书链家充、员工停车场公共桩同一辆车不同人EIM App不需要证书网络层做 TLS 即可网约车、共享车队公共桩希望即插即充PnC 运营平台二次确认需要合同证书 平台在线校验高速服务区、商场实际开发里经常出现一个错误代码在 EIM 模式下也把证书链发出去导致桩端返回证书错误。正确的做法是在应用层 SessionSetup 阶段就约定好鉴权方式EVCC 根据约定决定 TLS 层是否携带证书。证书装没装好不能只在自测环境验证还要拿桩端设备的信任库来验因为桩端 Root CA 可能是运营商自定义的。4. 双向充电BPT与直流充电参数新消息藏着 15118-20 的一半工作量4.1 BPT 模式从单向放行到功率反送双向充电Bidirectional Power Transfer是 15118-20 最有分量的新增功能它把车辆给电网送电放进标准的消息框架里。标准定义了 AC_BPT 和 DC_BPT 两种模式分别对应交流慢充和直流快充场景。DC_BPT 里充电桩不再是能量源而是受控的负载或者并网节点功率方向由车辆决定。参数AC_BPTDC_BPT功率方向单向或双向双向关键消息PowerDelivery 附带 AC 侧参数PowerDelivery 附带 DC 侧参数电网同步需要频率与相位跟踪需要直流母线电压控制典型功率等级3.7 kW ~ 22 kW30 kW ~ 150 kWBPT 模式下最容易踩坑的是消息里的方向字段和功率字段不在同一个结构里。很多工程师以为改了功率符号就能反向实际上 15118-20 里方向由 EnergyTransferMode 决定功率限制由 ChargeParameterDiscovery 阶段的 n 组离散功率点表达两者必须同时匹配否则桩端会误判为普通充电。4.2 ChargeParameterDiscovery 与 PowerDelivery 的关键字段ChargeParameterDiscovery 是充电参数协商的核心消息车辆上报自己的最大功率、最大电流、目标电池电压等信息桩端回合适的充电方案。这段 XML 简化的字段结构如下ChargeParameterDiscoveryReq Header SessionID0x8C2A1B/SessionID /Header DeploymentDC_BPT/Deployment DCParameterList DCChargingParameter DCMaximumPowerLimit150000/DCMaximumPowerLimit DCMaximumCurrentLimit400/DCMaximumCurrentLimit DCMaximumVoltageLimit920/DCMaximumVoltageLimit DCTargetVoltage723/DCTargetVoltage /DCChargingParameter BPTDCChannelParameter BPTMaximumDischargePowerLimit30000/BPTMaximumDischargePowerLimit /BPTDCChannelParameter /DCParameterList /ChargeParameterDiscoveryReq这个请求把充电和放电的参数分开列了DCMaximumPowerLimit是车辆能接受的充电功率上限BPTMaximumDischargePowerLimit是车辆能反送的放电功率上限。单位都是 W 或 A 这种 SI 单位不要自己加倍率换算否则 150 kW 变成 150 W 的案子我是真见过。DCTargetVoltage是电池请求的充电电压桩端必须按这个值做输出电压控制偏差太大电池保护会介入。PowerDeliveryReq 则负责启动和停止充电里面的ChargeProgress字段直接决定行为PowerDeliveryReq Header SessionID0x8C2A1B/SessionID /Header EnergyTransferModeDC_BPT/EnergyTransferMode ChargeProgressSTART/ChargeProgress /PowerDeliveryReqChargeProgress有三个取值START 表示开始充电STOP 表示正常停止STANDBY 表示暂停但不是错误退出。在 BPT 场景里STANDBY 经常被当成 STOP导致桩端把直流母线电压迅速泄放车辆如果还在并网状态就会触发接触器拉弧。解决方式是严格区分这两个语义STANDBY 只降功率不断开STOP 才执行断电序列。4.3 直流充电的绝缘检测与 Welding Detection直流大功率充电里物理安全比协议功能更重要。15118-20 延续了充电过程开始前的绝缘监测机制桩端在 PreCharge 之前要确认直流母线与车身地之间的绝缘电阻。车辆侧也要做 Welding Detection即检测充电接触器触头是否粘连如果粘连还执行功率传输会直接危害诊断人员安全。实际联调时这个检测流程在消息里是隐式的由车辆内部逻辑完成但桩端必须等待车辆上报 Ready 状态才能上电顺序不能反。5. 落地避坑清单15118-20 联调里最常翻车的 5 个环节5.1 SLAC 超时导致 EVCC 掉线上层消息一条都收不到现象EVCC 已经检测到 CP 信号变化但进入通信阶段后等待超时报 SLAC 失败。原因车和桩的 PLC 物理层虽然都支持 HomePlug Green PHY但 NMK 协商需要桩端在预设的时间内完成一些老桩固件对这个时序处理得很慢车端等不及就放弃了。解决先用厂商工具确认两边的 PLC 固件都开了 Green PHY 通道然后检查 SLAC 的匹配窗口有些桩默认只探测一组载波信道换了信道就没响应。开发阶段可以直接在桩端把 SLAC 重试次数调大比如从默认 3 次改到 10 次。5.2 SDP 响应永远不回来抓包又看不到重传现象SDP 请求发出去桩端没有任何响应UDP 端口 15118 上也没有数据回来。原因EVCC 发 SDP 用的链路本地地址跟桩端不在同一段。比如车机用了 fe80::1桩端是 fe80::2但两边不在同一个二层接口下或者中间有交换机隔离了 PLC 桥接的 VLAN。解决在 EV 侧抓一下发送的 SDP 包源地址确认它来自 PLC 网卡而不是 Wi-Fi 网卡。嵌入式平台上常见错误是绑定了错误的网络接口用ip addr show查看哪个接口有 HomePlug 的 MAC 前缀然后强制 socket 绑定到这个接口不要依赖默认路由。5.3 TLS 握手失败桩端日志提示证书验证失败现象TCP 连接建立成功TLS 握手卡在 Certificate 阶段桩端报 unknown CA 或者 bad certificate。原因EVCC 只发送了叶子证书没发送中间 CA或桩端本地信任库没有根证书或合同证书已经过期。还有一种隐蔽原因是桩端系统时间跑偏证书的 valid_from 是未来时间直接拒绝。解决先用 openssl 在 PC 上做一次完整的验证命令参考第 3.2 节再看桩端有没有提供导入 Root CA 的管理接口很多桩出厂预置的是旧版根证书15118-20 要求的新根证书需要单独导入。桩端时间校准也很容易被忽略建议联调前用一个统一 NTP 源对齐两边时间。5.4 BPT 模式下功率参数越界桩上报过流保护现象DC_BPT 模式下车辆请求放电功率 30 kW桩端启动后立刻触发过流保护。原因BPTMaximumDischargePowerLimit只代表车辆能力上限不等于桩端能接受的反向功率。桩端逆变器如果标称只能反向 20 kW超出了它的物理极限保护是必然的。解决在 ChargeParameterDiscovery 阶段车辆要读取桩端回发的 BPT 参数以两边的较小值作为最终限制。不要自作聪明在应用层做换算协议里已经给定单位直接比较大小取交集即可。5.5 充电状态消息里的 meter 读数对不上运营平台扣费异常现象充电结束后桩端上报的电量和车辆记录的充电量相差 5%运营平台对不上账。原因桩端 ChargingStatus 消息里的 MeterInfo 是直流母线侧的电能读数而车辆记录的是电池侧的实际充电量两者之间存在充电模块损耗、线缆损耗和 DC/DC 转换损耗。解决充电桩作为计费依据时必须使用桩端仪表读数车辆侧读数只能做参考不能直接参与扣费。联调时在协议层做好双方读数的比对记录损耗率超过 5% 就要检查直流母线上是不是串了额外负载。6. 最小验证环境仿真协议栈 Wireshark 就能验证 80% 的问题联调 15118-20 不一定必须立刻拿到真实车辆和真实充电桩。我现在的习惯是用一个模拟 EVCC 的轻量协议栈配合 Wireshark 做协议级验证先把 SDP、TLS、V2GTP 这些传输层问题排掉再上真车真桩能把调试周期压缩一半以上。验证环境由三部分组成一个模拟 EVCC 的脚本负责发 SDP、建 TLS、发 ChargeParameterDiscovery、一台真实桩端或桩端模拟器、以及抓包工具。最小流程是先把桩端模拟器跑起来然后运行模拟 EVCC观察三层现象第一层看 SDP 响应能否拿到端口第二层看 TLS 握手是否完成第三层看 ChargeParameterDiscovery 请求能否得到业务级响应。只要第三层通了消息协议的正确性就基本有保证剩下的是功率控制策略和硬件保护逻辑的问题。# 抓取 PLC 和以太网的通信过滤出 15118-20 相关的报文 # 在 EVCC 侧的网卡上执行 tshark -i eth0 \ -f port 15118 or tcp port 8401 or tcp port 8402 \ -w v2g20_test.pcap抓完包后重点看三处SDP Request 的版本号是否等于 2TLS ClientHello 里是否带 SNIV2GTP 头的长度字段是否跟实际负载一致。这三处全对协议栈 80% 的正误能确定。如果 SDP 版本号配错成 1桩端会按 15118-2 的协议去解析后续消息表现为 TCP 能连上但应用层一直没有响应。这个排查思路是某 A 同学在一次真实联调里总结出来的他当时排了两天才意识到是版本号写死在了配置里而不是按标准协商得来的。后来我也把这条经验固化下来凡是涉及版本号的字段一律不从常量读取而是用 V2GTP 头和 SDP 响应里的实际值动态推导。这样即使换了不同固件的桩也不会被版本号坑第二次。做 15118-20 最忌讳的就是按照 15118-2 的思维去猜协议。TCP 重传、证书链、双向功率参数每一层都值得先在仿真环境里跑通再拖着电源柜和整车上台架。希望这篇笔记能让你少走两趟弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑