Skip to content

Vehicle communication

Communication with ECUs in a vehicle can take place in several different ways, depending on the communication protocol used. This page lists examples of different communication protocols that are supported by openOBD. These examples use the openobd Python module. For more extensive background information on implementing these examples in a different programming language, please see the reference documentation on vehicle communication.

ISO-TP communication

The example below demonstrates how a VIN can be read from a vehicle using the ISO-TP protocol. This is done by configuring the required bus, specifying an ECU, and starting a stream to communicate with that ECU.

python -m openobd run --file academy/vehicle_communication/isotp.py --ticket <TICKET_NR>

from openobd import *


class IsotpCommunicationExample(OpenOBDFunction):

    def run(self):
        # Define two buses with the ISO-TP protocol
        print("Configuring buses...")
        bus_configs = [
            (BusConfiguration(bus_name="bus_6_14",
                              can_bus=CanBus(pin_plus=6, pin_min=14, can_protocol=CanProtocol.CAN_PROTOCOL_ISOTP,
                                             can_bit_rate=CanBitRate.CAN_BIT_RATE_500,
                                             transceiver=TransceiverSpeed.TRANSCEIVER_SPEED_HIGH))),
            (BusConfiguration(bus_name="bus_3_11",
                              can_bus=CanBus(pin_plus=3, pin_min=11, can_protocol=CanProtocol.CAN_PROTOCOL_ISOTP,
                                             can_bit_rate=CanBitRate.CAN_BIT_RATE_500,
                                             transceiver=TransceiverSpeed.TRANSCEIVER_SPEED_HIGH)))
        ]

        # Set the bus configuration
        set_bus_configuration(self.openobd_session, bus_configs)
        print("Buses have been configured.")

        # Define the engine control module, which is present on bus pins 6, 14, with CAN IDs 7E0-7E8, and turn on frame padding
        ecm_channel = IsotpChannel(bus_name="bus_6_14", request_id=0x7E0, response_id=0x7E8, padding=Padding.PADDING_ENABLED)

        # Start an IsotpSocket to communicate with the engine, using the 'with' statement ensures that the stream will be neatly
        # closed after this block finishes
        with IsotpSocket(self.openobd_session, ecm_channel) as ecm:

            # Start an extended diagnostic session, with silent set to True so no ResponseException will be raised on an incorrect response
            print("Sending 1003...")
            response = ecm.request("1003", silent=True)
            print(f"Response: {response}")

            try:
                # Request the VIN from the engine, attempting it up to 2 times, each attempt waiting a maximum of 5 seconds for a response
                print("Sending 22F190...")
                response = ecm.request("22F190", tries=2, timeout=5)
                print(f"Response: {response}")

                # Decode and print the VIN, ignoring the first 3 bytes of the response
                vin = bytes.fromhex(response[6:]).decode("utf-8")
                print(f"VIN: {vin}")

            # Catch any exceptions that are raised because of the ECU returning a negative response, or not responding at all
            except ResponseException as e:
                print(f"Request failed: {e}")


        # Finish the function with a successful result
        self.result = ServiceResult(result=[Result.RESULT_SUCCESS])

TP 2.0 communication

The example below demonstrates how a VIN can be read from a vehicle using the TP 2.0 protocol. This is done by configuring the required bus, specifying a communication channel with a logical address, and starting a stream to communicate with that ECU.

python -m openobd run --file academy/vehicle_communication/tp20.py --ticket <TICKET_NR>

from openobd import *

class Tp20CommunicationExample(OpenOBDFunction):

    def run(self):
        # Define a bus with the TP protocol
        print("Configuring buses...")
        bus_configs = [
            (BusConfiguration(bus_name="bus_6_14",
                              can_bus=CanBus(pin_plus=6, pin_min=14, can_protocol=CanProtocol.CAN_PROTOCOL_TP20,
                                             can_bit_rate=CanBitRate.CAN_BIT_RATE_500,
                                             transceiver=TransceiverSpeed.TRANSCEIVER_SPEED_HIGH)))
        ]

        # Set the bus configuration
        set_bus_configuration(self.openobd_session, bus_configs)
        print("Buses have been configured.")

        # Define the engine control module, which is present on bus pins 6, 14 with the logical address 0x1F
        ecm_channel = Tp20Channel(bus_name="bus_6_14", logical_address=0x1F)

        # Start an Tp20Socket to communicate with the engine, using the 'with' statement ensures that the stream will be neatly
        # closed after this block finishes
        with Tp20Socket(self.openobd_session, ecm_channel) as ecm:

            # Start an extended diagnostic session, with silent set to True so no ResponseException will be raised on an incorrect response
            print("Sending 1003...")
            response = ecm.request("1003", silent=True)

            try:
                # Request the VIN from the engine, attempting it up to 2 times, each attempt waiting a maximum of 5 seconds for a response
                print("Sending 22F190...")
                response = ecm.request("22F190", tries=2, timeout=5)
                print(f"Response: {response}")

                # Decode and print the VIN, ignoring the first 3 bytes of the response
                vin = bytes.fromhex(response[6:]).decode("utf-8")
                print(f"VIN: {vin}")

            # Catch any exceptions that are raised because of the ECU returning a negative response, or not responding at all
            except ResponseException as e:
                print(f"Request failed: {e}")


        # Finish the function with a successful result
        self.result = ServiceResult(result=[Result.RESULT_SUCCESS])

Raw CAN frame communication

The raw frames protocol can be used when it is desired to have direct control over the frames that are being exchanged with an ECU. Below is an example in which a VIN is read from an ECU while using the frames protocol. It demonstrates how to set up a frames stream and how to handle multi-frame messages.

python -m openobd run --file academy/vehicle_communication/raw.py --ticket <TICKET_NR>

from openobd import *

class RawCommunicationExample(OpenOBDFunction):

    def run(self):
        # Define a bus with the frames protocol
        print("Configuring buses...")
        bus_configs = [
            (BusConfiguration(bus_name="bus_6_14",
                              can_bus=CanBus(pin_plus=6, pin_min=14, can_protocol=CanProtocol.CAN_PROTOCOL_FRAMES,
                                             can_bit_rate=CanBitRate.CAN_BIT_RATE_500,
                                             transceiver=TransceiverSpeed.TRANSCEIVER_SPEED_HIGH)))
        ]

        # Set the bus configuration
        set_bus_configuration(self.openobd_session, bus_configs)
        print("Buses have been configured.")

        # Define the engine control module, which is present on bus pins 6, 14, with CAN IDs 7E0-7E8
        ecm_channel = RawChannel(bus_name="bus_6_14", request_id=0x7E0, response_id=0x7E8)

        # Start an RawSocket to communicate with the engine, using the 'with' statement ensures that the stream will be neatly
        # closed after this block finishes
        with RawSocket(self.openobd_session, ecm_channel) as ecm:

            # Start an extended diagnostic session
            try:
                print("Sending 1003...")
                # Send the entire 8-byte long CAN frame to the engine
                ecm.send("0210030000000000")
                # Wait max 5 seconds for a response
                response = ecm.receive(timeout=5)
                print(f"Received frame: {response}")
            except OpenOBDStreamTimeoutException:
                print("No frame received.")

            # Send a "tester present" message to the engine control module to keep its diagnostic session active
            ecm.send("023E800000000000")

            # Request the VIN, which does not fit in a single frame and therefore requires multiple frames to be received
            try:
                print("Sending 22F190...")
                ecm.send("0322F19000000000")
                response = ecm.receive()  # Default timeout is 10 seconds
                print(f"Received frame: {response}")

                # Determine how long the response will be using info from the first frame
                total_bytes = int(response[1:4], 16)
                received_payload = response[4:]
                received_bytes = 6

                # Send a flow control frame to indicate that the engine should continue sending frames
                ecm.send("3000000000000000")

                # Continue receiving frames until all the bytes have been received
                while received_bytes < total_bytes:
                    response = ecm.receive(timeout=2)
                    print(f"Received frame: {response}")
                    received_payload += response[2:]
                    received_bytes += 7

                # Decode and print the VIN, ignoring the first 3 bytes of the response
                vin = bytes.fromhex(received_payload[6:]).decode("utf-8")
                print(f"VIN: {vin}")

            except OpenOBDStreamTimeoutException:
                print("No frame received.")

        # Finish the function with a successful result
        self.result = ServiceResult(result=[Result.RESULT_SUCCESS])

K-Line communication

Below is an example on how to read the DTCs from an ECU using K-Line. It creates a K-Line bus and channel, and opens a stream for communication with the ECU.

python -m openobd run --file academy/vehicle_communication/kline.py --ticket <TICKET_NR>

from openobd import *

