>
Quantex GmbH
DE RU EN EL
Ваш регион: Европа

DoIP (ISO 13400) ScanDoc

Diagnostics over IP

Последнее изменение:

Описание

ISO 13400 (DoIP — Diagnostics over IP) — стандарт диагностики автомобилей через Ethernet. Используется в современных автомобилях для высокоскоростной диагностики и обновления ПО. ScanDoc поддерживает DoIP через встроенный Ethernet-адаптер.

Для работы с DoIP необходим адаптер ScanDoc с поддержкой Ethernet (проверьте через GET_DEVICE_INFO параметр ETHERNET_NDIS_SUPPORTED).

Процесс подключения DoIP

  1. Настройте параметры DoIP через SET_CONFIG
  2. Выполните поиск автомобилей в сети (ISO13400_DISCOVER_VEHICLES)
  3. Получите информацию о найденных автомобилях (ISO13400_GET_VEHICLE_INFO)
  4. Установите TCP-соединение (ISO13400_CONNECT_TCP)
  5. Активируйте маршрутизацию (ISO13400_ACTIVATE_ROUTING)
  6. Используйте PassThruReadMsgs/PassThruWriteMsgs для диагностики

Параметры DoIP (SET_CONFIG)

Перед использованием команд DoIP необходимо настроить параметры через SET_CONFIG.

Параметр Значение Описание По умолчанию
ISO13400_SOURCE_ADDR 0x8100 Логический адрес тестера (SA). Обычно 0x0E00-0x0EFF. 0x0E00
ISO13400_TARGET_ADDR 0x8101 Логический адрес ЭБУ (TA). Зависит от автомобиля.
ISO13400_ECU_IP_ADDR 0x8102 IP-адрес шлюза/ЭБУ (4 байта, big-endian)
ISO13400_ECU_TCP_PORT 0x8103 TCP-порт для подключения 13400
ISO13400_T_TCP_INITIAL 0x8104 Таймаут неактивности до routing activation (мс) 2000
ISO13400_T_TCP_GENERAL 0x8105 Общий таймаут неактивности TCP (мс) 300000
ISO13400_T_DIAG_MSG 0x8106 Таймаут ожидания диагностического ответа (мс) 2000
ISO13400_ACTIVATION_TYPE 0x8107 Тип активации маршрутизации (0 — default, 1 — WWH-OBD) 0

Команды DoIP

ISO13400_DISCOVER_VEHICLES — Поиск автомобилей

Отправляет широковещательный UDP-запрос для обнаружения автомобилей с DoIP в локальной сети. Результаты сохраняются во внутреннем буфере и доступны через ISO13400_GET_VEHICLE_INFO.

IoctlID 0x8110
pInput NULL или unsigned long* — таймаут поиска (мс)
pOutput unsigned long* — количество найденных автомобилей

Пример на C/C++

#include "j2534_dll.hpp"

unsigned long DeviceID;  // Получен от PassThruOpen
unsigned long timeout = 3000;  // 3 секунды на поиск
unsigned long vehicleCount = 0;
long ret;

ret = PassThruIoctl(DeviceID, ISO13400_DISCOVER_VEHICLES, &timeout, &vehicleCount);
if (ret == STATUS_NOERROR)
{
    printf("Найдено автомобилей: %lu\n", vehicleCount);
}

Пример на Kotlin (Android)

val result = j2534.ptIoctl(deviceID, ISO13400_DISCOVER_VEHICLES, 3000, null)
if (result.status == STATUS_NOERROR) {
    Log.i("DoIP", "Найдено автомобилей: ${result.outputValue}")
}

Пример на Python

from ctypes import *

timeout = c_ulong(3000)  # 3 секунды
vehicle_count = c_ulong(0)

ret = j2534.PassThruIoctl(device_id, ISO13400_DISCOVER_VEHICLES, byref(timeout), byref(vehicle_count))
if ret == 0:
    print(f"Найдено автомобилей: {vehicle_count.value}")

Пример на C#

uint timeout = 3000;
uint vehicleCount;
int ret = J2534.PassThruIoctl(deviceId, ISO13400_DISCOVER_VEHICLES, ref timeout, out vehicleCount);
if (ret == 0)
    Console.WriteLine($"Найдено автомобилей: {vehicleCount}");

ISO13400_GET_VEHICLE_INFO — Информация об автомобиле

Возвращает информацию о найденном автомобиле: VIN, логический адрес, IP-адрес шлюза. Вызывается после ISO13400_DISCOVER_VEHICLES.

IoctlID 0x8111
pInput unsigned long* — индекс автомобиля (0..N-1)
pOutput DOIP_VEHICLE_INFO* — структура с информацией
typedef struct {
    char VIN[18];              // VIN-код (17 символов + '\0')
    unsigned short LogicalAddr; // Логический адрес шлюза
    unsigned char GID[6];       // Group Identification (опционально)
    unsigned char EID[6];       // Entity Identification (MAC-адрес)
    unsigned long IPAddr;       // IP-адрес (big-endian)
} DOIP_VEHICLE_INFO;

Пример на C/C++

#include "j2534_dll.hpp"

unsigned long DeviceID;
unsigned long index = 0;  // Первый найденный автомобиль
DOIP_VEHICLE_INFO vehicleInfo;
long ret;

ret = PassThruIoctl(DeviceID, ISO13400_GET_VEHICLE_INFO, &index, &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(deviceID, 0)  // Первый автомобиль
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),
        ("GID", c_ubyte * 6),
        ("EID", c_ubyte * 6),
        ("IPAddr", c_ulong)
    ]

index = c_ulong(0)
vehicle_info = DOIP_VEHICLE_INFO()

ret = j2534.PassThruIoctl(device_id, ISO13400_GET_VEHICLE_INFO, byref(index), 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[] GID;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] EID;
    public uint IPAddr;
}

