Quantex GmbH
您的地区:欧洲

DoIP (ISO 13400) Quantex

基于 IP 的诊断

最后修改:

说明

ISO 13400(DoIP — Diagnostics over IP)是通过 Ethernet 进行汽车诊断的标准。它用于现代汽车的高速诊断和软件更新。ScanDoc 通过内置 Ethernet 适配器支持 DoIP。

使用 DoIP 需要具备 Ethernet 支持的 ScanDoc 适配器(请通过 GET_DEVICE_INFO 的 ETHERNET_NDIS_SUPPORTED 参数进行确认)。

DoIP 连接流程

  1. 通过 PassThruConnect(ISO13400_PS) 打开 ISO 13400 通道
  2. 在网络中搜索车辆(ISO13400_DISCOVER_VEHICLES)或手动指定 IP 地址
  3. 获取找到的车辆信息(ISO13400_GET_VEHICLE_INFO)
  4. 通过 SET_CONFIG 配置 DoIP 参数(SA/TA 地址、ECU 的 IP 地址)
  5. 建立 TCP 连接(ISO13400_CONNECT_TCP)
  6. 激活路由(ISO13400_ACTIVATE_ROUTING)
  7. 使用 PassThruReadMsgs/PassThruWriteMsgs 进行诊断

DoIP 参数(SET_CONFIG)

在使用 DoIP 命令前,必须通过 SET_CONFIG 配置相关参数。

参数 说明 默认值
ISO13400_SOURCE_ADDR 0x8100 测试仪的逻辑地址(SA)。通常为 0x0E00-0x0EFF。 0x0E00
ISO13400_TARGET_ADDR 0x8101 ECU 的逻辑地址(TA)。取决于具体车辆。
ISO13400_ECU_IP_ADDR 0x8102 网关/ECU 的 IP 地址(4 字节,big-endian)
ISO13400_ECU_TCP_PORT 0x8103 用于连接的 TCP 端口 13400
ISO13400_T_TCP_INITIAL 0x8104 routing activation 前的非活动超时(ms) 2000
ISO13400_T_TCP_GENERAL 0x8105 TCP 通用非活动超时(ms) 300000
ISO13400_T_DIAG_MSG 0x8106 等待诊断响应的超时(ms) 2000
ISO13400_ACTIVATION_TYPE 0x8107 路由激活类型(0x00 — default,0x01 — WWH-OBD,0xE0 — Central Security) 0

DoIP 命令

ISO13400_DISCOVER_VEHICLES — 搜索车辆

发送 UDP 广播请求,以发现局域网中支持 DoIP 的车辆。结果保存在内部缓冲区中,可通过 ISO13400_GET_VEHICLE_INFO 获取。

IoctlID 0x8110
pInput NULL
pOutput NULL

C/C++ 示例

#include "j2534_dll.hpp"

unsigned long ChannelID;  // 由 PassThruConnect 为 ISO13400_PS 返回
long ret;

ret = PassThruIoctl(ChannelID, ISO13400_DISCOVER_VEHICLES, NULL, NULL);
if (ret == STATUS_NOERROR)
{
    printf("搜索完成\n");
}

Kotlin (Android) 示例

val result = j2534.ptIoctl(channelID, ISO13400_DISCOVER_VEHICLES, 0, null)
if (result.status == STATUS_NOERROR) {
    Log.i("DoIP", "搜索完成")
}

Python 示例

ret = j2534.PassThruIoctl(channel_id, ISO13400_DISCOVER_VEHICLES, None, None)
if ret == 0:
    print("搜索完成")

C# 示例

int ret = J2534.PassThruIoctl(channelId, ISO13400_DISCOVER_VEHICLES, IntPtr.Zero, IntPtr.Zero);
if (ret == 0)
    Console.WriteLine("搜索完成");

ISO13400_GET_VEHICLE_INFO — 车辆信息

返回找到的车辆信息:VIN、逻辑地址、网关的 IP 地址。在 ISO13400_DISCOVER_VEHICLES 之后调用。

