From 937babbe90b5306f1a986a0436c534342db96e9c Mon Sep 17 00:00:00 2001 From: Kirill <33geek@gmail.com> Date: Wed, 14 Jan 2026 18:57:16 +0300 Subject: [PATCH 1/2] Refactoring J2534: - More readable TxStatus, ConnectFlags and RxStatus; - add method check_connection_opened(); - Fixing overcalling PassThruReadMsgs: Increase Timeout(from 1 to 100 ms) in call PassThruReadMsgs. --- udsoncan/connections.py | 107 +++++++++++----------------------------- udsoncan/j2534.py | 99 +++++++++++++++++++++++++++++++------ 2 files changed, 112 insertions(+), 94 deletions(-) diff --git a/udsoncan/connections.py b/udsoncan/connections.py index 09af001..576925e 100755 --- a/udsoncan/connections.py +++ b/udsoncan/connections.py @@ -23,7 +23,7 @@ _import_isotp_err = e try: - from udsoncan.j2534 import J2534, TxStatusFlag, Protocol_ID, Error_ID, Ioctl_Flags, Ioctl_ID, SCONFIG_LIST + from udsoncan.j2534 import J2534, Protocol_ID, Error_ID, Ioctl_Flags, Ioctl_ID, SCONFIG_LIST _import_j2534_err = None except Exception as e: _import_j2534_err = e @@ -63,8 +63,7 @@ def send(self, data: Union[bytes, Request, Response], timeout: Optional[float] = :returns: None """ - if not self.is_open(): - raise RuntimeError("Connection is not opened") + self.check_connection_opened() if isinstance(data, Request) or isinstance(data, Response): payload = data.get_payload() @@ -79,6 +78,10 @@ def send(self, data: Union[bytes, Request, Response], timeout: Optional[float] = else: self.specific_send(payload) + def check_connection_opened(self): + if not self.is_open(): + raise RuntimeError(self.__class__.__name__ + ' is not opened') + def wait_frame(self, timeout: Optional[float] = None, exception: bool = False) -> Optional[bytes]: """Waits for the reception of a frame of data from the underlying transport protocol @@ -92,8 +95,7 @@ def wait_frame(self, timeout: Optional[float] = None, exception: bool = False) - :returns: Received data :rtype: bytes or None """ - if not self.is_open(): - raise RuntimeError("Connection is not opened") + self.check_connection_opened() try: frame = self.specific_wait_frame(timeout=timeout) @@ -243,21 +245,13 @@ def specific_send(self, payload: bytes, timeout: Optional[float] = None) -> None self.sock.send(payload) def specific_wait_frame(self, timeout: Optional[float] = None) -> Optional[bytes]: - if not self.opened: - raise RuntimeError("Connection is not open") + self.check_connection_opened() - timedout = False - frame = None try: - frame = self.rxqueue.get(block=True, timeout=timeout) + return self.rxqueue.get(block=True, timeout=timeout) except queue.Empty: - timedout = True - - if timedout: raise TimeoutException("Did not received frame in time (timeout=%s sec)" % timeout) - return frame - def empty_rxqueue(self) -> None: while not self.rxqueue.empty(): self.rxqueue.get() @@ -359,22 +353,13 @@ def specific_send(self, payload: bytes, timeout: Optional[float] = None) -> None self.tpsock.send(payload) def specific_wait_frame(self, timeout: Optional[float] = None) -> Optional[bytes]: - if not self.opened: - raise RuntimeError("Connection is not open") + self.check_connection_opened() - timedout = False - frame = None try: - frame = self.rxqueue.get(block=True, timeout=timeout) - + return self.rxqueue.get(block=True, timeout=timeout) except queue.Empty: - timedout = True - - if timedout: raise TimeoutException("Did not received ISOTP frame in time (timeout=%s sec)" % timeout) - return frame - def empty_rxqueue(self) -> None: while not self.rxqueue.empty(): self.rxqueue.get() @@ -442,17 +427,12 @@ def specific_send(self, payload: bytes, timeout: Optional[float] = None) -> None self.touserqueue.put(payload, block=True, timeout=timeout) def specific_wait_frame(self, timeout: Optional[float] = None) -> Optional[bytes]: - if not self.opened: - raise RuntimeError("Connection is not open") + self.check_connection_opened() - timedout = False frame = None try: frame = self.fromuserqueue.get(block=True, timeout=timeout) except queue.Empty: - timedout = True - - if timedout: raise TimeoutException("Did not receive frame from user queue in time (timeout=%s sec)" % timeout) if self.mtu is not None: @@ -577,8 +557,7 @@ def specific_send(self, payload: bytes, timeout: Optional[float] = None) -> None self.isotp_layer.send(payload, send_timeout=timeout) def specific_wait_frame(self, timeout: Optional[float] = None) -> Optional[bytes]: - if not self.opened: - raise RuntimeError("Connection is not opened") + self.check_connection_opened() frame = self.isotp_layer.recv(block=True, timeout=timeout) if frame is None: @@ -649,25 +628,15 @@ def specific_send(self, payload: bytes, timeout: Optional[float] = None): self.toIsoTPQueue.put(bytearray(payload)) # isotp.protocol.TransportLayer uses byte array. udsoncan is strict on bytes format def specific_wait_frame(self, timeout: Optional[float] = None) -> Optional[bytes]: - if not self.opened: - raise RuntimeError("Connection is not open") + self.check_connection_opened() - timedout = False - frame = None try: frame = self.fromIsoTPQueue.get(block=True, timeout=timeout) + # isotp.protocol.TransportLayer uses bytearray. udsoncan is strict on bytes format + return bytes(frame) except queue.Empty: - timedout = True - - if timedout: raise TimeoutException("Did not receive IsoTP frame from the Transport layer in time (timeout=%s sec)" % timeout) - if frame is None: - return None - - # isotp.protocol.TransportLayer uses bytearray. udsoncan is strict on bytes format - return bytes(frame) - def empty_rxqueue(self) -> None: while not self.fromIsoTPQueue.empty(): self.fromIsoTPQueue.get() @@ -804,7 +773,7 @@ def __init__(self, def open(self) -> "J2534Connection": self.exit_requested = False - self.sem = threading.Semaphore() + self.interfaceSemaphore = threading.Semaphore() self.rxthread = threading.Thread(target=self.rxthread_task, daemon=True) self.rxthread.start() self.opened = True @@ -822,15 +791,15 @@ def is_open(self) -> bool: def rxthread_task(self) -> None: while not self.exit_requested: - self.sem.acquire() + self.interfaceSemaphore.acquire() try: - result, data, numMessages = self.interface.PassThruReadMsgs(self.channelID, self.protocol.value, 1, 1) + result, data, numMessages = self.interface.PassThruReadMsgs(self.channelID, self.protocol.value, pNumMsgs=1) if data is not None: self.rxqueue.put(data) except Exception: self.logger.critical("Exiting J2534 rx thread") self.exit_requested = True - self.sem.release() + self.interfaceSemaphore.release() time.sleep(0.001) def log_last_operation(self, exec_method: str, with_raise = False) -> None: @@ -860,27 +829,19 @@ def specific_send(self, payload: bytes, timeout: Optional[float] = None): timeout = 0 if timeout is None else timeout # Fix for avoid ERR_CONCURRENT_API_CALL. Stop reading - self.sem.acquire() + self.interfaceSemaphore.acquire() self.result = self.interface.PassThruWriteMsgs(self.channelID, payload, self.protocol.value, Timeout=int(timeout * 1000)) self.log_last_operation('PassThruWriteMsgs', with_raise=True) - self.sem.release() + self.interfaceSemaphore.release() def specific_wait_frame(self, timeout: Optional[float] = None) -> Optional[bytes]: - if not self.opened: - raise RuntimeError("J2534 Connection is not open") + self.check_connection_opened() - timedout = False - frame = None try: - frame = self.rxqueue.get(block=True, timeout=timeout) + return self.rxqueue.get(block=True, timeout=timeout) except queue.Empty: - timedout = True - - if timedout: raise TimeoutException("Did not received response from J2534 RxQueue (timeout=%s sec)" % timeout) - return frame - def empty_rxqueue(self) -> None: while not self.rxqueue.empty(): self.rxqueue.get() @@ -942,21 +903,13 @@ def specific_send(self, payload: bytes, timeout: Optional[float] = None): self.rxqueue.put(self.ResponseData[payload]) def specific_wait_frame(self, timeout: Optional[float] = None) -> Optional[bytes]: - if not self.opened: - raise RuntimeError("Fake Connection is not open") + self.check_connection_opened() - timedout = False - frame = None try: - frame = self.rxqueue.get(block=True, timeout=timeout) + return self.rxqueue.get(block=True, timeout=timeout) except queue.Empty: - timedout = True - - if timedout: raise TimeoutException("Did not received response from J2534 RxQueue (timeout=%s sec)" % timeout) - return frame - def empty_rxqueue(self) -> None: while not self.rxqueue.empty(): self.rxqueue.get() @@ -1002,13 +955,11 @@ def __init__(self, rx_id: int, tx_id: int, name: Optional[str] = None, *args, ** self.opened = False def specific_send(self, payload: bytes, timeout: Optional[float] = None) -> None: - if self.conn is None or not self.opened: - raise RuntimeError("Connection is not opened") + self.check_connection_opened() self.conn.send(payload) def specific_wait_frame(self, timeout: Optional[float] = None) -> Optional[bytes]: - if not self.opened or self.conn is None: - raise RuntimeError("Connection is not open") + self.check_connection_opened() frame = cast(Optional[bytes], self.conn.recv(timeout)) @@ -1034,7 +985,7 @@ def empty_rxqueue(self) -> None: self.conn.empty() def is_open(self) -> bool: - return self.opened + return self.conn and self.opened def __enter__(self) -> "SyncAioIsotpConnection": return self diff --git a/udsoncan/j2534.py b/udsoncan/j2534.py index e5a7537..7c32f96 100755 --- a/udsoncan/j2534.py +++ b/udsoncan/j2534.py @@ -61,9 +61,12 @@ def __init__(self, windll, rxid, txid): self.hDLL = ctypes.cdll.LoadLibrary(windll) self.rxid = rxid.to_bytes(4, 'big') self.txid = txid.to_bytes(4, 'big') - # Determine mode ID29 or ID11 - self.txConnectFlags = TxStatusFlag.ISO15765_CAN_ID_29.value if txid >> 11 else TxStatusFlag.ISO15765_CAN_ID_11.value - self.txFlags = self.txConnectFlags | TxStatusFlag.ISO15765_FRAME_PAD.value + self.txFlags = TxFlags.ISO15765_FRAME_PAD.value + self.connectFlags = ConnectFlags.NONE.value + # Determine mode ID29 or ID11 + if txid >> 11: + self.txFlags |= TxFlags.CAN_29_BIT_ID.value + self.connectFlags |= ConnectFlags.CAN_29_BIT_ID.value self.logger = logging.getLogger() @@ -192,7 +195,7 @@ def PassThruConnect(self, deviceID, protocol, baudrate, pChannelID=None): if not pChannelID: pChannelID = c_ulong() - result = dllPassThruConnect(deviceID, protocol, self.txConnectFlags, baudrate, byref(pChannelID)) + result = dllPassThruConnect(deviceID, protocol, self.connectFlags, baudrate, byref(pChannelID)) return Error_ID(hex(result)), pChannelID def PassThruClose(self, DeviceID): @@ -212,15 +215,18 @@ def PassThruReadMsgs(self, ChannelID, protocol, pNumMsgs=1, Timeout=100): while 1: # breakpoint() result = dllPassThruReadMsgs(ChannelID, byref(pMsg), byref(pNumMsgs), c_ulong(Timeout)) - if Error_ID(hex(result)) == Error_ID.ERR_BUFFER_EMPTY or pNumMsgs == 0: + if hex(result) == Error_ID.ERR_BUFFER_EMPTY.value or pNumMsgs == 0: return None, None, 0 - elif pMsg.RxStatus in [0, 0x100, 0x110]: - return Error_ID(hex(result)), bytes(pMsg.Data[4:pMsg.DataSize]), pNumMsgs + + if pMsg.RxStatus & (RxStatus.TX_INDICATION.value | RxStatus.TX_MSG_TYPE.value): + continue + + return Error_ID(hex(result)), bytes(pMsg.Data[4:pMsg.DataSize]), pNumMsgs def PassThruWriteMsgs(self, ChannelID, Data, protocol, pNumMsgs=1, Timeout=1000): txmsg = PASSTHRU_MSG() txmsg.TxFlags = self.txFlags - txmsg.ProtocolID = protocol; + txmsg.ProtocolID = protocol Data = self.txid + Data self.logger.info("Sending data: " + str(Data.hex())) @@ -289,7 +295,7 @@ def PassThruStartMsgFilter(self, ChannelID, protocol): msgPattern.Data[i] = self.rxid[i] msgFlow = PASSTHRU_MSG() - msgFlow.ProtocolID = protocol; + msgFlow.ProtocolID = protocol msgFlow.TxFlags = self.txFlags msgFlow.DataSize = 4 msgFlow.RxStatus = msgFlow.ExtraDataIndex = 0xCCCC_CCCC @@ -354,15 +360,76 @@ class Filter(Enum): FLOW_CONTROL_FILTER = 0x00000003 -class TxStatusFlag(Enum): - ISO15765_CAN_ID_BOTH = 0x00000800 - ISO15765_CAN_ID_29 = 0x00000100 - ISO15765_CAN_ID_11 = 0x00000040 +class ConnectFlags(Enum): + NONE = 0 + CAN_29_BIT_ID = 0x100 + ISO9141_NO_CHECKSUM = 0x200 + CAN_ID_BOTH = 0x800 + ISO9141_K_LINE_ONLY = 0x1000 + + +class TxFlags(Enum): + NONE = 0 + # 0 = no padding + # 1 = pad all flow controlled messages to a full CAN frame using zeroes ISO15765_FRAME_PAD = 0x00000040 + + ISO15765_ADDR_TYPE = 0x00000080 + CAN_29_BIT_ID = 0x00000100 + + # 0 = Interface message timing as specified in ISO 14230 + # 1 = After a response is received for a physical request, the wait time shall be reduced to P3_MIN + # Does not affect timing on responses to functional requests WAIT_P3_MIN_ONLY = 0x00000200 - SW_CAN_HV_TX = 0x00000400 # OP2.0: Not supported - SCI_MODE = 0x00400000 # OP2.0: Not supported - SCI_TX_VOLTAGE = 0x00800000 # OP2.0: Not supported + + SW_CAN_HV_TX = 0x00000400 + + # 0 = Transmit using SCI Full duplex mode + # 1 = Transmit using SCI Half duplex mode + SCI_MODE = 0x00400000 + + # 0 = no voltage after message transmit + # 1 = apply 20V after message transmit + SCI_TX_VOLTAGE = 0x00800000 + + DT_PERIODIC_UPDATE = 0x10000000 + + +class RxStatus(Enum): + NONE = 0 + # 0 = received + # 1 = transmitted + TX_MSG_TYPE = 0x00000001 + + # 0 = Not a start of message indication + # 1 = First byte or frame received + START_OF_MESSAGE = 0x00000002 + ISO15765_FIRST_FRAME = 0x00000002 + + ISO15765_EXT_ADDR = 0x00000080 + + # 0 = No break received + # 1 = Break received + RX_BREAK = 0x00000004 + + # 0 = No TxDone + # 1 = TxDone + TX_INDICATION = 0x00000008 + TX_DONE = 0x00000008 + + # 0 = No Error + # 1 = Padding Error + ISO15765_PADDING_ERROR = 0x00000010 + + # 0 = no extended address, + # 1 = extended address is first byte after the CAN ID + ISO15765_ADDR_TYPE = 0x00000080 + + CAN_29_BIT_ID = 0x00000100 + + SW_CAN_NS_RX = 0x00040000 + SW_CAN_HS_RX = 0x00020000 + SW_CAN_HV_RX = 0x00010000 class Ioctl_ID(Enum): From 24f4e239250a5f213e8523477bb4a85c7536b5b9 Mon Sep 17 00:00:00 2001 From: Kirill <33geek@gmail.com> Date: Wed, 14 Jan 2026 20:06:33 +0300 Subject: [PATCH 2/2] Ignore start_of_message, limit=20 --- udsoncan/j2534.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/udsoncan/j2534.py b/udsoncan/j2534.py index 7c32f96..47dfb04 100755 --- a/udsoncan/j2534.py +++ b/udsoncan/j2534.py @@ -206,7 +206,7 @@ def PassThruDisconnect(self, ChannelID): result = dllPassThruDisconnect(ChannelID) return Error_ID(hex(result)) - def PassThruReadMsgs(self, ChannelID, protocol, pNumMsgs=1, Timeout=100): + def PassThruReadMsgs(self, ChannelID, protocol, pNumMsgs=1, Timeout=20): pMsg = PASSTHRU_MSG() pMsg.ProtocolID = protocol @@ -218,7 +218,7 @@ def PassThruReadMsgs(self, ChannelID, protocol, pNumMsgs=1, Timeout=100): if hex(result) == Error_ID.ERR_BUFFER_EMPTY.value or pNumMsgs == 0: return None, None, 0 - if pMsg.RxStatus & (RxStatus.TX_INDICATION.value | RxStatus.TX_MSG_TYPE.value): + if pMsg.RxStatus & (RxStatus.TX_INDICATION.value | RxStatus.TX_MSG_TYPE.value | RxStatus.START_OF_MESSAGE.value): continue return Error_ID(hex(result)), bytes(pMsg.Data[4:pMsg.DataSize]), pNumMsgs