pylessard / python-udsoncan

Python implementation of UDS (ISO-14229) standard.
MIT License
575 stars 199 forks source link

udsoncan.exceptions.TimeoutException: Did not receive response in time. P2 timeout time has expired (timeout=10.000 sec) #155

Closed catmemo closed 1 year ago

catmemo commented 1 year ago

I have already created a bus in another python file and added it to a global dict - GLOBAL_DICT. And I tried to use it as below:

bus = GLOBAL_DICT['bus']
stack = isotp.CanStack(bus=bus, address=tp_addr, params=isotp_params)  

But got error as below:

Exception in thread Thread-3:
Traceback (most recent call last):
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 1647, in send_request
    payload = self.conn.wait_frame(timeout=timeout_value, exception=True)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\connections.py", line 68, in wait_frame
    frame = self.specific_wait_frame(timeout=timeout)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\connections.py", line 490, in specific_wait_frame
    raise TimeoutException("Did not receive frame IsoTP Transport layer in time (timeout=%s sec)" % timeout)
udsoncan.exceptions.TimeoutException: Did not receive frame IsoTP Transport layer in time (timeout=10 sec)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:\Development_Tools\Python\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "D:\Development_Tools\Python\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "D:\Development_Tasks\CANSimulator\send_diag_request.py", line 262, in send_diag_session_control
    session_response = self.client.change_session(DiagnosticSessionControl.Session.defaultSession)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 131, in decorated
    return func(self, *args, **kwargs)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 189, in change_session
    response = self.send_request(req)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 1655, in send_request
    raise TimeoutException('Did not receive response in time. %s time has expired (timeout=%.3f sec)' % (timeout_name_to_report, timeout_value))
udsoncan.exceptions.TimeoutException: Did not receive response in time. P2 timeout time has expired (timeout=10.000 sec)

However, it works well when I changed the code as follows:

if 'bus' in GLOBAL_DICT.keys():
    GLOBAL_DICT['bus'].shutdown()

bus = can.ThreadSafeBus(bustype='pcan', app_name=None, channel='PCAN_USBBUS1', bitrate=bitrate1, receive_own_messages=True, name="BUS1")

And then, I tried to added the new bus to GLOBAL_DICT, and it got the same error again:

if 'bus' in GLOBAL_DICT.keys():
    GLOBAL_DICT['bus'].shutdown()

bus = can.ThreadSafeBus(bustype='pcan', app_name=None, channel='PCAN_USBBUS1', bitrate=bitrate1, receive_own_messages=True, name="BUS1")
GLOBAL_DICT['bus'] = bus

I printed the GLOBAL_DICT before and after, but it seems normal:

{'bus': <ThreadSafeBus at 0x000001707C5F6048 for PcanBus at 0x000001707C5BFD30>}
{'bus': <ThreadSafeBus at 0x000001707C9B1308 for PcanBus at 0x000001707C5E9470>}

I'm so confused about it. Could anyone help to check? Thanks in advance!

catmemo commented 1 year ago

@pylessard

ep081106 commented 1 year ago

hello pylessard, i have the same issues, this is my code:

!/usr/bin/env python

-- coding: utf-8 --

import isotp import logging import udsoncan

from can.interfaces.vector import VectorBus # CANaylzer from udsoncan.connections import PythonIsoTpConnection from udsoncan.client import Client

udsoncan.setup_logging()

logger = logging.getLogger('UDSdemo_CANaylzer') logger.setLevel(level=logging.INFO) handler = logging.FileHandler("udsoncan.log") handler.setLevel(logging.INFO) formatter = logging.Formatter(fmt='%(asctime)s [%(levelname)s] %(name)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

handler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) logger.addHandler(handler)

status_mask = 0xAF

Refer to isotp documentation for full details about parameters

isotp_params = { 'stmin': 32, # Will request the sender to wait 32ms between consecutive frame. 0-127ms

or 100-900ns with values from 0xF1-0xF9

'blocksize': 8,  # Request the sender to send 8 consecutives frames before sending a new
# flow control message
'wftmax': 0,  # Number of wait frame allowed before triggering an error
'll_data_length': 8,  # Link layer (CAN layer) works with 8 byte payload (CAN 2.0)
'tx_padding': 0,  # Will pad all transmitted CAN messages with byte 0x00. None means no padding
'rx_flowcontrol_timeout': 1000,  # Triggers a timeout if a flow control is awaited for more than 1000 milliseconds
'rx_consecutive_frame_timeout': 1000,
# Triggers a timeout if a consecutive frame is awaited for more than 1000 milliseconds
'squash_stmin_requirement': False,
# When sending, respect the stmin requirement of the receiver. If set to True, go as fast as possible.
'max_frame_size': 4095  # Limit the size of receive frame.

}

bus = VectorBus(channel=0, bitrate=500000)