IoctlID 0x8111
pInput NULL
pOutput DOIP_VEHICLE_INFO* — 包含第一台找到的车辆信息的结构体
typedef struct {
    char VIN[18];               // VIN 码(17 个字符 + '\0')
    unsigned short LogicalAddr; // DoIP 网关的逻辑地址
    unsigned char EID[6];       // Entity Identification(MAC 地址)
    unsigned char GID[6];       // Group Identification
    unsigned char FurtherAction;// 后续操作代码(0x00 — 无,0x10 — 需要 routing activation)
    unsigned char SyncStatus;   // VIN/GID 同步状态(0x00 — 已同步,0x10 — 未完成)
    unsigned long IPAddr;       // IPv4 地址(big-endian)
} DOIP_VEHICLE_INFO;

C/C++ 示例

#include "j2534_dll.hpp"

unsigned long ChannelID;  // 由 PassThruConnect 为 ISO13400_PS 返回
DOIP_VEHICLE_INFO vehicleInfo;
long ret;

ret = PassThruIoctl(ChannelID, ISO13400_GET_VEHICLE_INFO, NULL, &vehicleInfo);
if (ret == STATUS_NOERROR)
{
    printf("VIN: %s\n", vehicleInfo.VIN);
    printf("逻辑地址: 0x%04X\n", vehicleInfo.LogicalAddr);
    printf("IP: %d.%d.%d.%d\n",
           (vehicleInfo.IPAddr >> 24) & 0xFF,
           (vehicleInfo.IPAddr >> 16) & 0xFF,
           (vehicleInfo.IPAddr >> 8) & 0xFF,
           vehicleInfo.IPAddr & 0xFF);
}

Kotlin (Android) 示例

val result = j2534.ptGetVehicleInfo(channelID)
if (result.status == STATUS_NOERROR) {
    Log.i("DoIP", "VIN: ${result.vin}")
    Log.i("DoIP", "逻辑地址: 0x${result.logicalAddr.toString(16).uppercase()}")
    Log.i("DoIP", "IP: ${result.ipAddrString}")
}

Python 示例

from ctypes import *

class DOIP_VEHICLE_INFO(Structure):
    _fields_ = [
        ("VIN", c_char * 18),
        ("LogicalAddr", c_ushort),
        ("EID", c_ubyte * 6),
        ("GID", c_ubyte * 6),
        ("FurtherAction", c_ubyte),
        ("SyncStatus", c_ubyte),
        ("IPAddr", c_ulong)
    ]

vehicle_info = DOIP_VEHICLE_INFO()

ret = j2534.PassThruIoctl(channel_id, ISO13400_GET_VEHICLE_INFO, None, byref(vehicle_info))
if ret == 0:
    ip = vehicle_info.IPAddr
    print(f"VIN: {vehicle_info.VIN.decode()}")
    print(f"逻辑地址: 0x{vehicle_info.LogicalAddr:04X}")
    print(f"IP: {(ip >> 24) & 0xFF}.{(ip >> 16) & 0xFF}.{(ip >> 8) & 0xFF}.{ip & 0xFF}")

C# 示例

[StructLayout(LayoutKind.Sequential)]
public struct DOIP_VEHICLE_INFO {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)]
    public byte[] VIN;
    public ushort LogicalAddr;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] EID;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] GID;
    public byte FurtherAction;
    public byte SyncStatus;
    public uint IPAddr;
}

DOIP_VEHICLE_INFO vehicleInfo;
int ret = J2534.PassThruIoctl(channelId, ISO13400_GET_VEHICLE_INFO, IntPtr.Zero, out vehicleInfo);
if (ret == 0)
{
    Console.WriteLine($"VIN: {Encoding.ASCII.GetString(vehicleInfo.VIN).TrimEnd('\0')}");
    Console.WriteLine($"逻辑地址: 0x{vehicleInfo.LogicalAddr:X4}");
    var ip = vehicleInfo.IPAddr;
    Console.WriteLine($"IP: {(ip >> 24) & 0xFF}.{(ip >> 16) & 0xFF}.{(ip >> 8) & 0xFF}.{ip & 0xFF}");
}

ISO13400_CONNECT_TCP — TCP 连接

