python-can-isotp

repository·v2.x·Indexed 11 days ago

https://github.com/pylessard/python-can-isotp

A Python 3 package providing support for the ISO-15765 (IsoTP) transport protocol. It features a pure Python user-space implementation and a wrapper for the Linux SocketCAN can-isotp kernel module. Version 2.x introduces a NotifierBasedCanStack, asymmetric addressing, generator support for large payloads, and full type hinting.

Tokens
9K
Snippets
17
Records
32
Agent score
48%

What's inside python-can-isotp

  1. Overview of python-can-isotp

    v2.x

    python-can-isotp is a Python 3 package providing support for the ISO-15765 (IsoTP) transport protocol. It offers two primary implementation paths:

    1. Pure Python Implementation: A user-space implementation that may or may not be coupled with the python-can library.
    2. SocketCAN Wrapper: A wrapper for simplified usage of the Linux can-isotp kernel module.

    The project is licensed under the MIT license.

  2. Configure the Rate Limiter in TransportLayer

    v2.x

    The isotp.TransportLayer includes a rate limiter to throttle output data rates and prevent overwhelming CAN driver buffers. It uses a sliding window mechanism where the maximum allowed bits per window is the product of rate_limit_max_bitrate and rate_limit_window_size.

    Key considerations:

    • Burst Size: The product of bitrate and window size also defines the maximum burst size. For example, a bitrate of 80,000 bps and a window of 0.1s allows a burst of 8,000 bits (1,000 bytes) every 0.1s.
    • Payload vs. Hardware Bitrate: The rate_limit_max_bitrate applies only to the CAN payload. It does not include CAN layer overhead (ID, CRC, etc.). Consequently, the actual hardware bitrate measured may be 1x to 1.5x higher than requested.
    • Accuracy: Bitrate is achieved using OS Sleep() calls. Accuracy may be poor if the bitrate is very low or the window size is very small due to OS scheduler resolution.
  3. Configure Extended addressing

    v2.x

    Extended addressing uses standard rxid and txid for the CAN arbitration ID, but requires an additional source_address and target_address which are added as the first byte of each CAN message payload.

    This mode works with both 11-bit and 29-bit identifiers.

    Reception Condition:

    • Message arbitration ID must match receiver rxid.
    • Payload first byte must match receiver source_address.

    Example:

    • rxid: 0x123
    • txid: 0x456
    • source_address: 0x55
    • target_address: 0xAA
    0x123    [8]   55 10 0A 00 01 02 03    // First frame
    0x456    [5]   AA 30 00 08 00             // Flow control
    0x123    [8]   55 21 04 05 06 07 08 09 // consecutive frame
    0x123    [8]   55 10 0A 00 01 02 03    // First frame
    0x456    [5]   AA 30 00 08 00             // Flow control
    0x123    [8]   55 21 04 05 06 07 08 09 // consecutive frame
  4. Configure Normal addressing

    v2.x

    In Normal addressing, communication is based on the CAN arbitration ID. To receive messages, the incoming message's arbitration ID must match the configured rxid.

    This mode supports both 11-bit and 29-bit CAN identifiers.

    Example (10 bytes payload):

    • rxid: 0x123
    • txid: 0x456
    0x123    [8]   10 0A 00 01 02 03 04    // First frame
    0x456    [4]   30 00 08 00             // Flow control
    0x123    [6]   21 05 06 07 08 09       // Consecutive frame
    0x123    [8]   10 0A 00 01 02 03 04    // First frame
    0x456    [4]   30 00 08 00             // Flow control
    0x123    [6]   21 05 06 07 08 09       // Consecutive frame
  5. Key improvements in v2.x

    v2.x

    Version 2.x introduces several features and performance enhancements over the legacy version:

    • NotifierBasedCanStack: A new object that uses a python-can Notifier instead of calling bus.recv(). This prevents the CanStack from depleting the receive queue and starving other modules.
    • Asymmetric Addressing: Supports using different addresses for transmission and reception.
    • Generator Support: You can now send data using a generator, which is useful for handling large payloads.
    • Type Hinting: The module is fully type-hinted for better developer experience.
    • Precise Timing: Supports a wait_func parameter to allow for busy-wait implementations to achieve higher timing precision.
    • Windows Performance: Improved performance on Windows by using time.perf_counter instead of time.monotonic.
  6. Configure Normal fixed addressing (29-bit)

    v2.x

    Normal fixed addressing encodes the target_address and source_address within a 29-bit CAN arbitration ID. This mode is only available for 29-bit identifiers.

    ID Encoding Patterns:

    • 1-to-1 communication (target_address_type = Physical): 0x18DA<TA><SA>
    • 1-to-n communication (target_address_type = Functional): 0x18DB<TA><SA>

    Reception Condition:

    • Message Target Address must match receiver source_address.
    • Message Source Address must match the receiver target_address.

    Example:

    • source_address: 0x55
    • target_address: 0xAA
    0x18DA55AA    [8]   10 0A 00 01 02 03 04  // First frame
    0x18DAAA55    [4]   30 00 08 00             // Flow control
    0x18DA55AA    [6]   21 05 06 07 08 09       // Consecutive frame
    0x18DA55AA    [8]   10 0A 00 01 02 03 04  // First frame
    0x18DAAA55    [4]   30 00 08 00             // Flow control
    0x18DA55AA    [6]   21 05 06 07 08 09       // Consecutive frame
  7. Understand the TransportLayer threading model

    v2.x

    In v2.x, isotp.TransportLayer uses an internal thread-based strategy to minimize latency and improve performance compared to v1.x. It employs a 3-thread strategy using Python Queue objects:

    1. Relay Thread: Reads the user-provided rxfn (receive function) in a loop and fills a relay queue.
    2. Worker Thread:
      • Performs non-blocking reads from the Rx Queue to interact with the user.
      • Performs blocking reads from the relay queue to process incoming messages immediately.
      • When send() is called, a None is injected into the relay queue to wake the worker thread to process the payload.

    This architecture allows for a latency of approximately 40us (two context switches), which is acceptable for standard CAN bus speeds.

  8. Configure the Rate Limiter

    v2.x

    The rate limiter throttles the output rate of the TransportLayer. To use it, set rate_limit_enable=True in the params dictionary.

    It uses a sliding time window to ensure that no more than $N$ bits are sent within the window, where $N = \text{rate_limit_max_bitrate} \times \text{rate_limit_window_size}$.

    Parameters:

    • rate_limit_max_bitrate (int): The target bitrate in bits per second.
    • rate_limit_window_size (float): The width of the sliding window in seconds. This should be at least 0.05 (50ms) for reliable behavior.
  9. Understand ISO-TP addressing modes

    v2.x

    ISO-15765 defines several addressing modes supported by this module. The way source and target addresses are defined depends on the selected isotp.AddressingMode. Addressing is primarily handled via the isotp.Address object.

    Key addressing modes include:

    • Normal addressing: Uses standard CAN arbitration IDs (rxid and txid). Works with both 11-bit and 29-bit identifiers.
    • Normal fixed addressing: Encodes target_address and source_address directly into a 29-bit CAN arbitration ID. Only available for 29-bit identifiers.
    • Extended addressing: Uses standard rxid/txid but adds source_address and target_address as the first byte of the payload.
    • Mixed addressing (11-bit): Uses rxid/txid and an address_extension byte in the payload.
    • Mixed addressing (29-bit): Combines normal fixed addressing (in the ID) with an address_extension byte in the payload.
  10. Configure Mixed addressing (11-bit and 29-bit)

    v2.x

    Mixed addressing uses a payload prefix called address_extension.

    Mixed addressing - 11 bits

    Combines normal addressing with extended addressing. The address_extension byte in the payload acts as both the source and target address.

    Reception Condition:

    • Message arbitration ID must match receiver rxid.
    • Payload first byte must match receiver address_extension.

    Example:

    • rxid: 0x123, txid: 0x456, address_extension: 0x99
    0x123    [8]   99 10 0A 00 01 02 03    // First frame
    0x456    [5]   99 30 00 08 00             // Flow control
    0x123    [8]   99 21 04 05 06 07 08 09 // consecutive frame

    Mixed addressing - 29 bits

    Combines normal fixed addressing with extended addressing.

    ID Encoding Patterns:

    • 1-to-1 communication (target_address_type = Physical): 0x18CE<TA><SA>
    • 1-to-n communication (target_address_type = Functional): 0x18CD<TA><SA>

    Reception Condition:

    • Message Target Address must match receiver source_address.
    • Message Source Address must match the receiver target_address.
    • Payload first byte must match receiver address_extension.

    Example:

    • source_address: 0x55, target_address: 0xAA, address_extension: 0x99
    0x18CE55AA    [8]   99 10 0A 00 01 02 03    // First frame
    0x18CEAA55    [5]   99 30 00 08 00             // Flow control
    0x18CE55AA    [8]   99 21 04 05 06 07 08 09 // consecutive frame
    0x123    [8]   99 10 0A 00 01 02 03    // First frame
    0x456    [5]   99 30 00 08 00             // Flow control
    0x123    [8]   99 21 04 05 06 07 08 09 // consecutive frame
    
    0x18CE55AA    [8]   99 10 0A 00 01 02 03    // First frame
    0x18CEAA55    [5]   99 30 00 08 00             // Flow control
    0x18CE55AA    [8]   99 21 04 05 06 07 08 09 // consecutive frame
  11. Pass hardware handles to rxfn and txfn using functools.partial

    v2.x

    Since isotp.TransportLayer calls rxfn and txfn without additional arguments, you cannot pass hardware handles (like file descriptors or API handles) directly to them. To solve this, use functools.partial to wrap your functions with the required handles before passing them to the TransportLayer constructor.

    import isotp
    import functools
    from typing import Optional
    
    def my_rxfn(hardware_handle, timeout:float) -> Optional[isotp.CanMesage]:
        msg = my_hardware_api_recv(hardware_handle, timeout)
        if msg is None:
            return None
        return isotp.CanMesage(arbitration_id=msg.get_id(), data=msg.get_data(), dlc=msg.get_dlc(), extended_id=msg.is_extended_id())
    
    def my_txfn(hardware_handle, isotp_msg:isotp.CanMesage):
        msg = my_hardware_api_make_msg()
        msg.set_id(isotp_msg.arbitration_id)
        msg.set_data(isotp_msg.data)
        msg.set_dlc(isotp_msg.dlc)
        msg.set_extended_id(isotp_msg.is_extended_id)
        my_hardware_api_send(hardware_handle, msg)
    
    hardware_handle = my_hardware_open()
    addr = isotp.Address(isotp.AddressingMode.Normal_29bits, txid=0x123456, rxid = 0x123457)
    
    # Wrap functions with the handle
    partial_rxfn = functools.partial(my_rxfn, hardware_handle)
    partial_txfn = functools.partial(my_txfn, hardware_handle)
    
    layer = isotp.TransportLayer(rxfn=partial_rxfn, txfn=partial_txfn, address=addr)
    layer.start()
    # ... rest of program
    layer.stop()
    my_hardware_close()
  12. Handle ISO-TP errors with an error handler

    v2.x

    Errors occurring during transmission or reception are reported via a user-provided error handler. An error handler must be a callable that accepts an isotp.IsoTpError as its first parameter.

    Important: The error handler is called from the internal thread. Any interaction with your main application from the handler must use thread-safe mechanisms.

    All errors inherit from isotp.IsoTpError (which inherits from Exception). Common error types include:

    • FlowControlTimeoutError
    • ConsecutiveFrameTimeoutError
    • InvalidCanDataError
    • UnexpectedFlowControlError
    • UnexpectedConsecutiveFrameError
    • ReceptionInterruptedWithSingleFrameError
    • ReceptionInterruptedWithFirstFrameError
    • WrongSequenceNumberError
    • UnsupportedWaitFrameError
    • MaximumWaitFrameReachedError
    • FrameTooLongError
    • ChangingInvalidRXDLError
    • MissingEscapeSequenceError
    • InvalidCanFdFirstFrameRXDL
    • OverflowError
    • BadGeneratorError
    def my_error_handler(error):
        # error is an instance of isotp.IsoTpError
        print(f"An error occurred: {error}")
    
    # When initializing your TransportLayer, pass this handler