.. _diagnostics: Getting Started with Diagnostics ================================ This tutorial will cover the basic setup for a UDS (ISO-14229) diagnostics interface via DoIP (ISO-13400). For this tutorial, it will be assumed that the network interface is already configured elsewhere and associated with a Python variable arbitrarily named :py:attr:`network` and a VSpy X application object named :py:attr:`app`. Setup (Transport Layer) ----------------------- Instatiating and configuring a DoIP client interface is fairly straightforward: .. code:: python import vspyx transportLayer = vspyx.Diagnostics.ISO13400_2.New() transportLayer.Initialize(app, "DoIP") transportLayer.Attach(network) .. note:: The argument passed into :py:func:`Attach` must be a configured :py:attr:`vspyx.TCPIP.Network` object (see `Extras` for more info). Add a handler for reception of DoIP NACK messages (optional): .. code:: python def _OnDoIPNack(source, nackCode): print("Received nack code: ", nackCode) transportLayer.OnDoIPNack = _OnDoIPNack If the IP address or logical address of a target ECU are unknown, the information can be collected by the application when DoIP vehicle annoucements are made. Add a handler for reception of DoIP vehicle annoucements (optional): .. code:: python def _OnVehicleAnnouncement(ipAddress, entity): AddEntityInfoToAddressbook(ipAddress, entity) print("ECU detected at IP address %s with logical address %s, VIN= %s"%(ipAddress, hex(entity.Address), [hex(octet) for octet in entity.VIN])) transportLayer.OnVehicleAnnouncement = _OnVehicleAnnouncement .. note:: In the above example, :py:func:`AddEntityInfoToAddressbook` is a suggested function that may perform an action such as recording the vehicle entity info in an application database, etc. By default, the DoIP discovery broadcast address is `255.255.255.255` - if a different address is desired, it can be changed: .. code:: python BROADCAST_ADDRESS = "192.168.7.255" transportLayer.SetBroadcastAddress(BROADCAST_ADDRESS) At this point, the transport layer is configured and can be handed off to the next layer! Setup (Session Layer) --------------------- The session management layer manages session timeouts and keep-alive, message retransmission, and session changes. ISO-14229 standard session management is handled by an object of type :py:class:`vspyx.Diagnostics.ISO14229_2`. .. code:: python # Session layer configurations are being changed from default sessionParameters = vspyx.Diagnostics.ISO14229_2.Parameters() # sessionParameters.DeltaP2 = datetime.timedelta(milliseconds= 200) # sessionParameters.DeltaP6 = datetime.timedelta(milliseconds= 200) # sessionParameters.P2_server_max = datetime.timedelta(milliseconds= 250) # sessionParameters.P2star_server_max = datetime.timedelta(milliseconds= 5000) # sessionParameters.P2_client_max = datetime.timedelta(milliseconds= 250) # sessionParameters.P6_client_max = datetime.timedelta(milliseconds= 250) # sessionParameters.P2star_client_max = datetime.timedelta(milliseconds= 5000) # sessionParameters.P6star_client_max = datetime.timedelta(milliseconds= 5000) # Instantiate the session layer management object session = vspyx.Diagnostics.ISO14229_2.NewClient(sessionParameters) session.Initialize(app, "Session") session.Attach(transportLayer) Setup (Application Layer) ------------------------- The application layer manages basic application message transactions, inter-message timeouts, and reception/relaying of any received responses or requests. ISO-14229 standard application client is handled by an object of type :py:class:`vspyx.Diagnostics.ISO14229_1ClientApplicationLayerProtocol`. .. code:: python udsInterface = vspyx.Diagnostics.ISO14229_1ClientApplicationLayerProtocol.New() udsInterface.Initialize(app, "UDS Client") udsInterface.Attach(session) Setup (Application Services Layer) ---------------------------------- The application services layer manages the highest-level service capabilities of a client or server. Its functionality includes management of available service configuration as well as standard service request validation and simplified service invocation. ISO-14229 standard application services client is handled by an object of type :py:class:`vspyx.Diagnostics.ISO14229_ServiceClient`. .. code:: python services = vspyx.Diagnostics.ISO14229_ServiceClient.New() services.Initialize(app, "Service Client") services.Attach(udsInterface) Configuring the application services object to utilize services is done through the :py:func:`vspyx.Diagnostics.ISO14229_Services.ServiceConfig.ConfigureService` function. Below is an example of configuring predefined, standard services for use. .. code:: python # Configure predefined services # ServiceConfig.ConfigureService(name, supported_sessions, p4server_max, supported_subfunctions= None, security_requirements= None) service = services.ServiceConfig.ConfigureService("SessionControl", [ 1, 2, 3 ], datetime.timedelta(milliseconds= 10), [ 1, 2, 3 ], None) service = services.ServiceConfig.ConfigureService("TesterPresent", [ 1, 2, 3 ], datetime.timedelta(milliseconds= 10), [ 0 ], None) service = services.ServiceConfig.ConfigureService("ReadDataById", [ 1, 2, 3 ], datetime.timedelta(milliseconds= 30000), None, None) .. note:: The argument p4server_max is used to indicate the max amount of time a service may execute. This argument is most important for a server object, as this value determines whether it is possible for the server to transmit NRC(RCRPR) (`NRC 0x78`) if the service takes longer than (P2_server_keepalive_ratio * P2_server_max). Adding a custom service is similar to configuring a predefined one; however, the parameters must be completely provided and message interpreters must be provided. .. code:: python # Add custom services # Custom message interpreter handler def _CreateMessageWithSubfunction(pdu, message): return vspyx.Diagnostics.ISO14229_Services.MessageWithSubfunction(pdu, message) # ServiceConfig.AddService(sid, name, supported_sessions, datetime.timedelta(milliseconds= p4server_max), supported_subfunctions, security_requirements) service = services.ServiceConfig.AddService(0xBA, "Echo", [ 1 ], datetime.timedelta(milliseconds= 5000), [ 1 ], 0xFFFFFFFF) service.RequestDecoder = _CreateMessageWithSubfunction service.ResponseDecoder = _CreateMessageWithSubfunction .. note:: The RequestDecoder and ResponseDecoder handlers must return a type inheriting :py:class:`vspyx.Diagnostics.ISO14229_Services.MessageWithSubfunction` Starting Runtime ---------------- The components must be registered with the runtime environment: .. code:: python runtime = app.VehicleSpy.PrepareForStart() runtime.AddComponent(services) runtime.AddComponent(udsInterface) runtime.AddComponent(session) runtime.AddComponent(network) runtime.AddComponent(transportLayer) Use During Runtime ------------------ UDS Services can be be invoked directly through the :py:class:`vspyx.Diagnostics.ISO14229_ServiceClient` object. Addressing can be done either on a per-call basis, or by assigning the target address (`N_AI`) to the :py:attr:`vspyx.Diagnostics.ISO14229_ServiceClient.TargetAddress` attribute. .. code:: python # N_AI => (MessageType, SA, TA, TA_Type, AE) udsInterface.TargetAddress = (vspyx.Communication.ISOStandardizedServicePrimitiveInterface.MessageType.Diagnostics, SA, TA_PHYS, 0, None) Invoking a service using the `TargetAddress` attribute is done by providing `None` as the `target` argument to a service call: .. code:: python # Physical TesterPresent transaction, expecting response, implicit address (TargetAddress) result = udsInterface.TesterPresent(None, False) Below is an example of invoking a service using an explicit address: .. code:: python # Functional N_AI target_func = (vspyx.Communication.ISOStandardizedServicePrimitiveInterface.MessageType.Diagnostics, SA, TA_FUNC, 0, None) ... # Functional TesterPresent transaction, expecting no responses, explicit address result = udsInterface.TesterPresent(target_func, True) Use case: SessionControl(Extended) .. code:: python # Example: SessionControl result = services.SessionControl(2, None) if (result is None or len(result.Responses) == 0): print("No response received!") else: message = result.Responses[0] if (message.IsNegativeResponse): responseCode = int(message.NRC) sid = int(message.FailedSID) print("Received NRC", hex(responseCode), "for service", hex(sid)) else: print("SessionControl Response - Subfunction=", hex(message.Subfunction), "P2ServerMax=", message.P2ServerMax, "P2StarServerMax=", message.P2StarServerMax) Use case: ReadDataById(...) .. code:: python # Example: Read Data by Id result = services.ReadDataById([ 0xF101, 0xF107 ], None) if (result is None or len(result.Responses) == 0): print("No response received!") else: message = result.Responses[0] if (message.IsNegativeResponse): responseCode = int(message.NRC) sid = int(message.FailedSID) print("Received NRC", hex(responseCode), "for service", hex(sid)) else: handle = message.DataStart while (handle.IsValid()): did = message.ReadId(handle) signalSize = GetDIDSize(did) if (signalSize is None): # Unknown DID, unable to determine size signalSize = (handle.Size - handle.CurrentOffset) print("Unknown DID: %s"%(hex(did))) signalValue = message.ReadParameterData(handle, signalSize).bytes() print("DID", hex(did), "contains:", [hex(octet) for octet in signalValue]) .. note:: In the above example, function :py:func:`GetDIDSize` is assumed to be a user-defined script helper which resolves DID definition. Use case: ECUReset(HardReset) .. code:: python # Example: ECU Reset (hard reset) result = services.EcuReset(1, None) if (result is None or len(result.Responses) == 0): print("No response received!") else: message = result.Responses[0] if (message.IsNegativeResponse): responseCode = int(message.NRC) sid = int(message.FailedSID) print("Received NRC", hex(responseCode), "for service", hex(sid)) else: print("EcuReset Response - ResetType=", hex(message.ResetType)) powerDownTime = message.PowerDownTime if (powerDownTime > 0): print("Invalid response! Unexpected PowerDownTime included ", powerDownTime) Use case: Custom Service .. code:: python # Example: Custom service result = services.GenericService(vspyx.Core.BytesView(bytes([0xBA, 0x01, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10])), None) if (result is None or len(result.Responses) == 0): print("No response received!") else: message = result.Responses[0] if (message.IsNegativeResponse): responseCode = int(message.NRC) sid = int(message.FailedSID) print("Received NRC", hex(responseCode), "for service", hex(sid)) else: print("Custom Echo Response - Subfunction=", hex(message.Subfunction), "payload=", [hex(octet) for octet in message.ToRaw().bytes()[2:]]) Shutting Down ------------- The components will automatically shut down on application shutdown: .. code:: python runtime.ShutdownEnvironment() app.VehicleSpy.Stop() Extras: Network Interface Configuration --------------------------------------- Below is an example of configuring a network object utilizing a Windows Ethernet interface named `Local Area Connection` with MAC address `d0:50:99:5b:fa:07` via pcap and assigning it its own MAC and IP addresses: .. code:: python import vspyx from intrepidcs.vspyx.rpc.TCPIP import Network_pb2 # Ethernet device name and MAC address IF_AUTO_DETECT = True # Automatically detect network interface by name; otherwise, the device's MAC address must be provided IF_NAME = "Local Area Connection" # OS name of Ethernet interface device IF_MAC_ADDRESS = "d0:50:99:5b:fa:07" # Actual MAC address of the Ethernet interface device (only used if IF_AUTO_DETECT == False) IF_SOFT_MAC_ADDRESS = "00:FC:70:00:00:01" # Client network virtual interface MAC address IF_IP_ADDRESS = "192.168.7.100" # Client network interface IP address IF_IP_MASK = "255.255.255.0" # Client network interface IP netmask # Create application object if one already hasn't been created or provided app = vspyx.Core.Application.New() app.Initialize(loadAllModules=True) if (IF_AUTO_DETECT): # This will resolve an existing pcap device and channel object interfaceDetectionAttempts = 10 source = None while (interfaceDetectionAttempts > 0): try: interfaceDetectionAttempts -= 1 source = app.Resolver[IF_NAME] except: print("Waiting for interface %s..."%(IF_NAME)) time.sleep(1.0) if (source is None): print("Unable to resolve device %s"%(IF_NAME)) exit() source = app.VehicleSpy.AddSource(source.Description) for child in source.Discovery.Children: if (isinstance(child, vspyx.Communication.EthernetChannel)): channel = child break else: # This will initialize the pcap relationship with the specified device and resolve the channel object source = app.VehicleSpy.AddSource("pcap " + IF_MAC_ADDRESS) source.SearchFor() print(IF_NAME + " :", source.SourceState) if (source.SourceState != source.SourceState.Ready): print("Source is not ready!") exit() # Get the channel reference channel = app.Resolver[IF_NAME + " Discovery Channel"] # Interface Configuration interfaceConfig = Network_pb2.Interface() interfaceConfig.MACAddress = IF_SOFT_MAC_ADDRESS interfaceConfig.IPv4.Address = IF_IP_ADDRESS interfaceConfig.IPv4.Netmask = IF_IP_MASK # Instantiate and associate the interface device interface = vspyx.TCPIP.Interface.New(interfaceConfig) interface.Initialize(app, "Interface") interface.Attach(channel) # Instantiate and associate the network object network = vspyx.TCPIP.Network.New() network.Initialize(app, "Network") network.AddInterface(interface) Extras: DoIP Discovery ---------------------- If the target DoIP entity addressing info is already known, simply add it to the DoIP transport layer's entity table: .. code:: python transportLayer.AddDoIPEntity(TARGET_ECU_IP_ADDRESS, TARGET_ECU_LOGICAL_ADDRESS) If it is desired to manually broadcast an entity discover request: .. code:: python entities = transportLayer.GetVehicleIdentification(None) # Argument of None will broadcast; otherwise, providing an IP address will query a specific entity print("Results of manual vehicle identification request (%d entities responded):"%(len(entities))) for entityInfo in entities: (ipAddress, entity) = entityInfo print("ECU detected at IP address %s with logical address %s, VIN= %s"%(ipAddress, hex(entity.Address), [hex(octet) for octet in entity.VIN])) It is also possible to query by EID: .. code:: python QUERY_EID = vspyx.Core.BytesView(bytes([ 0xFA, 0x0A, 0x43, 0x99, 0xF7, 0xD7])) entities = transportLayer.GetVehicleIdentificationByEID(QUERY_EID, None) # Argument of None will broadcast; otherwise, providing an IP address will query a specific entity or by VIN: .. code:: python QUERY_VIN = vspyx.Core.BytesView(bytes([ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])) entities = transportLayer.GetVehicleIdentificationByVIN(QUERY_VIN, None) # Argument of None will broadcast; otherwise, providing an IP address will query a specific entity Extras: Configuring for Use with CAN and ISO-15765 -------------------------------------------------- Similarly to using DoIP, a CAN device must be initialized and an ISO-15765 instance must be instantiated for use as the transport layer. Device configuration of a client expecting ECU with 11-bit arb id 0x123 and 29-bit tester id 0x18FF456: .. code:: python # Device and TP Configuration class ISO15765_AddressTypes(enum.Enum): Normal = 1 NormalFixed = 2 Extended = 3 IF_LIB = "icsneo" # Use this when using the icsneo library IF_DEVICE = "88432" # ValueCAN 3 - 88432 #IF_DEVICE = "CY7347" # neoVI FIRE 2 - CY7347 IF_NAME = "%s %s"%(IF_LIB, IF_DEVICE) IF_CHANNEL = "HSCAN" NETWORK_ADDRESS_TYPE = ISO15765_AddressTypes.Normal TESTER_ADDRESS = 0x45 # Specify the client external tester address TESTER_CAN_ADDRESS = 0x18ff456 TESTER_ADDRESS_TYPE = vspyx.Communication.ISO15765_2.NetworkAddressType.PhysicalClassicalCAN29Bit ECU_ADDRESS = 0x23 # Specify the ECU server address ECU_CAN_ADDRESS = 0x123 ECU_ADDRESS_TYPE = vspyx.Communication.ISO15765_2.NetworkAddressType.PhysicalClassicalCAN11Bit TESTER_DATA_LENGTH = 8 # Specify the client external tester DLC (in octets) TESTER_STMIN = 0 TESTER_BS = 0 ECU_AE = None # Specify the ECU server address (N_AE) ECU_STMIN_MIN = 0 ... # Configure the Device channel = AutoDetectDevice(IF_NAME, 10, vspyx.Communication.CANChannel, IF_CHANNEL) if (channel is None): print("Unable to initialize device: %s"%(IF_NAME)) exit() interface = channel.NewISO11898_1Interface() # Create an ISO-15765 TP layer handler transportLayer = vspyx.Communication.ISO15765_2.New(TESTER_DATA_LENGTH, 0) transportLayer.Initialize(app, "ISO 15765-2") transportLayer.Attach(interface) # Note: Extended + RemoteDiagnostics is not possible mType = vspyx.Communication.ISOStandardizedServicePrimitiveInterface.MessageType.Diagnostics if (ECU_AE is None) else vspyx.Communication.ISOStandardizedServicePrimitiveInterface.MessageType.RemoteDiagnostics # Configure the TP address filtering/handling if (NETWORK_ADDRESS_TYPE == ISO15765_AddressTypes.Normal): transportLayer.AddRxNormalAddress(mType, ECU_ADDRESS, ECU_CAN_ADDRESS, ECU_ADDRESS_TYPE, TESTER_ADDRESS, TESTER_CAN_ADDRESS, TESTER_ADDRESS_TYPE, ECU_AE, TESTER_STMIN, TESTER_BS) transportLayer.AddTxNormalAddress(mType, TESTER_ADDRESS, TESTER_CAN_ADDRESS, TESTER_ADDRESS_TYPE, ECU_ADDRESS, ECU_CAN_ADDRESS, ECU_ADDRESS_TYPE, ECU_AE, ECU_STMIN_MIN) elif (NETWORK_ADDRESS_TYPE == ISO15765_AddressTypes.NormalFixed): transportLayer.AddRxFixedAddress(mType, ECU_ADDRESS, TESTER_ADDRESS, TESTER_ADDRESS_TYPE, ECU_AE, False, TESTER_STMIN, TESTER_BS) transportLayer.AddTxFixedAddress(mType, TESTER_ADDRESS, ECU_ADDRESS, ECU_ADDRESS_TYPE, ECU_AE, False, ECU_STMIN_MIN) elif (NETWORK_ADDRESS_TYPE == ISO15765_AddressTypes.Extended): transportLayer.AddRxExtendedAddress(vspyx.Communication.ISOStandardizedServicePrimitiveInterface.MessageType.Diagnostics, ECU_ADDRESS, ECU_CAN_ADDRESS, ECU_ADDRESS_TYPE, TESTER_ADDRESS, TESTER_CAN_ADDRESS, TESTER_ADDRESS_TYPE, None, TESTER_STMIN, TESTER_BS) transportLayer.AddTxExtendedAddress(vspyx.Communication.ISOStandardizedServicePrimitiveInterface.MessageType.Diagnostics, TESTER_ADDRESS, TESTER_CAN_ADDRESS, TESTER_ADDRESS_TYPE, ECU_ADDRESS, ECU_CAN_ADDRESS, ECU_ADDRESS_TYPE, None, ECU_STMIN_MIN) ... # Getting ready to run... targetAddress = ECU_ADDRESS taType = int(ECU_ADDRESS_TYPE) n_ae = ECU_AE ... target_ai = (mType, TESTER_ADDRESS, targetAddress, taType, n_ae) Examples -------- .. toctree:: :maxdepth: 1 diagnostics-client diagnostics-server