与车辆网关建立 TCP 连接。IP 地址和端口必须事先通过 SET_CONFIG 配置,或从 ISO13400_GET_VEHICLE_INFO 获取。

IoctlID 0x8112
pInput NULL(使用 SET_CONFIG 中的参数)或 DOIP_CONNECT_PARAMS*
pOutput NULL

C/C++ 示例

#include "j2534_dll.hpp"

unsigned long ChannelID;  // 由 PassThruConnect 为 ISO13400 返回
long ret;

// 参数已通过 SET_CONFIG 配置
ret = PassThruIoctl(ChannelID, ISO13400_CONNECT_TCP, NULL, NULL);
if (ret == STATUS_NOERROR)
{
    printf("TCP 连接已建立\n");
}
else
{
    char error[256];
    PassThruGetLastError(error);
    printf("连接错误: %s\n", error);
}

Kotlin (Android) 示例

val result = j2534.ptIoctl(channelID, ISO13400_CONNECT_TCP, 0, null)
if (result.status == STATUS_NOERROR) {
    Log.i("DoIP", "TCP 连接已建立")
} else {
    Log.e("DoIP", "TCP 连接错误: ${result.status}")
}

Python 示例

ret = j2534.PassThruIoctl(channel_id, ISO13400_CONNECT_TCP, None, None)
if ret == 0:
    print("TCP 连接已建立")
else:
    print(f"TCP 连接错误: {ret}")

C# 示例

int ret = J2534.PassThruIoctl(channelId, ISO13400_CONNECT_TCP, IntPtr.Zero, IntPtr.Zero);
if (ret == 0)
    Console.WriteLine("TCP 连接已建立");
else
    Console.WriteLine($"TCP 连接错误: {ret}");

ISO13400_ACTIVATE_ROUTING — 激活路由

发送 Routing Activation 请求,以获得诊断访问权限。激活类型由 ISO13400_ACTIVATION_TYPE 参数指定。

IoctlID 0x8113
pInput NULL 或 unsigned long* — 激活类型(0 — default,1 — WWH-OBD)
pOutput unsigned long* — 来自网关的响应代码

Routing Activation 响应代码(ISO 13400-2, Table 25)

0x00 Routing activation denied — Unknown source address
0x01 Routing activation denied — All TCP_DATA sockets registered and active
0x02 Routing activation denied — Different SA on already activated socket
0x03 Routing activation denied — SA already registered on different socket
0x04 Routing activation denied — Missing authentication
0x05 Routing activation denied — Rejected confirmation
0x06 Routing activation denied — Unsupported routing activation type
0x07 Routing activation denied — Requires TLS socket
0x10 Routing successfully activated
0x11 Routing will be activated — confirmation required

C/C++ 示例

#include "j2534_dll.hpp"

unsigned long ChannelID;
unsigned long activationType = 0;  // Default
unsigned long responseCode = 0;
long ret;

ret = PassThruIoctl(ChannelID, ISO13400_ACTIVATE_ROUTING, &activationType, &responseCode);
if (ret == STATUS_NOERROR)
{
    if (responseCode == 0x10)
        printf("路由激活成功\n");
    else
        printf("响应代码: 0x%02X\n", responseCode);
}

Kotlin (Android) 示例

val result = j2534.ptIoctl(channelID, ISO13400_ACTIVATE_ROUTING, 0, null)
if (result.status == STATUS_NOERROR) {
    if (result.outputValue == 0x10) {
        Log.i("DoIP", "路由激活成功")
    } else {
        Log.w("DoIP", "响应代码: 0x${result.outputValue.toString(16)}")
    }
}

Python 示例

from ctypes import *

activation_type = c_ulong(0)  # Default
response_code = c_ulong(0)

ret = j2534.PassThruIoctl(channel_id, ISO13400_ACTIVATE_ROUTING, byref(activation_type), byref(response_code))
if ret == 0:
    if response_code.value == 0x10:
        print("路由激活成功")
    else:
        print(f"响应代码: 0x{response_code.value:02X}")

C# 示例

