Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling "pending" message from ECU #14

Open
Power-6 opened this issue Jun 2, 2022 · 4 comments
Open

Handling "pending" message from ECU #14

Power-6 opened this issue Jun 2, 2022 · 4 comments

Comments

@Power-6
Copy link

Power-6 commented Jun 2, 2022

Hi! I'm new at software development so go easy on me ;-) I am plenty technical though. I am using this client with udsoncan to implement flash programming and diag over Ethernet. I am hitting a problem that I think is contained in the way doipclient handles wait state frames. In this case when doing data transfer. ECU is unlocked, programming mode, 0x34 Request Download is sent and acknowledge so all is well. Then send 0x36 request to transfer data, ECU will always respond with a 0x3f error, "Code: Request correctly received, but response is pending (0x78)". I beleive udsoncan has been updated to handle this, i.e. keep waiting on pending message return (if timeout not expired). I am getting immediate timeout from doipclient though.

Relevant code:

from doipclient import DoIPClient
from doipclient.connectors import DoIPClientUDSConnector
import udsoncan
from udsoncan.client import Client
from udsoncan.exceptions import *
from udsoncan.services import *
import time

prog_client = DoIPClient(ecu_ip, currentecu, client_logical_address=OrBit)
    conn = DoIPClientUDSConnector(prog_client)
    with Client(conn, request_timeout=25, config=Carconfig) as client:
	try:
		client.request_download(mem_loc, dfi=SBLdfi)
		for c in chunk:
			seq=seq+1
			client.transfer_data(seq, data=c)
			time.sleep(.5)
		client.request_transfer_exit()

The first transfer of the first block, the client.transfer_data times out almost immediately, via the doipclient.

errors in testing:

  File "C:\Program Files\Python39\lib\site-packages\udsoncan\client.py", line 128, in decorated
    return func(self, *args, **kwargs)
  File "C:\Program Files\Python39\lib\site-packages\udsoncan\client.py", line 844, in transfer_data
    response = self.send_request(request)
  File "C:\Program Files\Python39\lib\site-packages\udsoncan\client.py", line 1956, in send_request
    raise e
  File "C:\Program Files\Python39\lib\site-packages\udsoncan\client.py", line 1946, in send_request
    payload = self.conn.wait_frame(timeout=timeout_value, exception=True)
  File "C:\Program Files\Python39\lib\site-packages\udsoncan\connections.py", line 77, in wait_frame
    frame = self.specific_wait_frame(timeout=timeout)
  File "C:\Program Files\Python39\lib\site-packages\doipclient\connectors.py", line 46, in specific_wait_frame
    return bytes(self._connection.receive_diagnostic(timeout=timeout))
  File "C:\Program Files\Python39\lib\site-packages\doipclient\client.py", line 666, in receive_diagnostic
    result = self.read_doip(timeout=(timeout - ellapsed_time))
  File "C:\Program Files\Python39\lib\site-packages\doipclient\client.py", line 351, in read_doip
    raise TimeoutError("ECU failed to respond in time")
TimeoutError: ECU failed to respond in time

In trying to figure out where this goes wrong, I think it's in connectors.py, the specific_wait_frame, as that returns from the function? I could be wrong about that, but at any rate it appears as far as I can see, doipclient is handling the pending message as the result and returning it, not waiting for the result. I have tried playing with the timeout there and in the constants, A_PROCESSING_TIME, setting to 20 to check if there is an effect, but there is not, return is still immediate timeout on pending message. If I sniff the factory tool, the ECU issues the pending message within 0.3 sec, then 0.5 sec later sends the transfer confirmation, so that seems to be happening within the 2 sec timeout of doipclient.

    def specific_wait_frame(self, timeout=20):
        return bytes(self._connection.receive_diagnostic(timeout=timeout))

I could be way off though, I'm a n00b but wanted to bring it up as it seems an issue with doipclient.

@jacobschaer
Copy link
Owner

jacobschaer commented Jun 3, 2022

Do you have a PCAP? The DoIP layer is indifferent to the UDS interactions. The ECU responding with 0x7b is typical, it essentially just extends the UDS P2 timeout. udsoncan handles it properly. Normally the loop is

  1. udsoncan sends something
  2. doip packs it up and sends it
  3. udsoncan asks doip client if there's anything back - there's not, so we send Timeout Error
  4. udsoncan accepts this and keeps looping until either p2 timeout or something shows up
  5. If it receives 7b it keeps extending the timer

The only time you should have issues with timeouts is if the ECU isn't responding with DoIP ack in a timely manner, or if your udsoncan client isn't properly configured.

I suppose it's possible the udsoncan client interface has changed in newer versions. Can you show which version you have installed as well?

I vaguely recall you might need to tweak use_server_timing, p2_timeout, and p2_star_timeout

@Power-6
Copy link
Author

Power-6 commented Jun 3, 2022

