Quantex GmbH
Votre région : Europe

DoIP (ISO 13400) Quantex

Diagnostic par IP

Dernière modification :

Description

ISO 13400 (DoIP — Diagnostics over IP) — norme de diagnostic des automobiles via Ethernet. Elle est utilisée dans les véhicules modernes pour le diagnostic à haute vitesse et la mise à jour du logiciel. ScanDoc prend en charge DoIP via un adaptateur Ethernet intégré.

Pour travailler avec DoIP, un adaptateur ScanDoc compatible Ethernet est requis (vérifiez-le avec le paramètre ETHERNET_NDIS_SUPPORTED dans GET_DEVICE_INFO).

Processus de connexion DoIP

  1. Ouvrez le canal ISO 13400 via PassThruConnect(ISO13400_PS)
  2. Recherchez les véhicules sur le réseau (ISO13400_DISCOVER_VEHICLES) ou indiquez l'adresse IP manuellement
  3. Obtenez les informations du véhicule trouvé (ISO13400_GET_VEHICLE_INFO)
  4. Configurez les paramètres DoIP via SET_CONFIG (adresses SA/TA, adresse IP de l'ECU)
  5. Établissez la connexion TCP (ISO13400_CONNECT_TCP)
  6. Activez le routage (ISO13400_ACTIVATE_ROUTING)
  7. Utilisez PassThruReadMsgs/PassThruWriteMsgs pour le diagnostic

Paramètres DoIP (SET_CONFIG)

Avant d'utiliser les commandes DoIP, il est nécessaire de configurer les paramètres via SET_CONFIG.

Paramètre Valeur Description Par défaut
ISO13400_SOURCE_ADDR 0x8100 Adresse logique du testeur (SA). Généralement 0x0E00-0x0EFF. 0x0E00
ISO13400_TARGET_ADDR 0x8101 Adresse logique de l'ECU (TA). Dépend du véhicule.
ISO13400_ECU_IP_ADDR 0x8102 Adresse IP de la passerelle/ECU (4 octets, big-endian)
ISO13400_ECU_TCP_PORT 0x8103 Port TCP pour la connexion 13400
ISO13400_T_TCP_INITIAL 0x8104 Délai d'inactivité avant routing activation (ms) 2000
ISO13400_T_TCP_GENERAL 0x8105 Délai d'inactivité TCP général (ms) 300000
ISO13400_T_DIAG_MSG 0x8106 Délai d'attente de la réponse de diagnostic (ms) 2000
ISO13400_ACTIVATION_TYPE 0x8107 Type d'activation du routage (0x00 — default, 0x01 — WWH-OBD, 0xE0 — Central Security) 0

Commandes DoIP

ISO13400_DISCOVER_VEHICLES — Recherche de véhicules

Envoie une requête UDP en diffusion pour détecter les véhicules compatibles DoIP sur le réseau local. Les résultats sont enregistrés dans un tampon interne et sont accessibles via ISO13400_GET_VEHICLE_INFO.

IoctlID 0x8110
pInput NULL
pOutput NULL

Exemple en C/C++

#include "j2534_dll.hpp"

unsigned long ChannelID;  // Obtenu de PassThruConnect pour ISO13400_PS
long ret;

ret = PassThruIoctl(ChannelID, ISO13400_DISCOVER_VEHICLES, NULL, NULL);
if (ret == STATUS_NOERROR)
{
    printf("Recherche terminée\n");
}

Exemple en Kotlin (Android)

val result = j2534.ptIoctl(channelID, ISO13400_DISCOVER_VEHICLES, 0, null)
if (result.status == STATUS_NOERROR) {
    Log.i("DoIP", "Recherche terminée")
}

Exemple en Python

ret = j2534.PassThruIoctl(channel_id, ISO13400_DISCOVER_VEHICLES, None, None)
if ret == 0:
    print("Recherche terminée")

Exemple en C#

int ret = J2534.PassThruIoctl(channelId, ISO13400_DISCOVER_VEHICLES, IntPtr.Zero, IntPtr.Zero);
if (ret == 0)
    Console.WriteLine("Recherche terminée");

ISO13400_GET_VEHICLE_INFO — Informations sur le véhicule

Renvoie les informations du véhicule trouvé : VIN, adresse logique, adresse IP de la passerelle. Appelée après ISO13400_DISCOVER_VEHICLES.

IoctlID 0x8111
pInput NULL
pOutput DOIP_VEHICLE_INFO* — structure contenant les informations du premier véhicule trouvé
typedef struct {
    char VIN[18];               // Code VIN (17 caractères + '\0')
    unsigned short LogicalAddr; // Adresse logique de la passerelle DoIP
    unsigned char EID[6];       // Entity Identification (adresse MAC)
    unsigned char GID[6];       // Group Identification
    unsigned char FurtherAction;// Code d'action supplémentaire (0x00 — aucune, 0x10 — routing activation requise)
    unsigned char SyncStatus;   // État de synchronisation VIN/GID (0x00 — synchronisé, 0x10 — non terminé)
    unsigned long IPAddr;       // Adresse IPv4 (big-endian)
} DOIP_VEHICLE_INFO;

Exemple en C/C++

#include "j2534_dll.hpp"

unsigned long ChannelID;  // Obtenu de PassThruConnect pour 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("Adresse logique : 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);
}