uint activationType = 0;  // Default
uint responseCode;
int ret = J2534.PassThruIoctl(channelId, ISO13400_ACTIVATE_ROUTING, ref activationType, out responseCode);
if (ret == 0)
{
    if (responseCode == 0x10)
        Console.WriteLine("路由激活成功");
    else
        Console.WriteLine($"响应代码: 0x{responseCode:X2}");
}

返回的错误代码

代码 说明 可能的原因和解决方法
STATUS_NOERROR 函数执行成功
ERR_DEVICE_NOT_CONNECTED 与适配器无连接
  • 适配器已关闭或超出连接范围
  • 解决方法:检查电源和连接
ERR_NOT_SUPPORTED 不支持 DoIP
  • 适配器没有 Ethernet 接口
  • 解决方法:通过 GET_DEVICE_INFO 检查 ETHERNET_NDIS_SUPPORTED
ERR_TIMEOUT 操作超时
  • 车辆不响应 DoIP 请求
  • IP 地址或端口不正确
  • 解决方法:检查网络连接和参数
ERR_INVALID_CHANNEL_ID 无效的通道标识符
  • ChannelID 未通过 ISO13400 的 PassThruConnect 获取
  • 解决方法:使用 ISO13400_PS 协议执行 PassThruConnect
ERR_FAILED 未定义的错误
  • 网络错误或网关拒绝
  • 解决方法:调用 PassThruGetLastError()

DoIP 完整使用示例

C/C++ 示例

#include "j2534_dll.hpp"
#include <stdio.h>

int DoIPDiagnosticSession(void)
{
    unsigned long DeviceID, ChannelID;
    long ret;

    // 1. 打开设备
    ret = PassThruOpen(NULL, &DeviceID);
    if (ret != STATUS_NOERROR) return ret;

    // 2. 检查 Ethernet 支持
    SCONFIG cfg[1];
    SCONFIG_LIST cfgList = {1, cfg};
    cfg[0].Parameter = ETHERNET_NDIS_SUPPORTED;
    ret = PassThruIoctl(DeviceID, GET_DEVICE_INFO, &cfgList, NULL);
    if (ret != STATUS_NOERROR || cfg[0].Value == 0)
    {
        printf("不支持 DoIP\n");
        PassThruClose(DeviceID);
        return -1;
    }

    // 3. 打开 ISO 13400 通道
    ret = PassThruConnect(DeviceID, ISO13400_PS, 0, 0, &ChannelID);
    if (ret != STATUS_NOERROR)
    {
        PassThruClose(DeviceID);
        return ret;
    }

    // 4. 在网络中搜索车辆
    ret = PassThruIoctl(ChannelID, ISO13400_DISCOVER_VEHICLES, NULL, NULL);
    if (ret != STATUS_NOERROR)
    {
        printf("车辆搜索错误\n");
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }

    // 5. 获取找到的车辆信息
    DOIP_VEHICLE_INFO vehicleInfo;
    ret = PassThruIoctl(ChannelID, ISO13400_GET_VEHICLE_INFO, NULL, &vehicleInfo);
    if (ret != STATUS_NOERROR)
    {
        printf("未找到车辆\n");
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }
    printf("VIN: %s\n", vehicleInfo.VIN);

    // 6. 配置 DoIP 参数
    SCONFIG doipCfg[3];
    SCONFIG_LIST doipCfgList = {3, doipCfg};
    doipCfg[0].Parameter = ISO13400_SOURCE_ADDR;
    doipCfg[0].Value = 0x0E00;
    doipCfg[1].Parameter = ISO13400_TARGET_ADDR;
    doipCfg[1].Value = vehicleInfo.LogicalAddr;
    doipCfg[2].Parameter = ISO13400_ECU_IP_ADDR;
    doipCfg[2].Value = vehicleInfo.IPAddr;

    ret = PassThruIoctl(ChannelID, SET_CONFIG, &doipCfgList, NULL);
    if (ret != STATUS_NOERROR)
    {
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }

    // 7. 建立 TCP 连接
    ret = PassThruIoctl(ChannelID, ISO13400_CONNECT_TCP, NULL, NULL);
    if (ret != STATUS_NOERROR)
    {
        printf("TCP 连接错误\n");
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }

    // 8. 激活路由
    unsigned long activationType = 0;
    unsigned long responseCode = 0;
    ret = PassThruIoctl(ChannelID, ISO13400_ACTIVATE_ROUTING, &activationType, &responseCode);
    if (ret != STATUS_NOERROR || responseCode != 0x10)
    {
        printf("路由激活错误: 0x%02X\n", responseCode);
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }

    printf("DoIP 连接已建立!\n");

    // 9. 现在可以通过 PassThruWriteMsgs / PassThruReadMsgs
    // 发送诊断请求

    // 关闭连接
    PassThruDisconnect(ChannelID);
    PassThruClose(DeviceID);
    return 0;
}