Hi, thanks for getting back to me! I was able to do further testing and get past this. So as such this is not an issue, as it looks like the pending message and wait is being properly handled with udsoncan (1.15 latest version) and doipclient is working correctly there. The immediate cause of the issue was the car timing out the connection.

The root cause was a different issue, which would be more of an enhancement, to doipclient, I think, unless I am missing a limitation of the connection with udsoncan. I could open a new issue or if it's not feasible let me know your thoughts before you close this one and I won't bother if it's more of a udsoncan issue or I am missing how to properly use doipclient.

The issue stems from very modern cars with a gateway. In this case a 2019 Volvo with a "VCM" that acts as the DOIP gateway to the car via ethernet from the OBD port, for all diagnostics. As such a diag tool talks to multiple ECUs through the same TCP session, IP destination is always the same, but the logical address would change of course. The doipclient, only allows one "client" at a time since the logical address is defined on the DOIPClient connection. The effect is I need to close and open connections, to the gateway on connecting, then close and open new connection to broadcast address to ping "tester present" to all ECUs, then close that connection and open a new connection to program an ECU. The problem, it turns out, is i have to close the broadcast connection and stop the "tester present" ping in order to work on a specific ECU, and after a time with no "ping" all the ECUs, specifically the "VCM" gateway time out of their current mode, and drop the TCP connection. i was hoping that a programming session would keep things going but that timely "tester present", broadcast to all ECUs (logical address 0x1FFF on the Volvo) turned out to be critical.

Initially I tried opening a separate socket via TCP and send the raw packet. No dice the gateway responds negative and closes the session! So I needed to be in the same session as the doipclient. I did end up working around this, I figured out the handy "send_doip' feature and that I can control the logical addressing withing that. So I was able to incorporate timely "tester present" pings to the broadcast address in the same session where I am programming a specific ECU. That kept the gateway from timing out the session and the programming session was able to complete.

The downside of this way is that I can't leverage udsoncan for the communication, need to assemble the payload, and any response handling from scratch, as the doipclient is configured for only one logical address. For my current challenge it is not a big deal, as the broadcast "tester present" has suppress reply bit enabled, it's just a dumb ping. For the future, it could be more of a challenge, like cycling through ECUs reading DTC information. I will need to open and close a session for each ECU, there are 35 ECUs in the car. Again that may be worked around, but maybe not ideal.

Seems to me, ideally the TCP session is not tied to the ECU session (the logical address) as with a gateway, the paradigm of "one session, one ECU" is broken. New paradigm: the gateway is a router, open TCP session with it, and then communicate with any ECU behind it in that session via the logical address. I don't know what that looks like, if that's possible to integrate with udsoncan. Or if I am missing there is way to do that already...

@jacobschaer
Copy link
Owner

Just skimming your response, I agree that in this day and age vendors have come up with some pretty crazy situations, usually because they only need to support their own custom tooling. The spec does describe how edge gateway routers should behave, but I think in practice most vendors have some trickery required that's not in the spec. Mercedes and Volvo are the only two I've seen that even expose it. Zero Emission vehicles aren't required to provide diagnostics, so there is often no edge gateway to talk to - just their custom API's. I assume most other vendors still just use CAN and friends on the OBD2 port instead of DoIP, leaving DoIP for internal use.

In the common use case, with UDS and DoIP you're only talking to one ECU at a time, mainly because historically the transports have been ISOTP or LINTP. As such, I think as you're finding the tooling udsoncan and my library set out to handle this use case - specifically I was originally interested in talking to ECU's directly over automotive Ethernet rather than through a gateway. The architectural issue is that udsoncan doesn't have the concept of "who" you want to talk to, and we need to be careful to avoid pulling the rug out from under it. The easiest way, as you discovered, is to simply enforce a one to one relationship.

I suspect it would be pretty easy for you to create a new custom DoIPClient that does what you want and is able to reuse a lot of the pieces. It just needs to be threaded like the ISOTP connector and pet the DoIP and UDS layer for various logical addresses periodically, rather than waiting for the udsoncan caller to do it. If you do come up with something we could merge it in as a Volvo specific implementation. For now, the design goal of this project was "short lived synchronous" and my intended solution to your problem would be multiple DoIPClients and close/reopen as needed

@Power-6
Copy link
Author

Power-6 commented Jun 3, 2022

Thanks Jacob! You hit the nail on the head and it's helpful to me. I am in a relative bubble being new and only working on one specific use case. My assumption was this is the way DoIP is going but that's confined to a few mfrs as far as I know right now, I think BMW ENET is similar to Volvo and Benz but that's the limit of my knowledge. I understand the situation with automakers, moving slowly from historically highly proprietary diagnostics to more open standards based, yet still so much proprietary in the implementations.

I'd love to contribute to make a gateway firendly DoIP client, I am loving Python. I think I will finish my first project with DoIP so I get a good understanding of the what would be the ideal architecture for how this would work. I'm already hitting some other challenges, but able to work around them for now. Not sure I am qualified to figure out how to build a new client/module, not like that would stop me ;-) I may reach out though when I get to that if you don't mind!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants