You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用python-can Notifier+PCAN-USB时丢帧导致UDS(udsoncan/isotp)请求超时

使用python-can Notifier+PCAN-USB时丢帧导致UDS(udsoncan/isotp)请求超时

我之前做UDS over CAN开发时碰到过几乎一模一样的问题!核心原因其实很直白:python-can的Notifier会独占CAN Bus的接收通道——启动Notifier后,它会在后台持续调用bus.recv()把所有帧读走交给日志器,导致你的isotp栈(以及上层UDS客户端)根本拿不到ECU的回复帧。虽然外部抓包能看到ECU的响应,但这些帧已经被Notifier“吃掉”了,没传到isotp那里,自然就触发超时了。

你提到试过BufferedReader但没效果,应该是用法不对。我给你两个亲测有效的解决方案:


方案一:手动线程转发帧(最可靠)

直接绕开Notifier的独占问题,自己开一个后台线程,把从Bus收到的每帧同时传给日志器和isotp栈。这样所有帧的流向完全由你控制,不会出现抢帧冲突。

修改后的完整代码如下:

import can
import isotp
import udsoncan
import threading
from udsoncan.connections import PythonIsoTpConnection
from udsoncan.client import Client
from udsoncan.exceptions import TimeoutException
from udsoncan.configs import default_client_config

# 初始化PCAN总线
bus = can.interface.Bus(
    bustype="pcan",
    channel="PCAN_USBBUS1",
    bitrate=500000
)

# 配置ISOTP参数
isotp_params = {
    'stmin': 5,
    'blocksize': 40,
    'wftmax': 0,
    'tx_data_length': 8,
    'tx_padding': 0x55,
    'rx_flowcontrol_timeout': 5000,
    'rx_consecutive_frame_timeout': 1000,
    'max_frame_size': 4095,
    'can_fd': False
}

# 设置ISOTP地址
addr = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=0x7E0, rxid=0x7E8)

# 初始化ISOTP栈:不直接传入bus,用tx_callback处理发送,后续手动feed接收帧
stack = isotp.CanStack(
    address=addr,
    error_handler=None,
    params=isotp_params,
    tx_callback=lambda frame: bus.send(frame)  # 发送委托给原始bus
)

# 初始化BLF日志器
final_filename = "log_file.blf"
logger = can.BLFWriter(final_filename)

# 定义帧转发线程:同时给日志器和ISOTP栈喂帧
def frame_forwarder(bus, logger, stack):
    while True:
        try:
            # 从总线接收帧
            frame = bus.recv(timeout=1.0)
            if frame is None:
                continue
            # 1. 写入BLF日志
            logger.on_message_received(frame)
            # 2. 把帧传给ISOTP栈处理
            stack.feed(frame)
        except Exception as e:
            print(f"转发线程出错: {str(e)}")
            break

# 启动后台转发线程(设为daemon,主程序退出时自动结束)
forward_thread = threading.Thread(target=frame_forwarder, args=(bus, logger, stack), daemon=True)
forward_thread.start()

# 启动UDS客户端并发送请求
conn = PythonIsoTpConnection(stack)
with Client(conn, request_timeout=2, config=default_client_config) as client:
    try:
        response = client.tester_present()  # 发送3E 00命令
        print("Response:", response)
    except TimeoutException:
        print("UDS request timed out!")

这个方法完全可控,不管CAN流量多大,日志和UDS通信都能正常工作,不会出现丢帧。


方案二:用BufferedReader共享帧

如果你还是想用Notifier,可以通过AsyncBufferedReader把帧共享给Notifier和ISOTP栈。需要把ISOTP栈的Bus替换成一个包装类,让它从BufferedReader读取帧:

import can
import isotp
import udsoncan
from udsoncan.connections import PythonIsoTpConnection
from udsoncan.client import Client
from udsoncan.exceptions import TimeoutException
from udsoncan.configs import default_client_config

# 初始化PCAN总线
bus = can.interface.Bus(
    bustype="pcan",
    channel="PCAN_USBBUS1",
    bitrate=500000
)

# 定义包装类:让ISOTP栈从BufferedReader读,从原始Bus发
class BufferedBusWrapper:
    def __init__(self, buffered_reader, send_bus):
        self.buffered_reader = buffered_reader
        self.send_bus = send_bus
    
    def recv(self, timeout=None):
        return self.buffered_reader.recv(timeout=timeout)
    
    def send(self, frame, timeout=None):
        return self.send_bus.send(frame, timeout=timeout)

# 初始化AsyncBufferedReader和Notifier
reader = can.AsyncBufferedReader()
final_filename = "log_file.blf"
logger = can.BLFWriter(final_filename)
notifier = can.Notifier(bus, [reader, logger])  # 帧同时发给reader和logger

# 配置ISOTP参数和地址
isotp_params = {
    'stmin': 5,
    'blocksize': 40,
    'wftmax': 0,
    'tx_data_length': 8,
    'tx_padding': 0x55,
    'rx_flowcontrol_timeout': 5000,
    'rx_consecutive_frame_timeout': 1000,
    'max_frame_size': 4095,
    'can_fd': False
}
addr = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=0x7E0, rxid=0x7E8)

# 用包装后的BufferedBus初始化ISOTP栈
wrapped_bus = BufferedBusWrapper(reader, bus)
stack = isotp.CanStack(bus=wrapped_bus, address=addr, error_handler=None, params=isotp_params)

# 启动UDS客户端
conn = PythonIsoTpConnection(stack)
with Client(conn, request_timeout=2, config=default_client_config) as client:
    try:
        response = client.tester_present()
        print("Response:", response)
    except TimeoutException:
        print("UDS request timed out!")

# 程序结束时清理Notifier
notifier.stop()

这个方案依赖python-can的缓冲机制,低流量场景下没问题,但高负载时可能出现缓冲溢出,所以还是方案一更稳妥。


总结来说:本质问题就是Notifier和ISOTP栈不能同时直接从同一个Bus调用recv,必须让它们共享帧的来源。方案一的手动转发是最可靠的解决方式,我自己在项目里一直用这个方法,完全解决了超时问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 06:58:05