udsoncan: Unified Diagnostic Services (UDS) Protocol
udsoncan is a Python library that implements the Unified Diagnostic Service (UDS) protocol (ISO-14229), widely used in the automotive industry for vehicle diagnostics and programming. It provides client and server capabilities, support for various communication connections (CAN, ISO-TP, J2534, DoIP), and tools for managing Data Identifiers (DIDs) and Diagnostic Trouble Codes (DTCs). The library is actively maintained, with version 1.25.2 being the latest, and typically sees several minor releases per year to add features and address bug fixes.
Common errors
-
ModuleNotFoundError: No module named 'can'
cause You are attempting to use a CAN-based connection (e.g., `IsoTPSocketConnection`) but the `python-can` library is not installed.fixInstall the `python-can` library: `pip install python-can` (or `pip install udsoncan[can]` to install `udsoncan` with its `can` extras). -
udsoncan.exceptions.TimeoutException: No response received from the server (request=...) (timeout=...s)
cause The UDS server did not respond within the client's configured request timeout (P2 or P2* timeout). This can be due to an inactive server, incorrect CAN IDs, a physical connection issue, or an overloaded/slow server.fixVerify the UDS server is active and accessible. Double-check CAN IDs, physical wiring, and baud rates. If the server is genuinely slow, increase `client.request_timeout` or the P2/P2* timeout settings in the client configuration. -
AttributeError: module 'udsoncan.services' has no attribute 'MyCustomService'
cause You are trying to import or reference a UDS service class that does not exist in the `udsoncan.services` module or is misspelled. Services like `ReadDataByIdentifier`, `DiagnosticSessionControl` are defined there.fixCheck the `udsoncan.services` module for the correct class name for the UDS service you intend to use. Ensure your import statement and class reference match the library's API (e.g., `from udsoncan.services import ReadDataByIdentifier`). -
udsoncan.exceptions.NegativeResponseException: Server responded with negative response code 0x78 (Response Pending)
cause The UDS server sent a 'Response Pending' NRC (0x78) but then failed to send a final positive response or another NRC within the P2* timeout. This often indicates a server processing delay exceeding the client's tolerance, or a server internal issue.fixIncrease the `P2_star_timeout` in the client's configuration if the server is known to take a long time to process. Investigate the server-side behavior if it consistently fails to respond after 0x78. For `udsoncan` v1.24.0+, you can use `nrc_78_received_callback` for specific handling.
Warnings
- breaking The J2534 module has undergone significant refactoring and improvements across versions (v1.23.2, v1.24.0, v1.25.2). If you are using `J2534Connection`, code written for older versions (e.g., prior to v1.23.2) may require adjustments to adapt to the updated API and internal workings.
- gotcha When using `J2534Connection` or other connections with high concurrency, versions prior to 1.24.0 had a known concurrency issue that could lead to unstable behavior. This was addressed in v1.24.0.
- gotcha Versions of `udsoncan` prior to 1.23.1 provided less informative error messages when a timeout occurred after an NRC 0x78 (Response Pending) was received. Version 1.24.0 further introduced a callback mechanism for handling NRC 0x78.
- gotcha Version 1.22.0 introduced a bug that broke compatibility with Python 3.7. This was quickly fixed in version 1.22.1.
- gotcha A performance degradation was introduced in v1.21 when requesting Data Identifiers (DIDs) with a large configuration. This issue was resolved in v1.22.0.
Install
-
pip install udsoncan -
pip install udsoncan[can]
Imports
- Client
from udsoncan.client import Client
- Server
from udsoncan.server import Server
- IsoTPSocketConnection
from udsoncan.connections import IsoTPSocketConnection
- LoopbackConnection
from udsoncan.connections import LoopbackConnection
- services
import udsoncan.services as services
- Dtc
from udsoncan import Dtc
- NegativeResponseException
from udsoncan.exceptions import NegativeResponseException
- TimeoutException
from udsoncan.exceptions import TimeoutException
Quickstart
import udsoncan
from udsoncan.client import Client
from udsoncan.server import Server
from udsoncan.connections import LoopbackConnection
from udsoncan.services import ReadDataByIdentifier, DiagnosticSessionControl
from udsoncan.exceptions import NegativeResponseException
# Define a simple UDS server application logic
class MyServerApplication:
def __init__(self):
self.data_store = {
0xF190: b'PythonUDS', # Example VIN
0xF180: b'v1.0' # Example Software version
}
self.current_session = 1 # Default session
def read_data_by_identifier(self, did, access_level=None):
if did in self.data_store:
return self.data_store[did]
raise NegativeResponseException(0x10) # SubFunction Not Supported if DID is unknown
def diagnostic_session_control(self, session_id, access_level=None):
if session_id in [1, 2, 3]: # Default, Programming, Extended
self.current_session = session_id
return b''
raise NegativeResponseException(0x10) # SubFunction Not Supported if session is unknown
def get_did_config(self):
# Define how DIDs are encoded/decoded for the server
return {
0xF190: {'data_size': 9, 'codec': udsoncan.AsciiCodec},
0xF180: {'data_size': 4, 'codec': udsoncan.AsciiCodec},
}
# Configure client and server to use an in-memory loopback connection
conn = LoopbackConnection(name='test_bus')
# Server setup
server_app = MyServerApplication()
server_config = udsoncan.ServerConfiguration()
server_config.default_response_pending_timeout = 200 # ms
server_config.set_did_config(server_app.get_did_config())
# Link server services to the application methods
server_config.request_handler = {
ReadDataByIdentifier: server_app.read_data_by_identifier,
DiagnosticSessionControl: server_app.diagnostic_session_control
}
server = Server(conn, server_config)
server.start()
# Client setup
client = Client(conn, request_timeout=2) # 2 seconds timeout
try:
client.open()
# Example 1: Read VIN (DID F190)
response = client.read_data_by_identifier([0xF190])
vin = response.values[0xF190]
print(f"VIN: {vin.decode('ascii')}")
# Example 2: Change diagnostic session to Extended Diagnostic Session (ID 0x03)
response = client.diagnostic_session_control(3)
print(f"Session changed to: {response.service_data.session_id} (Success)")
# Example 3: Try reading an unsupported DID (will raise NegativeResponseException)
try:
client.read_data_by_identifier([0x1234])
except NegativeResponseException as e:
print(f"Tried reading unsupported DID 0x1234: Received NRC {hex(e.response.code)}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
finally:
client.close()
server.stop()
print("Client and server stopped.")