tp_address = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=0x78A, rxid=0x7CA)

Network layer addressing scheme

stack = isotp.CanStack(bus=bus, address=tp_address, params=isotp_params) # Network/Transport layer (IsoTP protocol) conn = PythonIsoTpConnection(stack) # interface between Application and Transport layer, Define the connection using

the absolute path to the DLL, rxid and txid's for isotp

def intToBytes(value, length): # 十进制转bytes result = [] for i in range(0, length): result.append(value >> (i * 8) & 0xff) result.reverse() return result

with Client(conn, request_timeout=1000) as client: # Application layer (UDS protocol) response = client.get_dtc_by_status_mask(status_mask) # 19022F

pylessard commented 1 year ago

I understand that you use your bus object for other functionality. You probably encounter an issue that many are encountering. Your other feature consume the messages from the b us queue, meaning that the isotp stack never receives them, therefore UDS throw a Timeout error.

The clean solution would be to modify the CanStack object to use a bus listener that duplicates each messages before consuming them. That's on my todo list (https://github.com/pylessard/python-can-isotp/issues/76)

There is many workaround to this. The simplest is that you write your own txfn and rxfn function and use the TransportLayer object. The glue code is easy to write, see how I did : https://github.com/pylessard/python-can-isotp/blob/master/isotp/protocol.py#L1108 BAsically, you can make a dispatching mechanism that feeds only the isotp message to the stack and redirect other message to the right portion of your project.

You can also try to do the lsitener approach, I accept PRs :)

There is at least 5 or 6 other issues on the isotp project reporting something similar.

catmemo commented 1 year ago

Hi @pylessard,

Thanks for looking into this and thank you for your suggest! But it's strange that it works well with vector and only got errors when I tried to use PCAN. Isn't that a problem with pcan?

If 'GLOBAL_DICT['bus'].shutdown()' is removed, I'll get another error (maybe the case you mentioned above):

Traceback (most recent call last):
  File "D:\Development_Tasks\CANSimulator\gui.py", line 1288, in start_can_bus_operation
    dpg.get_value(self.response_id))
  File "D:\Development_Tasks\CANSimulator\send_diag_request.py", line 186, in __init__
    bus = can.interface.Bus(bustype=bustype1, app_name='Pcan', channel='PCAN_USBBUS1', bitrate=bitrate1)
  File "D:\Development_Tools\Python\lib\site-packages\can\interface.py", line 120, in __new__
    bus = cls(channel, *args, **kwargs)
  File "D:\Development_Tools\Python\lib\site-packages\can\interfaces\pcan\pcan.py", line 245, in __init__
    raise PcanCanInitializationError(self._get_formatted_error(result))
can.interfaces.pcan.pcan.PcanCanInitializationError: A PCAN Channel has not been initialized yet or the initialization process has failed
pylessard commented 1 year ago

Is your bus object working? It says that it is not initialized. Are you able to send messages without isotp?

You can enable logging in debug mode to get more info. A raw can log would also help.

I must say that i find your GLOBAL_DICT strange. Are you sure you manage your initialization sequence properly?

catmemo commented 1 year ago

Hi, I initialize it in a class - SendCANMessages:

class SendCANMessages:

    def __init__(self, bustype1, bustype2, channel1, channel2, bitrate1, bitrate2):
        if bustype1 != "None":
            if bustype1 == "pcan":
                channel1 = 'PCAN_USBBUS' + str(int(channel1))
            else:
                channel1 = str(int(channel1) - 1)
            self.bus1 = can.ThreadSafeBus(bustype=bustype1, app_name=None, channel=channel1, bitrate=bitrate1, receive_own_messages=True, name="BUS1")

        if bustype2 != "None":
            if bustype2 == "pcan":
                channel2 = 'PCAN_USBBUS' + str(int(channel2))
            else:
                channel2 = str(int(channel2) - 1)
            self.bus2 = can.ThreadSafeBus(bustype=bustype2, app_name=None, channel=channel2, bitrate=bitrate2, receive_own_messages=True, name="BUS2")

And when I launch the test tool, I call this function, pass the parameters to it to initialize, and then add it to GLOBAL_DICT:

self.send_can_messages_obj = SendCANMessages(self.device_type1, self.device_type2, self.device_channel1, self.device_channel2, self.device_baudrates1, self.device_baudrates2)

self.bus1 = self.send_can_messages_obj.bus1
GLOBAL_DICT["bus1"] = self.bus1
self.bus2 = self.send_can_messages_obj.bus2
GLOBAL_DICT["bus2"] = self.bus2

And it works with tx and rx messages well when I used GLOBAL_DICT["bus1"] or GLOBAL_DICT["bus2"]. But when I tried to use GLOBAL_DICT["bus1"] or GLOBAL_DICT["bus2"] in diagnostic, it failed.

2023-03-23 16:27:51 [INFO] Connection: Connection opened
2023-03-23 16:27:56 [INFO] UdsClient: DiagnosticSessionControl<0x10> - Switching session to defaultSession (0x01)
2023-03-23 16:27:56 [DEBUG] Connection: Sending 2 bytes : [b'1001']
2023-03-23 16:28:06 [DEBUG] Connection: No data received: [TimeoutException] - Did not receive frame IsoTP Transport layer in time (timeout=10 sec) 
2023-03-23 16:28:06 [ERROR] UdsClient: [TimeoutException] : Did not receive response in time. P2 timeout time has expired (timeout=10.000 sec)
Exception in thread Thread-3:
Traceback (most recent call last):
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 1647, in send_request
    payload = self.conn.wait_frame(timeout=timeout_value, exception=True)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\connections.py", line 68, in wait_frame
    frame = self.specific_wait_frame(timeout=timeout)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\connections.py", line 490, in specific_wait_frame
    raise TimeoutException("Did not receive frame IsoTP Transport layer in time (timeout=%s sec)" % timeout)
udsoncan.exceptions.TimeoutException: Did not receive frame IsoTP Transport layer in time (timeout=10 sec)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:\Development_Tools\Python\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "D:\Development_Tools\Python\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "D:\Development_Tasks\CANSimulator\send_diag_request.py", line 289, in send_diag_session_control
    session_response = self.client.change_session(DiagnosticSessionControl.Session.defaultSession)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 131, in decorated
    return func(self, *args, **kwargs)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 189, in change_session
    response = self.send_request(req)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 1655, in send_request
    raise TimeoutException('Did not receive response in time. %s time has expired (timeout=%.3f sec)' % (timeout_name_to_report, timeout_value))
udsoncan.exceptions.TimeoutException: Did not receive response in time. P2 timeout time has expired (timeout=10.000 sec)

So I wrote the code like that to create a new bus:

if bustype1 == 'None' and bustype2 != 'None':
    if bustype2 == 'pcan':
        self.bus = can.interface.Bus(bustype='pcan', app_name='Pcan',   channel='PCAN_USBBUS2', bitrate=500000)
    else:
        self.bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=1, bitrate=500000)
else:
    if bustype1 == 'pcan':
        self.bus = can.interface.Bus(bustype='pcan',  app_name='Pcan',   channel='PCAN_USBBUS1', bitrate=500000)
    else:
        self.bus = can.interface.Bus(bustype='vector',  app_name='CANalyzer', channel=0, bitrate=500000)

And when I used vector can, it works:

2023-03-23 15:48:30 [INFO] Connection: Connection opened
2023-03-23 15:48:36 [INFO] UdsClient: DiagnosticSessionControl<0x10> - Switching session to defaultSession (0x01)
2023-03-23 15:48:36 [DEBUG] Connection: Sending 2 bytes : [b'1001']
2023-03-23 15:48:37 [DEBUG] Connection: Received 6 bytes : [b'5001003200c8']
2023-03-23 15:48:37 [INFO] UdsClient: Received positive response for service DiagnosticSessionControl (0x10) from server.

However, when I tried to make it works with pcan, it returned "PcanCanInitializationError".

pylessard commented 1 year ago

Looks like you have issue with making python-can work with pcan. That is a bit out of the scope of this project. Try to debug your bus object and sends raw messages without uds or isotp.

You may want to check with the python-can community

catmemo commented 1 year ago

Hi pylessard,

Thank you for your patience, but it works well in sending and receiving messages. As you see:

image

I'll review my code later. Thanks again!

pylessard commented 1 year ago

Out of curiosity, are you playing with threads? This lib is not thread safe. Send and recv are, but not the other methods

catmemo commented 1 year ago

Hi, Yes~ create the bus and call them in threads~ So this is the problem?

pylessard commented 1 year ago

If you don't call uds or isotp layer across threads, that should be fine. The isotp connection is thread safe.

catmemo commented 1 year ago

I understand that you use your bus object for other functionality. You probably encounter an issue that many are encountering. Your other feature consume the messages from the b us queue, meaning that the isotp stack never receives them, therefore UDS throw a Timeout error.

The clean solution would be to modify the CanStack object to use a bus listener that duplicates each messages before consuming them. That's on my todo list (pylessard/python-can-isotp#76)

There is many workaround to this. The simplest is that you write your own txfn and rxfn function and use the TransportLayer object. The glue code is easy to write, see how I did : https://github.com/pylessard/python-can-isotp/blob/master/isotp/protocol.py#L1108 BAsically, you can make a dispatching mechanism that feeds only the isotp message to the stack and redirect other message to the right portion of your project.

You can also try to do the lsitener approach, I accept PRs :)

There is at least 5 or 6 other issues on the isotp project reporting something similar.

Hi, pylessard

I tried to take your advice but failed again, there is my code:

class CanStack(isotp.TransportLayer):

    def _tx_canbus_3plus(self, msg):
        if msg.arbitration_id == 1835 or msg.arbitration_id == 1963:
            self.bus.send(can.Message(arbitration_id=msg.arbitration_id, data=msg.data, is_extended_id=msg.is_extended_id, is_fd=msg.is_fd, bitrate_switch=msg.bitrate_switch))

    def _tx_canbus_3minus(self, msg):
        if msg.arbitration_id == 1835 or msg.arbitration_id == 1963:
            self.bus.send(can.Message(arbitration_id=msg.arbitration_id, data=msg.data, extended_id=msg.is_extended_id, is_fd=msg.is_fd, bitrate_switch=msg.bitrate_switch))

    def rx_canbus(self):
        msg = self.bus.recv(self.timeout)
        if msg is not None:
            if msg.arbitration_id == 1835 or msg.arbitration_id == 1963:
                return isotp.CanMessage(arbitration_id=msg.arbitration_id, data=msg.data, extended_id=msg.is_extended_id, is_fd=msg.is_fd, bitrate_switch=msg.bitrate_switch)

    def __init__(self, bus, timeout=0.0, *args, **kwargs):

        message_input_args = can.Message.__init__.__code__.co_varnames[:can.Message.__init__.__code__.co_argcount]
        if 'is_extended_id' in message_input_args:
            self.tx_canbus = self._tx_canbus_3plus
        else:
            self.tx_canbus = self._tx_canbus_3minus

        self.set_bus(bus)
        self.timeout = timeout
        isotp.TransportLayer.__init__(self, rxfn=self.rx_canbus, txfn=self.tx_canbus, *args, **kwargs)

    def set_bus(self, bus):
        if not isinstance(bus, can.BusABC):
            raise ValueError('bus must be a python-can BusABC object')
        self.bus = bus

And the response:

2023-03-28 20:19:41 [INFO] Connection: Connection opened
2023-03-28 20:19:45 [INFO] UdsClient: DiagnosticSessionControl<0x10> - Switching session to defaultSession (0x01)
2023-03-28 20:19:45 [DEBUG] Connection: Sending 2 bytes : [b'1001']
Exception in thread Thread-3:
2023-03-28 20:19:55 [DEBUG] Connection: No data received: [TimeoutException] - Did not receive frame IsoTP Transport layer in time (timeout=10 sec) 
2023-03-28 20:19:55 [ERROR] UdsClient: [TimeoutException] : Did not receive response in time. P2 timeout time has expired (timeout=10.000 sec)
Traceback (most recent call last):
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 1647, in send_request
    payload = self.conn.wait_frame(timeout=timeout_value, exception=True)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\connections.py", line 68, in wait_frame
    frame = self.specific_wait_frame(timeout=timeout)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\connections.py", line 490, in specific_wait_frame
    raise TimeoutException("Did not receive frame IsoTP Transport layer in time (timeout=%s sec)" % timeout)
udsoncan.exceptions.TimeoutException: Did not receive frame IsoTP Transport layer in time (timeout=10 sec)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:\Development_Tools\Python\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "D:\Development_Tools\Python\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "D:\Development_Tasks\CANSimulator\send_diag_request.py", line 258, in send_diag_session_control
    session_response = self.client.change_session(DiagnosticSessionControl.Session.defaultSession)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 131, in decorated
    return func(self, *args, **kwargs)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 189, in change_session
    response = self.send_request(req)
  File "D:\Development_Tools\Python\lib\site-packages\udsoncan\client.py", line 1655, in send_request
    raise TimeoutException('Did not receive response in time. %s time has expired (timeout=%.3f sec)' % (timeout_name_to_report, timeout_value))
udsoncan.exceptions.TimeoutException: Did not receive response in time. P2 timeout time has expired (timeout=10.000 sec)

And I have to say it got the correct response in PCAN-View: image

Could you please help to check when you are free? Thanks in advance!

catmemo commented 1 year ago

@pylessard

pylessard commented 1 year ago

If you have another section of your program rwading the bus, it will deplete the rx queue of the bus. You need to read a message and based on the ID, redirect to isotp or aomething else.

The best solution is to use a listener

catmemo commented 1 year ago

If you have another section of your program rwading the bus, it will deplete the rx queue of the bus. You need to read a message and based on the ID, redirect to isotp or aomething else.

The best solution is to use a listener

Hi, I solve this issue with this method, thanks again!

kongyun123 commented 1 year ago

如果你有程序的另一部分在总线上旋转,它将耗尽总线的 rx 队列。您需要阅读一条消息,并根据 ID 重定向到 isotp 或 aomething else。 最好的解决方案是使用侦听器

嗨,我用这种方法解决了这个问题,再次感谢!

How to use this method to solve it, I do not understand