Exemple en Kotlin (Android)

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

Exemple en 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"Adresse logique : 0x{vehicle_info.LogicalAddr:04X}")
    print(f"IP: {(ip >> 24) & 0xFF}.{(ip >> 16) & 0xFF}.{(ip >> 8) & 0xFF}.{ip & 0xFF}")

Exemple en 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($"Adresse logique : 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 — Connexion TCP

Établit la connexion TCP avec la passerelle du véhicule. L'adresse IP et le port doivent avoir été préalablement configurés via SET_CONFIG ou obtenus depuis ISO13400_GET_VEHICLE_INFO.

IoctlID 0x8112
pInput NULL (utilise les paramètres de SET_CONFIG) ou DOIP_CONNECT_PARAMS*
pOutput NULL

Exemple en C/C++

#include "j2534_dll.hpp"

unsigned long ChannelID;  // Obtenu de PassThruConnect pour ISO13400
long ret;

// Les paramètres sont déjà configurés via SET_CONFIG
ret = PassThruIoctl(ChannelID, ISO13400_CONNECT_TCP, NULL, NULL);
if (ret == STATUS_NOERROR)
{
    printf("Connexion TCP établie\n");
}
else
{
    char error[256];
    PassThruGetLastError(error);
    printf("Erreur de connexion : %s\n", error);
}

Exemple en Kotlin (Android)

val result = j2534.ptIoctl(channelID, ISO13400_CONNECT_TCP, 0, null)
if (result.status == STATUS_NOERROR) {
    Log.i("DoIP", "Connexion TCP établie")
} else {
    Log.e("DoIP", "Erreur de connexion TCP : ${result.status}")
}

Exemple en Python

ret = j2534.PassThruIoctl(channel_id, ISO13400_CONNECT_TCP, None, None)
if ret == 0:
    print("Connexion TCP établie")
else:
    print(f"Erreur de connexion TCP : {ret}")

Exemple en C#

int ret = J2534.PassThruIoctl(channelId, ISO13400_CONNECT_TCP, IntPtr.Zero, IntPtr.Zero);
if (ret == 0)
    Console.WriteLine("Connexion TCP établie");
else
    Console.WriteLine($"Erreur de connexion TCP : {ret}");

ISO13400_ACTIVATE_ROUTING — Activation du routage

Envoie une requête Routing Activation pour obtenir l'accès au diagnostic. Le type d'activation est défini par le paramètre ISO13400_ACTIVATION_TYPE.

IoctlID 0x8113
pInput NULL ou unsigned long* — type d'activation (0 — default, 1 — WWH-OBD)
pOutput unsigned long* — code de réponse de la passerelle

Codes de réponse de 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

Exemple en 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("Routage activé avec succès\n");
    else
        printf("Code de réponse : 0x%02X\n", responseCode);
}

Exemple en Kotlin (Android)

val result = j2534.ptIoctl(channelID, ISO13400_ACTIVATE_ROUTING, 0, null)
if (result.status == STATUS_NOERROR) {
    if (result.outputValue == 0x10) {
        Log.i("DoIP", "Routage activé avec succès")
    } else {
        Log.w("DoIP", "Code de réponse : 0x${result.outputValue.toString(16)}")
    }
}

Exemple en 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("Routage activé avec succès")
    else:
        print(f"Code de réponse : 0x{response_code.value:02X}")

Exemple en 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("Routage activé avec succès");
    else
        Console.WriteLine($"Code de réponse : 0x{responseCode:X2}");
}

Codes d'erreur renvoyés

Code Description Causes possibles et solutions
STATUS_NOERROR Fonction exécutée avec succès
ERR_DEVICE_NOT_CONNECTED Pas de connexion avec l'adaptateur
  • L'adaptateur est éteint ou hors de portée
  • Solution : vérifiez l'alimentation et la connexion
