diff --git a/test/client/test_communication_control.py b/test/client/test_communication_control.py index 4d3ee96..de186c6 100755 --- a/test/client/test_communication_control.py +++ b/test/client/test_communication_control.py @@ -2,6 +2,7 @@ from udsoncan.exceptions import * from test.ClientServerTest import ClientServerTest +from udsoncan import latest_standard class TestCommunicationControl(ClientServerTest): @@ -29,6 +30,18 @@ def _test_comcontrol_enable_node(self): self.assertTrue(response.positive) self.assertEqual(response.service_data.control_type_echo, control_type) + def test_comcontrol_enable_node_with_enhanced_addr_info(self): + request = self.conn.touserqueue.get(timeout=0.2) + self.assertEqual(request, b"\x28\x05\x01\x12\x34") + self.conn.fromuserqueue.put(b"\x68\x05") # Positive response + + def _test_comcontrol_enable_node_with_enhanced_addr_info(self): + control_type = services.CommunicationControl.ControlType.enableRxAndTxWithEnhancedAddressInformation + com_type = CommunicationType(subnet=CommunicationType.Subnet.node, normal_msg=True) + response = self.udsclient.communication_control(control_type=control_type, communication_type=com_type, node_id=0x1234) + self.assertTrue(response.positive) + self.assertEqual(response.service_data.control_type_echo, control_type) + def test_comcontrol_enable_node_spr(self): request = self.conn.touserqueue.get(timeout=0.2) self.assertEqual(request, b"\x28\x80\x01") @@ -148,3 +161,21 @@ def _test_bad_param(self): with self.assertRaises(ValueError): self.udsclient.communication_control(control_type=0, communication_type='x') + + with self.assertRaises(ValueError): + self.udsclient.communication_control( + control_type=services.CommunicationControl.ControlType.enableRxAndDisableTxWithEnhancedAddressInformation, + communication_type=CommunicationType(subnet=CommunicationType.Subnet.node, normal_msg=True)) # Missing node_id + + with self.assertRaises(ValueError): + self.udsclient.communication_control( + control_type=services.CommunicationControl.ControlType.enableRxAndTxWithEnhancedAddressInformation, + communication_type=CommunicationType(subnet=CommunicationType.Subnet.node, normal_msg=True)) # Missing node_id + + with self.assertRaises(ValueError): + self.udsclient.config['standard_version'] = 2006 + self.udsclient.communication_control( + control_type=services.CommunicationControl.ControlType.enableRxAndTxWithEnhancedAddressInformation, + communication_type=CommunicationType(subnet=CommunicationType.Subnet.node, normal_msg=True), + node_id=0x1234) # node_id not allowed + self.udsclient.config['standard_version'] = latest_standard diff --git a/udsoncan/client.py b/udsoncan/client.py index 26815e1..71c91b8 100755 --- a/udsoncan/client.py +++ b/udsoncan/client.py @@ -813,7 +813,8 @@ def access_timing_parameter(self, @standard_error_management def communication_control(self, control_type: int, - communication_type: Union[int, bytes, CommunicationType] + communication_type: Union[int, bytes, CommunicationType], + node_id: Optional[int] = None, ) -> Optional[services.CommunicationControl.InterpretedResponse]: """ Switches the transmission or reception of certain messages on/off with :ref:`CommunicationControl` service. @@ -826,15 +827,29 @@ def communication_control(self, :param communication_type: Indicates what section of the network and the type of message that should be affected by the command. Refer to :ref:`CommunicationType` for more details. If an `integer` or a `bytes` is given, the value will be decoded to create the required :ref:`CommunicationType` object :type communication_type: :ref:`CommunicationType`, bytes, int + :param node_id: DTC memory identifier (nodeIdentificationNumber). This value is user defined and introduced in 2013 version of ISO-14229-1. + Possible only when control type is ``enableRxAndDisableTxWithEnhancedAddressInformation`` or ``enableRxAndTxWithEnhancedAddressInformation`` + Only added to the request payload when different from None. Default : None + :type node_id: int + :return: The server response parsed by :meth:`CommunicationControl.interpret_response` :rtype: :ref:`Response` """ communication_type = services.CommunicationControl.normalize_communication_type(communication_type) - request = services.CommunicationControl.make_request(control_type, communication_type) - self.logger.info('%s - ControlType=0x%02x (%s) - Sending request with a CommunicationType byte of 0x%02x (%s)' % (self.service_log_prefix(services.CommunicationControl), - control_type, services.CommunicationControl.ControlType.get_name(control_type), communication_type.get_byte_as_int(), str(communication_type))) + request = services.CommunicationControl.make_request( + control_type, communication_type, node_id, standard_version=self.config['standard_version']) + + self.logger.info('%s - ControlType=0x%02x (%s) - Sending request with a CommunicationType byte of 0x%02x (%s). nodeIdentificationNumber=%s ' % ( + self.service_log_prefix(services.CommunicationControl), + control_type, + services.CommunicationControl.ControlType.get_name(control_type), + communication_type.get_byte_as_int(), + str(communication_type), + str(node_id) + ) + ) response = self.send_request(request) if response is None: diff --git a/udsoncan/services/ClearDiagnosticInformation.py b/udsoncan/services/ClearDiagnosticInformation.py index a0adfb6..29aab67 100755 --- a/udsoncan/services/ClearDiagnosticInformation.py +++ b/udsoncan/services/ClearDiagnosticInformation.py @@ -38,6 +38,13 @@ def make_request(cls, group: int = 0xFFFFFF, memory_selection: Optional[int] = N :param group: DTC mask ranging from 0 to 0xFFFFFF. 0xFFFFFF means all DTCs :type group: int + :param memory_selection: Number identifying the respective DTC memory. This value is user defined and introduced in 2013 version of ISO-14229-1. + Only added to the request payload when different from None. Default : None + :type memory_selection: int + + :param standard_version: The version of the ISO-14229 (the year). eg. 2006, 2013, 2020 + :type standard_version: int + :raises ValueError: If parameters are out of range, missing or wrong type """ tools.validate_int(group, min=0, max=0xFFFFFF, name='Group of DTC') diff --git a/udsoncan/services/CommunicationControl.py b/udsoncan/services/CommunicationControl.py index bb37539..2a034e1 100755 --- a/udsoncan/services/CommunicationControl.py +++ b/udsoncan/services/CommunicationControl.py @@ -5,8 +5,10 @@ from udsoncan.BaseService import BaseService, BaseSubfunction, BaseResponseData from udsoncan.ResponseCode import ResponseCode import udsoncan.tools as tools +from udsoncan import latest_standard +import struct -from typing import cast, Union +from typing import cast, Union, Optional class CommunicationControl(BaseService): @@ -22,6 +24,8 @@ class ControlType(BaseSubfunction): enableRxAndDisableTx = 1 disableRxAndEnableTx = 2 disableRxAndTx = 3 + enableRxAndDisableTxWithEnhancedAddressInformation = 4 + enableRxAndTxWithEnhancedAddressInformation = 5 supported_negative_response = [ResponseCode.SubFunctionNotSupported, ResponseCode.IncorrectMessageLengthOrInvalidFormat, @@ -55,7 +59,7 @@ def normalize_communication_type(self, communication_type: Union[int, bytes, Com return communication_type @classmethod - def make_request(cls, control_type: int, communication_type: CommunicationType) -> Request: + def make_request(cls, control_type: int, communication_type: CommunicationType, node_id: Optional[int] = None, standard_version=latest_standard) -> Request: """ Generates a request for CommunicationControl @@ -65,14 +69,39 @@ def make_request(cls, control_type: int, communication_type: CommunicationType) :param communication_type: The communication type requested. :type communication_type: :ref:`CommunicationType `, int, bytes + :param node_id: DTC memory identifier. This value is user defined and introduced in 2013 version of ISO-14229-1. + Possible and required only when ``control_type`` is ``enableRxAndDisableTxWithEnhancedAddressInformation`` or ``enableRxAndTxWithEnhancedAddressInformation`` + Default : ``None`` + :type node_id: int + + :param standard_version: The version of the ISO-14229 (the year). eg. 2006, 2013, 2020 + :type standard_version: int + :raises ValueError: If parameters are out of range, missing or wrong type """ tools.validate_int(control_type, min=0, max=0x7F, name='Control type') + require_node_id = standard_version >= 2013 and control_type in ( + CommunicationControl.ControlType.enableRxAndDisableTxWithEnhancedAddressInformation, + CommunicationControl.ControlType.enableRxAndTxWithEnhancedAddressInformation + ) + + if require_node_id and node_id is None: + raise ValueError( + "node_id is required when the standard version is 2013 (or more recent) and when control_type is enableRxAndDisableTxWithEnhancedAddressInformation or enableRxAndTxWithEnhancedAddressInformation ") + elif not require_node_id and node_id is not None: + raise ValueError( + "node_id is only possible when the standard version is 2013 (or more recent) and when control_type is enableRxAndDisableTxWithEnhancedAddressInformation or enableRxAndTxWithEnhancedAddressInformation ") + communication_type = cls.normalize_communication_type(communication_type) request = Request(service=cls, subfunction=control_type) - request.data = communication_type.get_byte() + payload = communication_type.get_byte() + + if node_id is not None: + tools.validate_int(node_id, min=0, max=0xFFFF, name='nodeIdentificationNumber') + payload += struct.pack('>H', node_id) + request.data = payload return request @classmethod diff --git a/udsoncan/services/DiagnosticSessionControl.py b/udsoncan/services/DiagnosticSessionControl.py index 311fa44..9ab39d2 100755 --- a/udsoncan/services/DiagnosticSessionControl.py +++ b/udsoncan/services/DiagnosticSessionControl.py @@ -84,6 +84,9 @@ def interpret_response(cls, response: Response, standard_version: int = latest_s :param response: The received response to interpret :type response: :ref:`Response` + :param standard_version: The version of the ISO-14229 (the year). eg. 2006, 2013, 2020 + :type standard_version: int + :raises InvalidResponseException: If length of ``response.data`` is too short """ if response.data is None: