From 3b5a593de38a87ebf1fda603f2d080772c678268 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sat, 29 Mar 2025 16:54:52 -0400 Subject: [PATCH 1/2] Added getter for session timings --- doc/source/udsoncan/client.rst | 26 +++++++++++++++++++++++++- udsoncan/client.py | 29 +++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/doc/source/udsoncan/client.rst b/doc/source/udsoncan/client.rst index dd32bdf..56cc01b 100755 --- a/doc/source/udsoncan/client.rst +++ b/doc/source/udsoncan/client.rst @@ -242,12 +242,18 @@ See :ref:`an example ` .. attribute:: use_server_timing :annotation: (bool) - When using 2013 standard or above, the server is required to provide its P2 and P2* timing values with a DiagnosticSessionControl request. By setting this parameter to ``True``, the value received from the server will be used. When ``False``, these timing values will be ignored and local configuration timing will be used. Note that no timeout value can exceed the ``config['request_timeout']`` as it is meant to avoid the client from hanging for too long. + When using 2013 standard or above, the server is required to provide its P2 and P2* timing values with a DiagnosticSessionControl request. + By setting this parameter to ``True``, the value received from the server will be used. When ``False``, these timing values will be ignored and local configuration timing will be used. + Note that no timeout value can exceed the ``config['request_timeout']`` as it is meant to avoid the client from hanging for too long. This parameter has no effect when ``config['standard_version']`` is set to ``2006``. Default value is True +.. note:: + + The timeouts provided by the server can be obtained via :meth:`get_session_timing` + .. _config_nrc78_callback: @@ -320,6 +326,24 @@ When using that feature, the client will process the response from the server ju ----- +Session timings +--------------- + +When a request is performed, the client uses the P2 & P2* timeouts value provided by the server in a response to :meth:`change_session`. +If :meth:`change_session` is not called yet (or if the standard used is 2006), the values from the configuration will be used : :ref:`p2_timeout` & :ref:`p2_star_timeout` + +The client can provide the received P2 & P2* timeouts value via :meth:`get_session_timing` + +.. automethod:: udsoncan.client.Client.get_session_timing + +.. autoclass:: udsoncan.client.SessionTiming + :exclude-members: __init__, __new__ + :members: + + +----- + + Methods by services ------------------- diff --git a/udsoncan/client.py b/udsoncan/client.py index 91df39f..320d834 100755 --- a/udsoncan/client.py +++ b/udsoncan/client.py @@ -23,9 +23,17 @@ from typing import Callable, Optional, Union, Dict, List, Any, cast, Type -class SessionTiming(TypedDict): +class SessionTiming: + """Container for server provided P2 & P2* timeouts.""" + p2_server_max: Optional[float] + """P2 server max provided by the server. ``None`` if not provided yet """ p2_star_server_max: Optional[float] + """P2* server max provided by the server. ``None`` if not provided yet """ + + def __init__(self, p2_server_max:Optional[float]=None, p2_star_server_max:Optional[float]=None) -> None: + self.p2_server_max = p2_server_max + self.p2_star_server_max = p2_star_server_max class Client: @@ -111,7 +119,7 @@ def __init__(self, conn: BaseConnection, config: ClientConfig = default_client_c self.payload_override = Client.PayloadOverrider() self.last_response = None - self.session_timing = cast(SessionTiming, dict(p2_server_max=None, p2_star_server_max=None)) # python 3.7 cast + self.session_timing = SessionTiming(p2_server_max=None, p2_star_server_max=None) self.refresh_config() @@ -204,6 +212,15 @@ def decorated(self: "Client", *args, **kwargs): def service_log_prefix(self, service: Type[BaseService]): return "%s<0x%02x>" % (service.get_name(), service.request_id()) + def get_session_timing(self) -> SessionTiming: + """Return the session timing provided by the server, including P2 & P2* timeouts. + If the timeout values are ``None``, it means that no timing has been given by the server yet and the timings form the client configuration + (:ref:`p2_timeout`, :ref:`p2_star_timeout`) + + :return: The session timings + """ + return self.session_timing + @standard_error_management def change_session(self, newsession: int) -> Optional[services.DiagnosticSessionControl.InterpretedResponse]: """ @@ -238,8 +255,8 @@ def change_session(self, newsession: int) -> Optional[services.DiagnosticSession if self.config['use_server_timing']: self.logger.info('%s - Received new timing parameters. P2=%.3fs and P2*=%.3fs. Using these value from now on.' % (self.service_log_prefix(services.DiagnosticSessionControl), response.service_data.p2_server_max, response.service_data.p2_star_server_max)) - self.session_timing['p2_server_max'] = response.service_data.p2_server_max - self.session_timing['p2_star_server_max'] = response.service_data.p2_star_server_max + self.session_timing.p2_server_max = response.service_data.p2_server_max + self.session_timing.p2_star_server_max = response.service_data.p2_star_server_max return response @@ -2174,7 +2191,7 @@ def send_request(self, request: Request, timeout: int = -1) -> Optional[Response if timeout < 0: # Timeout not provided by user: defaults to Client request_timeout value overall_timeout = self.config['request_timeout'] - p2 = self.config['p2_timeout'] if self.session_timing['p2_server_max'] is None else self.session_timing['p2_server_max'] + p2 = self.config['p2_timeout'] if self.session_timing.p2_server_max is None else self.session_timing.p2_server_max if overall_timeout is not None: single_request_timeout = min(overall_timeout, p2) else: @@ -2285,7 +2302,7 @@ def send_request(self, request: Request, timeout: int = -1) -> Optional[Response done_receiving = False if not using_p2_star: # Received a 0x78 NRC: timeout is now set to P2* - p2_star = self.config['p2_star_timeout'] if self.session_timing['p2_star_server_max'] is None else self.session_timing['p2_star_server_max'] + p2_star = self.config['p2_star_timeout'] if self.session_timing.p2_star_server_max is None else self.session_timing.p2_star_server_max single_request_timeout = p2_star using_p2_star = True self.logger.debug("Server requested to wait with response code %s (0x%02x), single request timeout is now set to P2* (%.3f seconds)" % From e847e6477f0cf9eaa9ba8c15b06f8069e178d9d7 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sat, 29 Mar 2025 16:57:53 -0400 Subject: [PATCH 2/2] Fix unit test --- test/client/test_diagnostic_session_control.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/client/test_diagnostic_session_control.py b/test/client/test_diagnostic_session_control.py index bf52e66..642c2f8 100755 --- a/test/client/test_diagnostic_session_control.py +++ b/test/client/test_diagnostic_session_control.py @@ -34,8 +34,8 @@ def _test_dsc_success_2013_plus(self): self.assertEqual(response.service_data.session_param_records, b"\x99\x88\x12\x34") self.assertEqual(response.service_data.p2_server_max, (0x9988) / 1000) self.assertEqual(response.service_data.p2_star_server_max, 0x1234 * 10 / 1000) - self.assertEqual(self.udsclient.session_timing['p2_server_max'], response.service_data.p2_server_max) - self.assertEqual(self.udsclient.session_timing['p2_star_server_max'], response.service_data.p2_star_server_max) + self.assertEqual(self.udsclient.get_session_timing().p2_server_max, response.service_data.p2_server_max) + self.assertEqual(self.udsclient.get_session_timing().p2_star_server_max, response.service_data.p2_star_server_max) def test_dsc_success_2013_plus_ignore_server_timing(self): request = self.conn.touserqueue.get(timeout=0.2) @@ -50,8 +50,8 @@ def _test_dsc_success_2013_plus_ignore_server_timing(self): self.assertEqual(response.service_data.session_param_records, b"\x99\x88\x12\x34") self.assertEqual(response.service_data.p2_server_max, 0x9988 / 1000) self.assertEqual(response.service_data.p2_star_server_max, 0x1234 * 10 / 1000) - self.assertIsNone(self.udsclient.session_timing['p2_server_max']) - self.assertIsNone(self.udsclient.session_timing['p2_star_server_max']) + self.assertIsNone(self.udsclient.get_session_timing().p2_server_max) + self.assertIsNone(self.udsclient.get_session_timing().p2_star_server_max) def test_dsc_success_spr(self): request = self.conn.touserqueue.get(timeout=0.2)