ERR_NOT_SUPPORTED DoIP non pris en charge
  • L'adaptateur ne dispose pas d'interface Ethernet
  • Solution : vérifiez ETHERNET_NDIS_SUPPORTED via GET_DEVICE_INFO
ERR_TIMEOUT Délai d'attente de l'opération dépassé
  • Le véhicule ne répond pas aux requêtes DoIP
  • Adresse IP ou port incorrects
  • Solution : vérifiez la connexion réseau et les paramètres
ERR_INVALID_CHANNEL_ID Identifiant de canal non valide
  • ChannelID n'a pas été obtenu via PassThruConnect pour ISO13400
  • Solution : exécutez PassThruConnect avec le protocole ISO13400_PS
ERR_FAILED Erreur indéterminée
  • Erreur réseau ou refus de la passerelle
  • Solution : appelez PassThruGetLastError()

Exemple complet d'utilisation de DoIP

Exemple en C/C++

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

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

    // 1. Ouvrir le périphérique
    ret = PassThruOpen(NULL, &DeviceID);
    if (ret != STATUS_NOERROR) return ret;

    // 2. Vérifier la prise en charge d'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 non pris en charge\n");
        PassThruClose(DeviceID);
        return -1;
    }

    // 3. Ouvrir le canal ISO 13400
    ret = PassThruConnect(DeviceID, ISO13400_PS, 0, 0, &ChannelID);
    if (ret != STATUS_NOERROR)
    {
        PassThruClose(DeviceID);
        return ret;
    }

    // 4. Recherche de véhicules sur le réseau
    ret = PassThruIoctl(ChannelID, ISO13400_DISCOVER_VEHICLES, NULL, NULL);
    if (ret != STATUS_NOERROR)
    {
        printf("Erreur lors de la recherche de véhicules\n");
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }

    // 5. Obtenir les informations du véhicule trouvé
    DOIP_VEHICLE_INFO vehicleInfo;
    ret = PassThruIoctl(ChannelID, ISO13400_GET_VEHICLE_INFO, NULL, &vehicleInfo);
    if (ret != STATUS_NOERROR)
    {
        printf("Aucun véhicule trouvé\n");
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }
    printf("VIN: %s\n", vehicleInfo.VIN);

    // 6. Configurer les paramètres 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. Établir la connexion TCP
    ret = PassThruIoctl(ChannelID, ISO13400_CONNECT_TCP, NULL, NULL);
    if (ret != STATUS_NOERROR)
    {
        printf("Erreur de connexion TCP\n");
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }

    // 8. Activer le routage
    unsigned long activationType = 0;
    unsigned long responseCode = 0;
    ret = PassThruIoctl(ChannelID, ISO13400_ACTIVATE_ROUTING, &activationType, &responseCode);
    if (ret != STATUS_NOERROR || responseCode != 0x10)
    {
        printf("Erreur d'activation du routage : 0x%02X\n", responseCode);
        PassThruDisconnect(ChannelID);
        PassThruClose(DeviceID);
        return ret;
    }

    printf("Connexion DoIP établie !\n");

    // 9. On peut désormais envoyer des requêtes de diagnostic
    // via PassThruWriteMsgs / PassThruReadMsgs

    // Fermer la connexion
    PassThruDisconnect(ChannelID);
    PassThruClose(DeviceID);
    return 0;
}

Exemple en Kotlin (Android)

suspend fun connectDoIP(): Boolean {
    // 1. Ouvrir le périphérique
    val openResult = j2534.ptOpen(null)
    if (openResult.status != STATUS_NOERROR) return false
    val deviceID = openResult.deviceId

    // 2. Ouvrir le canal 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. Recherche de véhicules
    val discoverResult = j2534.ptIoctl(channelID, ISO13400_DISCOVER_VEHICLES, 0, null)
    if (discoverResult.status != STATUS_NOERROR) {
        Log.e("DoIP", "Erreur lors de la recherche de véhicules")
        j2534.ptDisconnect(channelID)
        j2534.ptClose(deviceID)
        return false
    }

    // 4. Obtenir les informations du véhicule trouvé
    val infoResult = j2534.ptGetVehicleInfo(channelID)
    if (infoResult.status != STATUS_NOERROR) {
        Log.e("DoIP", "Aucun véhicule trouvé")
        j2534.ptDisconnect(channelID)
        j2534.ptClose(deviceID)
        return false
    }
    Log.i("DoIP", "VIN: ${infoResult.vin}")

    // 5. Configurer les paramètres et se connecter
    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. Connexion TCP
    val tcpResult = j2534.ptIoctl(channelID, ISO13400_CONNECT_TCP, 0, null)
    if (tcpResult.status != STATUS_NOERROR) {
        Log.e("DoIP", "Erreur de connexion TCP")
        j2534.ptDisconnect(channelID)
        j2534.ptClose(deviceID)
        return false
    }

    // 7. Activation du routage
    val routingResult = j2534.ptIoctl(channelID, ISO13400_ACTIVATE_ROUTING, 0, null)
    if (routingResult.status != STATUS_NOERROR || routingResult.outputValue != 0x10) {
        Log.e("DoIP", "Erreur d'activation : 0x${routingResult.outputValue.toString(16)}")
        j2534.ptDisconnect(channelID)
        j2534.ptClose(deviceID)
        return false
    }

    Log.i("DoIP", "Connexion DoIP établie !")
    return true
}