Kotlin (Android) 示例

suspend fun connectDoIP(): Boolean {
    // 1. 打开设备
    val openResult = j2534.ptOpen(null)
    if (openResult.status != STATUS_NOERROR) return false
    val deviceID = openResult.deviceId

    // 2. 打开 ISO 13400 通道
    val connectResult = j2534.ptConnect(deviceID, ISO13400_PS, 0u, 0u)
    if (connectResult.status != STATUS_NOERROR) {
        j2534.ptClose(deviceID)
        return false
    }
    val channelID = connectResult.channelId

    // 3. 搜索车辆
    val discoverResult = j2534.ptIoctl(channelID, ISO13400_DISCOVER_VEHICLES, 0, null)
    if (discoverResult.status != STATUS_NOERROR) {
        Log.e("DoIP", "车辆搜索错误")
        j2534.ptDisconnect(channelID)
        j2534.ptClose(deviceID)
        return false
    }

    // 4. 获取找到的车辆信息
    val infoResult = j2534.ptGetVehicleInfo(channelID)
    if (infoResult.status != STATUS_NOERROR) {
        Log.e("DoIP", "未找到车辆")
        j2534.ptDisconnect(channelID)
        j2534.ptClose(deviceID)
        return false
    }
    Log.i("DoIP", "VIN: ${infoResult.vin}")

    // 5. 配置参数并连接
    val params = listOf(
        PtConfig(ISO13400_SOURCE_ADDR, 0x0E00u),
        PtConfig(ISO13400_TARGET_ADDR, infoResult.logicalAddr.toUInt()),
        PtConfig(ISO13400_ECU_IP_ADDR, infoResult.ipAddr)
    )
    j2534.ptIoctl(channelID, SET_CONFIG, params.size, params.toByteArray())

    // 6. TCP 连接
    val tcpResult = j2534.ptIoctl(channelID, ISO13400_CONNECT_TCP, 0, null)
    if (tcpResult.status != STATUS_NOERROR) {
        Log.e("DoIP", "TCP 连接错误")
        j2534.ptDisconnect(channelID)
        j2534.ptClose(deviceID)
        return false
    }

    // 7. 激活路由
    val routingResult = j2534.ptIoctl(channelID, ISO13400_ACTIVATE_ROUTING, 0, null)
    if (routingResult.status != STATUS_NOERROR || routingResult.outputValue != 0x10) {
        Log.e("DoIP", "激活错误: 0x${routingResult.outputValue.toString(16)}")
        j2534.ptDisconnect(channelID)
        j2534.ptClose(deviceID)
        return false
    }

    Log.i("DoIP", "DoIP 连接已建立!")
    return true
}

Python 示例

from ctypes import *