uint index = 0;
DOIP_VEHICLE_INFO vehicleInfo;
int ret = J2534.PassThruIoctl(deviceId, ISO13400_GET_VEHICLE_INFO, ref index, 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

0x00 Routing activation denied — Unknown source address
0x01 Routing activation denied — No available socket
0x02 Routing activation denied — Socket already active
0x03 Routing activation denied — Missing authentication
0x04 Routing activation denied — Rejected confirmation
0x05 Routing activation denied — Unsupported activation type
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-интерфейса
  • Решение: проверьте ETHERNET_NDIS_SUPPORTED через GET_DEVICE_INFO
ERR_TIMEOUT Таймаут операции
  • Автомобиль не отвечает на запросы DoIP
  • Неправильный IP-адрес или порт
  • Решение: проверьте сетевое подключение и параметры
ERR_INVALID_CHANNEL_ID Недействительный идентификатор канала
  • ChannelID не был получен через PassThruConnect для ISO13400
  • Решение: выполните PassThruConnect с протоколом ISO13400_PS
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. Поиск автомобилей
    unsigned long timeout = 3000;
    unsigned long vehicleCount = 0;
    ret = PassThruIoctl(DeviceID, ISO13400_DISCOVER_VEHICLES, &timeout, &vehicleCount);
    if (ret != STATUS_NOERROR || vehicleCount == 0)
    {
        printf("Автомобили не найдены\n");
        PassThruClose(DeviceID);
        return -1;
    }
    printf("Найдено автомобилей: %lu\n", vehicleCount);

    // 4. Получаем информацию о первом автомобиле
    unsigned long index = 0;
    DOIP_VEHICLE_INFO vehicleInfo;
    ret = PassThruIoctl(DeviceID, ISO13400_GET_VEHICLE_INFO, &index, &vehicleInfo);
    if (ret != STATUS_NOERROR)
    {
        PassThruClose(DeviceID);
        return ret;
    }
    printf("VIN: %s\n", vehicleInfo.VIN);

    // 5. Настраиваем параметры 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;

    // 6. Открываем канал ISO 13400
    ret = PassThruConnect(DeviceID, ISO13400_PS, 0, 0, &ChannelID);
    if (ret != STATUS_NOERROR)
    {
        PassThruClose(DeviceID);
        return ret;
    }

    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. Поиск автомобилей
    val discoverResult = j2534.ptIoctl(deviceID, ISO13400_DISCOVER_VEHICLES, 3000, null)
    if (discoverResult.status != STATUS_NOERROR || discoverResult.outputValue == 0) {
        Log.e("DoIP", "Автомобили не найдены")
        j2534.ptClose(deviceID)
        return false
    }
    Log.i("DoIP", "Найдено автомобилей: ${discoverResult.outputValue}")

    // 3. Получаем информацию о первом автомобиле
    val infoResult = j2534.ptGetVehicleInfo(deviceID, 0)
    if (infoResult.status != STATUS_NOERROR) {
        j2534.ptClose(deviceID)
        return false
    }
    Log.i("DoIP", "VIN: ${infoResult.vin}")

    // 4. Открываем канал и настраиваем параметры
    val connectResult = j2534.ptConnect(deviceID, ISO13400_PS, 0u, 0u)
    if (connectResult.status != STATUS_NOERROR) {
        j2534.ptClose(deviceID)
        return false
    }
    val channelID = connectResult.channelId

    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())

    // 5. 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
    }

    // 6. Активация маршрутизации
    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. Поиск автомобилей
    timeout = c_ulong(3000)
    vehicle_count = c_ulong(0)
    ret = j2534.PassThruIoctl(device_id, ISO13400_DISCOVER_VEHICLES, byref(timeout), byref(vehicle_count))
    if ret != 0 or vehicle_count.value == 0:
        print("Автомобили не найдены")
        j2534.PassThruClose(device_id)
        return False
    print(f"Найдено автомобилей: {vehicle_count.value}")

    # 3. Получаем информацию о первом автомобиле
    index = c_ulong(0)
    vehicle_info = DOIP_VEHICLE_INFO()
    ret = j2534.PassThruIoctl(device_id, ISO13400_GET_VEHICLE_INFO, byref(index), byref(vehicle_info))
    if ret != 0:
        j2534.PassThruClose(device_id)
        return False
    print(f"VIN: {vehicle_info.VIN.decode()}")

    # 4. Открываем канал 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

    # 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. Поиск автомобилей
    uint timeout = 3000;
    uint vehicleCount;
    ret = J2534.PassThruIoctl(deviceId, ISO13400_DISCOVER_VEHICLES, ref timeout, out vehicleCount);
    if (ret != 0 || vehicleCount == 0)
    {
        Console.WriteLine("Автомобили не найдены");
        J2534.PassThruClose(deviceId);
        return false;
    }
    Console.WriteLine($"Найдено автомобилей: {vehicleCount}");

    // 3. Получаем информацию о первом автомобиле
    uint index = 0;
    DOIP_VEHICLE_INFO vehicleInfo;
    ret = J2534.PassThruIoctl(deviceId, ISO13400_GET_VEHICLE_INFO, ref index, out vehicleInfo);
    if (ret != 0)
    {
        J2534.PassThruClose(deviceId);
        return false;
    }
    Console.WriteLine($"VIN: {Encoding.ASCII.GetString(vehicleInfo.VIN).TrimEnd('\0')}");

    // 4. Открываем канал ISO 13400
    uint channelId;
    ret = J2534.PassThruConnect(deviceId, ISO13400_PS, 0, 0, out channelId);
    if (ret != 0)
    {
        J2534.PassThruClose(deviceId);
        return false;
    }

    // 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;
}