Exemple en Python

from ctypes import *

def connect_doip():
    # 1. Ouvrir le périphérique
    device_id = c_ulong()
    ret = j2534.PassThruOpen(None, byref(device_id))
    if ret != 0:
        return False

    # 2. Ouvrir le canal 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. Recherche de véhicules
    ret = j2534.PassThruIoctl(channel_id, ISO13400_DISCOVER_VEHICLES, None, None)
    if ret != 0:
        print("Erreur lors de la recherche de véhicules")
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False

    # 4. Obtenir les informations du véhicule trouvé
    vehicle_info = DOIP_VEHICLE_INFO()
    ret = j2534.PassThruIoctl(channel_id, ISO13400_GET_VEHICLE_INFO, None, byref(vehicle_info))
    if ret != 0:
        print("Aucun véhicule trouvé")
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False
    print(f"VIN: {vehicle_info.VIN.decode()}")

    # 5. Configurer les paramètres 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. Connexion TCP
    ret = j2534.PassThruIoctl(channel_id, ISO13400_CONNECT_TCP, None, None)
    if ret != 0:
        print("Erreur de connexion TCP")
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False

    # 7. Activation du routage
    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"Erreur d'activation : 0x{response_code.value:02X}")
        j2534.PassThruDisconnect(channel_id)
        j2534.PassThruClose(device_id)
        return False

    print("Connexion DoIP établie !")
    return True

Exemple en C#

public bool ConnectDoIP()
{
    // 1. Ouvrir le périphérique
    uint deviceId;
    int ret = J2534.PassThruOpen(null, out deviceId);
    if (ret != 0) return false;

    // 2. Ouvrir le canal ISO 13400
    uint channelId;
    ret = J2534.PassThruConnect(deviceId, ISO13400_PS, 0, 0, out channelId);
    if (ret != 0)
    {
        J2534.PassThruClose(deviceId);
        return false;
    }

    // 3. Recherche de véhicules
    ret = J2534.PassThruIoctl(channelId, ISO13400_DISCOVER_VEHICLES, IntPtr.Zero, IntPtr.Zero);
    if (ret != 0)
    {
        Console.WriteLine("Erreur lors de la recherche de véhicules");
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }

    // 4. Obtenir les informations du véhicule trouvé
    DOIP_VEHICLE_INFO vehicleInfo;
    ret = J2534.PassThruIoctl(channelId, ISO13400_GET_VEHICLE_INFO, IntPtr.Zero, out vehicleInfo);
    if (ret != 0)
    {
        Console.WriteLine("Aucun véhicule trouvé");
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }
    Console.WriteLine($"VIN: {Encoding.ASCII.GetString(vehicleInfo.VIN).TrimEnd('\0')}");

    // 5. Configurer les paramètres 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. Connexion TCP
    ret = J2534.PassThruIoctl(channelId, ISO13400_CONNECT_TCP, IntPtr.Zero, IntPtr.Zero);
    if (ret != 0)
    {
        Console.WriteLine("Erreur de connexion TCP");
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }

    // 7. Activation du routage
    uint activationType = 0;
    uint responseCode;
    ret = J2534.PassThruIoctl(channelId, ISO13400_ACTIVATE_ROUTING, ref activationType, out responseCode);
    if (ret != 0 || responseCode != 0x10)
    {
        Console.WriteLine($"Erreur d'activation : 0x{responseCode:X2}");
        J2534.PassThruDisconnect(channelId);
        J2534.PassThruClose(deviceId);
        return false;
    }

    Console.WriteLine("Connexion DoIP établie !");
    return true;
}