def connect_doip():
    # 1. 打开设备
    device_id = c_ulong()
    ret = j2534.PassThruOpen(None, byref(device_id))
    if ret != 0:
        return False

    # 2. 打开 ISO 13400 通道
    channel_id = c_ulong()
    ret = j2534.PassThruConnect(device_id, ISO13400_PS, 0, 0, byref(channel_id))
    if ret != 0:
        j2534.PassThruClose(device_id)
        return False

    # 3. 搜索车辆
    ret = j2534.PassThruIoctl(channel_id, ISO13400_DISCOVER_VEHICLES, None, None)
    if ret != 0:
        print("车辆搜索错误")
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False

    # 4. 获取找到的车辆信息
    vehicle_info = DOIP_VEHICLE_INFO()
    ret = j2534.PassThruIoctl(channel_id, ISO13400_GET_VEHICLE_INFO, None, byref(vehicle_info))
    if ret != 0:
        print("未找到车辆")
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False
    print(f"VIN: {vehicle_info.VIN.decode()}")

    # 5. 配置 DoIP 参数
    config = (SCONFIG * 3)()
    config[0].Parameter = ISO13400_SOURCE_ADDR
    config[0].Value = 0x0E00
    config[1].Parameter = ISO13400_TARGET_ADDR
    config[1].Value = vehicle_info.LogicalAddr
    config[2].Parameter = ISO13400_ECU_IP_ADDR
    config[2].Value = vehicle_info.IPAddr

    config_list = SCONFIG_LIST()
    config_list.NumOfParams = 3
    config_list.ConfigPtr = config

    ret = j2534.PassThruIoctl(channel_id, SET_CONFIG, byref(config_list), None)
    if ret != 0:
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False

    # 6. TCP 连接
    ret = j2534.PassThruIoctl(channel_id, ISO13400_CONNECT_TCP, None, None)
    if ret != 0:
        print("TCP 连接错误")
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False

    # 7. 激活路由
    activation_type = c_ulong(0)
    response_code = c_ulong(0)
    ret = j2534.PassThruIoctl(channel_id, ISO13400_ACTIVATE_ROUTING, byref(activation_type), byref(response_code))
    if ret != 0 or response_code.value != 0x10:
        print(f"激活错误: 0x{response_code.value:02X}")
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False

    print("DoIP 连接已建立!")
    return True

C# 示例

public bool ConnectDoIP()
{
    // 1. 打开设备
    uint deviceId;
    int ret = J2534.PassThruOpen(null, out deviceId);
    if (ret != 0) return false;

    // 2. 打开 ISO 13400 通道
    uint channelId;
    ret = J2534.PassThruConnect(deviceId, ISO13400_PS, 0, 0, out channelId);
    if (ret != 0)
    {
        J2534.PassThruClose(deviceId);
        return false;
    }

    // 3. 搜索车辆
    ret = J2534.PassThruIoctl(channelId, ISO13400_DISCOVER_VEHICLES, IntPtr.Zero, IntPtr.Zero);
    if (ret != 0)
    {
        Console.WriteLine("车辆搜索错误");
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }

    // 4. 获取找到的车辆信息
    DOIP_VEHICLE_INFO vehicleInfo;
    ret = J2534.PassThruIoctl(channelId, ISO13400_GET_VEHICLE_INFO, IntPtr.Zero, out vehicleInfo);
    if (ret != 0)
    {
        Console.WriteLine("未找到车辆");
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }
    Console.WriteLine($"VIN: {Encoding.ASCII.GetString(vehicleInfo.VIN).TrimEnd('\0')}");

    // 5. 配置 DoIP 参数
    var configs = new SCONFIG[3];
    configs[0] = new SCONFIG { Parameter = ISO13400_SOURCE_ADDR, Value = 0x0E00 };
    configs[1] = new SCONFIG { Parameter = ISO13400_TARGET_ADDR, Value = vehicleInfo.LogicalAddr };
    configs[2] = new SCONFIG { Parameter = ISO13400_ECU_IP_ADDR, Value = vehicleInfo.IPAddr };

    var configList = new SCONFIG_LIST { NumOfParams = 3, ConfigPtr = configs };
    ret = J2534.PassThruIoctl(channelId, SET_CONFIG, ref configList, IntPtr.Zero);
    if (ret != 0)
    {
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }

    // 6. TCP 连接
    ret = J2534.PassThruIoctl(channelId, ISO13400_CONNECT_TCP, IntPtr.Zero, IntPtr.Zero);
    if (ret != 0)
    {
        Console.WriteLine("TCP 连接错误");
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }

    // 7. 激活路由
    uint activationType = 0;
    uint responseCode;
    ret = J2534.PassThruIoctl(channelId, ISO13400_ACTIVATE_ROUTING, ref activationType, out responseCode);
    if (ret != 0 || responseCode != 0x10)
    {
        Console.WriteLine($"激活错误: 0x{responseCode:X2}");
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }

    Console.WriteLine("DoIP 连接已建立!");
    return true;
}