Implementation
This sections explains the python implementation of the IsoTP protocol.
Transport layer
Depending on your constraints, you may want to have the IsoTP protocol layer to run in Python (in the user space). For example, if you want to rely on python-can for the support of your CAN interface, you will need to run the IsoTP layer in Python.
In such case, the isotp.TransportLayer
will be the right tool. One must first define functions to access the hardware and provide them to the isotp.TransportLayer
as parameters named rxfn
and txfn
.
- class isotp.TransportLayer(rxfn, txfn, address=None, error_handler=None, params=None)[source]
The IsoTP transport layer implementation
- Parameters:
rxfn (Callable) – Function to be called by the transport layer to read the CAN layer. Must return a
isotp.CanMessage
or None if no message has been received.txfn (Callable) – Function to be called by the transport layer to send a message on the CAN layer. This function should receive a
isotp.CanMessage
address (isotp.Address) – The address information of CAN messages. Includes the addressing mode, txid/rxid, source/target address and address extension. See
isotp.Address
for more details.error_handler (Callable) – A function to be called when an error has been detected. An
isotp.IsoTpError
(inheriting Exception class) will be given as sole parameter. See the Error sectionparams (dict) – List of parameters for the transport layer
If python-can must be used as CAN layer, one can use the isotp.CanStack
which extends the TransportLayer object with predefined functions that calls python-can.
- class isotp.CanStack(bus, timeout=0.0, *args, **kwargs)[source]
The IsoTP transport using python-can as CAN layer. python-can must be installed in order to use this class. All parameters except the
bus
parameter will be given to theTransportLayer
constructor- Parameters:
bus (BusABC) – A python-can bus object implementing
recv
andsend
address (isotp.Address) – The address information of CAN messages. Includes the addressing mode, txid/rxid, source/target address and address extension. See
isotp.Address
for more details.error_handler (Callable) – A function to be called when an error has been detected. An
isotp.protocol.IsoTpError
(inheriting Exception class) will be given as sole parameterparams (dict) – List of parameters for the transport layer
The CAN messages going in and out from the transport layer are defined with isotp.CanMessage
.
- class isotp.CanMessage(arbitration_id=None, dlc=None, data=None, extended_id=False, is_fd=False, bitrate_switch=False)[source]
Represent a CAN message (ISO-11898)
- Parameters:
arbitration_id (int) – The CAN arbitration ID. Must be a 11 bits value or a 29 bits value if
extended_id
is Truedlc (int) – The Data Length Code representing the number of bytes in the data field
data (bytearray) – The 8 bytes payload of the message
extended_id (bool) – When True, the arbitration ID stands on 29 bits. 11 bits when False
is_fd – When True, message has to be transmitted or has been received in a CAN FD frame. CAN frame when set to False
Parameters
The transport layer params
parameter must be a dictionary with the following keys.
- stmin (int)
default: 0
The single-byte Separation Time to include in the flow control message that the layer will send when receiving data. Refer to ISO-15765-2 for specific values. From 1 to 127, represents milliseconds. From 0xF1 to 0xF9, represents hundreds of microseconds (100us, 200us, …, 900us). 0 Means no timing requirements
- blocksize (int)
default: 8
The single-byte Block Size to include in the flow control message that the layer will send when receiving data. Represents the number of consecutive frames that a sender should send before expecting the layer to send a flow control message. 0 means infinitely large block size (implying no flow control message)
- tx_data_length (int)
default: 8
The maximum number of bytes that the Link Layer (CAN layer) can transport. In other words, the biggest number of data bytes possible in a single CAN message. Valid values are : 8, 12, 16, 20, 24, 32, 48, 64.
Large frames will be transmitted in small CAN messages of this size except for the last CAN message that will be as small as possible, unless padding is used.
This parameter was formely named
ll_data_length
but has been renamed to explicitly indicate that it affects transmitted messages only.
- tx_data_min_length (int)
default: None
Sets the minimum length of CAN messages. Message with less data than this value will be padded using
tx_padding
byte or0xCC
iftx_padding=None
.When set to
None
, CAN messages will be as small as possible unlesstx_data_length=8
andtx_padding != None
; in that case, all CAN messages will be padded up to 8 bytes to be compliant with ISO-15765.Valid values are : 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64.
- squash_stmin_requirement (bool)
default: False
Indicates if the layer should override the receiver separation time (stmin) when sending and try sending as fast as possible instead. This can be useful when the layer is running on an operating system giving low priority to your application; such as Windows that has a thread resolution of 16ms.
- rx_flowcontrol_timeout
default: 1000
The number of milliseconds to wait for a flow control frame before stopping reception and triggering a
FlowControlTimeoutError
. Defined as N_BS bs ISO-15765-2
- rx_consecutive_frame_timeout (int)
default: 1000
The number of milliseconds to wait for a consecutive frame before stopping reception and triggering a
ConsecutiveFrameTimeoutError
. Defined as N_CS by ISO-15765-2
- tx_padding (int or None)
default: None
When not
None
represents the byte used for padding messages sent. No padding applied whenNone
unlesstx_data_min_length
is set or CAN FD mandatory padding is required.
- wftmax (int)
default: 0
The single-byte Wait Frame Max to include in the flow control message that the layer will send when receiving data. When this limits is reached, reception will stop and trigger a
MaximumWaitFrameReachedError
A value of 0 means that wait frames are not supported and none shall be sent.
- max_frame_size (int)
default: 4095
The maximum frame length that the stack will accept to receive. ISO-15765-2:2016 allows frames as long as 2^32-1 (4294967295 bytes). When a FirstFrame is sent with a length longer than
max_frame_size
, the message will be ignored, a FlowControl message with FlowStaus=2 (Overflow) will be sent and aFrameTooLongError
will be triggered.This parameter mainly is a protection to avoid crashes due to lack of memory (caused by an external device).
- can_fd (bool)
default: False
When set to
True
, transmitted messages will be CAN FD. CAN 2.0 whenFalse
.Setting this parameter to
True
does not change the behaviour of theTransportLayer
except that outputted message will have theiris_fd
property set toTrue
. This parameter is just a convenience to integrate more easily with python-can
- bitrate_switch (bool)
default: False
When set to
True
, tx message will have a flagbitrate_switch
marked asTrue
, meaning that the underlying layer shall performe a CAN FD bitrate switch after arbitration phase.Setting this parameter to
True
does not change the behaviour of theTransportLayer
except that outputted message will have theirbitrate_switch
property set toTrue
. This parameter is just a convenience to integrate more easily with python-can
- default_target_address_type (int)
default: Physical (0)
When using the
TransportLayer.send
method without specifyingtarget_address_type
, the value in this field will be used. The purpose of this parameter is to easily switch the address type if your program is not calling send directly; for example, if you use a library that interact with theTransportLayer
object (such as a UDS client).Can either be
Physical (0)
orFunctional (1)
- rate_limit_enable (bool)
default: False
Enable or disable the rate limiter. When disabled, no throttling is done on the output rate. When enabled, extra wait states are added in between CAN message tranmission to meet
rate_limit_max_bitrate
Refer to Rate Limiter Section for more details
- rate_limit_max_bitrate (int)
default: 10000000 b/s
Defines the target bitrate in Bits/seconds that the TranportLayer object should try to respect. This rate limiter only apply to the data of the output messages.
Refer to Rate Limiter Section for more details
- rate_limit_window_size (float)
default: 0.2 sec
Time window used to compute the rate limit. The rate limiter algorithm works with a sliding time window. This parameter defines the width of the window. The rate limiter ensure that no more than N bits is sent within the moving window where N=(rate_limit_max_bitrate*rate_limit_window_size).
This value should be at least 50 msec for reliable behavior.
Refer to Rate Limiter Section for more details
- listen_mode (bool)
default: False
When Listen Mode is enabled, the
TransportLayer
will correctly receive and transmit ISO-TP Frame, but will not send Flow Control message when receiving a frame. This mode of operation is usefull to listen to a transmission between two third-party devices without interferring.
Rate Limiter
The isotp.TransportLayer
transmission rate limiter is a feature that allows to do some throttling on the output data rate. It works with a simple sliding window and
keeps the total amount of bits sent during that time window below the maximum allowed.
The maximum of bits allowed during the moving time window is defined by the product of rate_limit_max_bitrate
and rate_limit_window_size
.
For example, if the target bitrate is 1000b/s and the window size is 0.1sec, then the rate limiter will keep to total amount of bits during a window of 0.1 sec below 100bits.
It is important to understand that this product also defines the maximum burst size that the isotp.TransportLayer
object will output, and this is actually the original problem the
rate limiter is intended to fix (See issue #61). Consider the case where a big payload of 10000 bytes must be transmitted,
after the transmission of the FirstFrame, the receiving party sends a FlowControl message with BlockSize=0 and STMin=0. In that situation, the whole payload can be sent immediately
but writing 10000 bytes in a single burst might be too much for the CAN driver to handle and may overflow its internal buffer. In
this situation, it is useful to use the rate limiter to reduces the strain on the driver internal buffer.
In the above scenario, having a bitrate of 80000 bps and a window size of 0.1 sec would make the isotp.TransportLayer
output a burst of 8000 bits (1000 bytes) every 0.1 seconds.
Warning
The bitrate defined by rate_limit_max_bitrate represent the bitrate of the CAN payload that goes out of the isotp.TransportLayer
object only,
the CAN layer overhead is exluded.
Knowing the a classical CAN message with 11bits ID and a payload of 64 bits usually have 111 bits, the extra 47 bits of overhead will not be considered by the rate limiter. This means
that even if the rate limiter is requested to keep a steady 10kbps, depending on the CAN layer configuration, the effective hardware bitrate measured might be much more significant, from 1 to 1.5x more.
Warning
Bitrate is achieved by adding extra wait states which normally translate into OS calls to Sleep()
.
Because an OS scheduler has a time resolution, bitrate accuracy will be poor if the specified bitrate is very low or if the window size is very small.
Usage
The isotp.TransportLayer
object has the following methods
- TransportLayer.send(data, target_address_type=None)[source]
Enqueue an IsoTP frame to be sent over CAN network
- Parameters:
data (bytearray) – The data to be sent
target_address_type (int) – Optional parameter that can be Physical (0) for 1-to-1 communication or Functional (1) for 1-to-n. See
isotp.TargetAddressType
. If not provided, parameter default_target_address_type will be used (default to Physical)
- Raises:
ValueError – Input parameter is not a bytearray or not convertible to bytearray
RuntimeError – Transmit queue is full
- TransportLayer.recv()[source]
Dequeue an IsoTP frame from the reception queue if available.
- Returns:
The next available IsoTP frame
- Return type:
bytearray or None
- TransportLayer.available()[source]
Returns
True
if an IsoTP frame is awaiting in the receptionqueue
.False
otherwise
- TransportLayer.transmitting()[source]
Returns
True
if an IsoTP frame is being transmitted.False
otherwise
- TransportLayer.set_address(address)[source]
Sets the layer
Address
. Can be set after initialization if needed.
- TransportLayer.reset()[source]
Reset the layer: Empty all buffers, set the internal state machines to Idle
- TransportLayer.process()[source]
Function to be called periodically, as fast as possible. This function is non-blocking.
- TransportLayer.sleep_time()[source]
Returns a value in seconds that can be passed to
time.sleep()
when the stack is processed in a different thread.The value will change according to the internal state machine state, sleeping longer while idle and shorter when active.
Errors
When calling TransportLayer.process
, no exception should raise. Still, errors are possible and are given to an error handler provided by the user.
An error handler should be a callable function that expects an Exception as first parameter.
- my_error_handler(error)
- Parameters:
error (
isotp.IsoTpError
) – The error
All errors inherit isotp.IsoTpError
which itself inherits Exception
- class isotp.FlowControlTimeoutError(*args, **kwargs)[source]
Happens when the senders fails to sends a Flow Control message in time. Refer to TransportLayer parameter rx_flowcontrol_timeout
- class isotp.ConsecutiveFrameTimeoutError(*args, **kwargs)[source]
Happens when the senders fails to sends a Consecutive Frame message in time. Refer to TransportLayer parameter rx_consecutive_frame_timeout
- class isotp.InvalidCanDataError(*args, **kwargs)[source]
Happens when a CAN message that cannot be decoded as valid First Frame, Consecutive Frame, Single Frame or Flow Control PDU is received.
- class isotp.UnexpectedFlowControlError(*args, **kwargs)[source]
Happens when a Flow Control message is received and was not expected
- class isotp.UnexpectedConsecutiveFrameError(*args, **kwargs)[source]
Happens when a Consecutive Frame message is received and was not expected
- class isotp.ReceptionInterruptedWithSingleFrameError(*args, **kwargs)[source]
Happens when the reception of a multi packet message reception is interrupted with a new Single Frame PDU.
- class isotp.ReceptionInterruptedWithFirstFrameError(*args, **kwargs)[source]
Happens when the reception of a multi packet message reception is interrupted with a new First Frame PDU.
- class isotp.WrongSequenceNumberError(*args, **kwargs)[source]
Happens when a consecutive frame is received with a wrong sequence number.
- class isotp.UnsuportedWaitFrameError(*args, **kwargs)[source]
Happens when a Flow Control PDU with FlowStatus=Wait is received and wftmax is set to 0
- class isotp.MaximumWaitFrameReachedError(*args, **kwargs)[source]
Happens when too much Flow Control PDU with FlowStatus=Wait is received. Refer to wftmax
- class isotp.FrameTooLongError(*args, **kwargs)[source]
Happens when a FirstFrame with a length (FF_DL) longer than max_frame_size is received.
- class isotp.ChangingInvalidRXDLError(*args, **kwargs)[source]
Happens when a ConsecutiveFrame is received with a length smaller than
RX_DL
(size of first frame) without being the last message of the IsoTP frame.
- class isotp.MissingEscapeSequenceError(*args, **kwargs)[source]
Happens when a SingleFrame with length (CAN_DL) greater than 8 bytes is received and the length of the payload (SF_DL) is encoded in the first byte, which is forbidden by ISO-15765-2