class KlineCommunicationExample(OpenOBDFunction):

    def run(self):
        # Define a bus with the K-Line protocol
        print("Configuring buses...")
        bus_configs = [
            (BusConfiguration(bus_name="bus_7",
                              kline_bus=KlineBus(pin=7, kline_protocol=KlineProtocol.KLINE_PROTOCOL_ISO14230_FAST,
                                                 kline_bit_rate=KlineBitRate.KLINE_BIT_RATE_10400))),
        ]

        # Set the bus configuration
        set_bus_configuration(self.openobd_session, bus_configs)
        print("Buses have been configured.")

        # Define the immobilizer, assuming it is present on bus pin 7, with ECU ID C1
        immo_channel = KlineChannel(bus_name="bus_7", ecu_id=0xC1)

        # Start a KlineSocket to communicate with the engine, using the 'with' statement ensures that the stream will be neatly
        # closed after this block finishes
        with KlineSocket(self.openobd_session, immo_channel) as immo:

            # Start a default diagnostic session, with silent set to True so no ResponseException will be raised on an incorrect response
            print("Sending 81...")
            response = immo.request("81", silent=True)
            print(f"Response: {response}")

            try:
                # Request the DTCs stored in the ECU
                print("Sending 17FF00...")
                response = immo.request("17FF00")
                print(f"Response: {response}")

                # Print the amount of DTCs present in the ECU
                dtc_count = int(response[2:4], 16)
                if dtc_count == 0:
                    print("No DTCs present in the ECU.")
                else:
                    print(f"Found {dtc_count} DTCs: {response[4:]}")

            # Catch any exceptions that are raised because of the ECU returning a negative response, or not responding at all
            except ResponseException as e:
                print(f"Request failed: {e}")


        # Finish the function with a successful result
        self.result = ServiceResult(result=[Result.RESULT_SUCCESS])

Terminal15 communication

Below is an example on how to read the state of the ignition switch (Terminal15). It creates a Terminal15 bus, and opens a stream to listen for updates.

python -m openobd run --file academy/vehicle_communication/terminal15.py --ticket <TICKET_NR>

from openobd import *

class Terminal15CommunicationExample(OpenOBDFunction):

    def run(self):
        # Define a bus with the Terminal15 protocol for listening on pin 1
        print("Configuring buses...")
        bus_configs = [
            (BusConfiguration(bus_name="terminal15",
                              terminal15_bus=Terminal15Bus(pin=1)))
        ]

        # Set the bus configuration
        set_bus_configuration(self.openobd_session, bus_configs)
        print("Buses have been configured.")

        # Start an Terminal15Socket to communicate with the engine, using the 'with' statement ensures that the stream will be neatly
        # closed after this block finishes
        with Terminal15Socket(self.openobd_session, timeout=10) as terminal15_state:

            # Keep listening to the terminal15 signal for 30 seconds and show the state updates
            try:
                current_time = time.time()
                while current_time + 30 > time.time():
                    current_state = terminal15_state.receive()
                    print(f" [i] Current terminal15 state: {current_state}")
            except OpenOBDStreamTimeoutException as e:
                print(f"Request failed: {e}")


        # Finish the function with a successful result
        self.result = ServiceResult(result=[Result.RESULT_SUCCESS])

DoIP communication

Below is an example on how to read the VIN from an ECU using DoIP. The example creates a DoIP bus and channel, and opens a stream for communication with the ECU.

python -m openobd run --file academy/vehicle_communication/doip.py --ticket <TICKET_NR>

from openobd import *


class DoipCommunicationExample(OpenOBDFunction):

    def run(self):
        # Define a DoIP bus and specify the network configuration used for Volvo
        print("Configuring buses...")
        bus_configs = [
            (BusConfiguration(bus_name="bus_1",
                              doip_bus=DoipBus(doip_option=DoipOption.DOIP_OPTION_1,
                                               doip_network_configuration=DoipNetworkConfiguration.DOIP_NETWORK_CONFIGURATION_2))),
        ]

        # Set the bus configuration
        # Note: this can take up to 90 seconds for DoIP
        set_bus_configuration(self.openobd_session, bus_configs)
        print("Buses have been configured.")

        # Define the instrument panel, assuming it is present on the bus with DoIP option 1, is behind a gateway with ID 1001, and has ECU ID 1801
        ipc_channel = DoipChannel(bus_name="bus_1", gateway_id=0x1001, tester_id=0xE80, ecu_id=0x1801)

        # Start an DoipSocket to communicate with the engine, using the 'with' statement ensures that the stream will be neatly
        # closed after this block finishes
        with DoipSocket(self.openobd_session, ipc_channel) as ipc:

            # Start an extended diagnostic session, with silent set to True so no ResponseException will be raised on an incorrect response
            print("Sending 1003...")
            response = ipc.request("1003", silent=True)
            print(f"Response: {response}")

            try:
                # Request the VIN from the engine, attempting it up to 2 times, each attempt waiting a maximum of 5 seconds for a response
                print("Sending 22F190...")
                response = ipc.request("22F190", tries=2, timeout=5)
                print(f"Response: {response}")

                # Decode and print the VIN, ignoring the first 3 bytes of the response
                vin = bytes.fromhex(response[6:]).decode("utf-8")
                print(f"VIN: {vin}")

            # Catch any exceptions that are raised because of the ECU returning a negative response, or not responding at all
            except ResponseException as e:
                print(f"Request failed: {e}")


        # Finish the function with a successful result
        self.result = ServiceResult(result=[Result.RESULT_SUCCESS])