From 17ff3fd7a33c29ef07ed699181027ee0072fe68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Tue, 17 Sep 2024 19:10:00 +0200 Subject: [PATCH 01/20] mark todo Define where changes are required. Add a few new system tests. --- .../test_python_can.py | 142 +++++++++++++-- uds/segmentation/can_segmenter.py | 162 ++++++++++-------- .../can_transport_interface.py | 2 +- 3 files changed, 219 insertions(+), 87 deletions(-) diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index d581f8ef..a9215f25 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -705,9 +705,8 @@ async def _send_frame(): @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), - # TODO: add more with https://github.com/mdabrowski1990/uds/issues/267 ]) - def test_send_message(self, example_addressing_information, message): + def test_send_message__sf(self, example_addressing_information, message): """ Check for a simple synchronous UDS message sending. @@ -729,24 +728,72 @@ def test_send_message(self, example_addressing_information, message): assert message_record.direction == TransmissionDirection.TRANSMITTED assert message_record.payload == message.payload assert message_record.addressing_type == message.addressing_type + assert message_record.transmission_start == message_record.transmission_end + assert len(message_record.packets_records) == 1 + assert message_record.packets_records[0].packet_type == CanPacketType.SINGLE_FRAME + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert datetime_before_send < message_record.transmission_start + # assert message_record.transmission_end < datetime_after_send + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("send_after", [5, 45]) + def test_send_message__multi_packets(self, example_addressing_information, + example_addressing_information_2nd_node, + message, send_after): + """ + Check for a simple synchronous UDS message sending. + + Procedure: + 1. Schedule Flow Control CAN Packet with information to continue sending all consecutive frame packets at once. + 2. Send a UDS message using Transport Interface (via CAN Interface). + Expected: UDS message record returned. + 3. Validate transmitted UDS message record attributes. + Expected: Attributes of UDS message record are in line with the transmitted UDS message. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information_2nd_node: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + :param send_after: Delay to use for sending CAN flow control. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.ContinueToSend, + block_size=0, + st_min=0) + flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) + Timer(interval=send_after / 1000., function=self.can_interface_2.send, args=(flow_control_frame,)).start() + datetime_before_send = datetime.now() + message_record = can_transport_interface.send_message(message) + datetime_after_send = datetime.now() + assert isinstance(message_record, UdsMessageRecord) + assert message_record.direction == TransmissionDirection.TRANSMITTED + assert message_record.payload == message.payload + assert message_record.addressing_type == message.addressing_type + assert message_record.transmission_start < message_record.transmission_end + assert len(message_record.packets_records) > 1 + assert message_record.packets_records[0].packet_type == CanPacketType.FIRST_FRAME + assert message_record.packets_records[1].packet_type == CanPacketType.FLOW_CONTROL + assert message_record.packets_records[1].direction == TransmissionDirection.RECEIVED + assert all(following_packet.packet_type == CanPacketType.CONSECUTIVE_FRAME + for following_packet in message_record.packets_records[2:]) # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send - if len(message_record.packets_records) == 1: - assert message_record.transmission_start == message_record.transmission_end - else: - assert message_record.transmission_start < message_record.transmission_end # async_send_message @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), - # TODO: add more with https://github.com/mdabrowski1990/uds/issues/267 ]) @pytest.mark.asyncio - async def test_async_send_message(self, example_addressing_information, message): + async def test_async_send_message__sf(self, example_addressing_information, message): """ Check for a simple asynchronous UDS message sending. @@ -768,14 +815,73 @@ async def test_async_send_message(self, example_addressing_information, message) assert message_record.direction == TransmissionDirection.TRANSMITTED assert message_record.payload == message.payload assert message_record.addressing_type == message.addressing_type + assert message_record.transmission_start == message_record.transmission_end + assert len(message_record.packets_records) == 1 + assert message_record.packets_records[0].packet_type == CanPacketType.SINGLE_FRAME + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert datetime_before_send < message_record.transmission_start + # assert message_record.transmission_end < datetime_after_send + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + ]) + @pytest.mark.parametrize("send_after", [5, 45]) + @pytest.mark.asyncio + async def test_send_message__multi_packets(self, example_addressing_information, + example_addressing_information_2nd_node, + message, send_after): + """ + Check for a simple synchronous UDS message sending. + + Procedure: + 1. Schedule Flow Control CAN Packet with information to continue sending all consecutive frame packets at once. + 2. Send (using async method) a UDS message using Transport Interface (via CAN Interface). + Expected: UDS message record returned. + 3. Validate transmitted UDS message record attributes. + Expected: Attributes of UDS message record are in line with the transmitted UDS message. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information_2nd_node: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + :param send_after: Delay to use for sending CAN flow control. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.ContinueToSend, + block_size=0, + st_min=0) + flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) + + async def _send_frame(): + await asyncio.sleep(send_after / 1000.) + self.can_interface_2.send(flow_control_frame) + + future_record = can_transport_interface.async_send_message(message) + tasks = [asyncio.create_task(_send_frame()), asyncio.create_task(future_record)] + datetime_before_send = datetime.now() + done_tasks, _ = await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) + datetime_after_send = datetime.now() + sent_records = tuple(filter(lambda result: isinstance(result, UdsMessageRecord), + (done_task.result() for done_task in done_tasks))) + assert len(sent_records) == 1, "UDS message was sent" + message_record = sent_records[0] + assert isinstance(message_record, UdsMessageRecord) + assert message_record.direction == TransmissionDirection.TRANSMITTED + assert message_record.payload == message.payload + assert message_record.addressing_type == message.addressing_type + assert len(message_record.packets_records) > 1 + assert message_record.packets_records[0].packet_type == CanPacketType.FIRST_FRAME + assert message_record.packets_records[1].packet_type == CanPacketType.FLOW_CONTROL + assert message_record.packets_records[1].direction == TransmissionDirection.RECEIVED + assert all(following_packet.packet_type == CanPacketType.CONSECUTIVE_FRAME + for following_packet in message_record.packets_records[2:]) # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send - if len(message_record.packets_records) == 1: - assert message_record.transmission_start == message_record.transmission_end - else: - assert message_record.transmission_start < message_record.transmission_end # receive_message @@ -1076,7 +1182,8 @@ async def test_async_send_packet_on_one_receive_on_other_bus(self, example_addre # https://github.com/mdabrowski1990/uds/issues/267 ]) def test_send_message_on_one_receive_on_other_bus(self, example_addressing_information, - example_addressing_information_2nd_node, message): + example_addressing_information_2nd_node, + message): """ Check for sending and receiving UDS message using two Transport Interfaces. @@ -1115,7 +1222,8 @@ def test_send_message_on_one_receive_on_other_bus(self, example_addressing_infor ]) @pytest.mark.asyncio async def test_async_send_message_on_one_receive_on_other_bus(self, example_addressing_information, - example_addressing_information_2nd_node, message): + example_addressing_information_2nd_node, + message): """ Check for asynchronous sending and receiving UDS message using two Transport Interfaces. @@ -1147,6 +1255,8 @@ async def test_async_send_message_on_one_receive_on_other_bus(self, example_addr assert message_record_1.addressing_type == message_record_2.addressing_type == message.addressing_type assert message_record_1.payload == message_record_2.payload == message.payload + # TODO: Flow Control CONTINUE TO SEND with changing block size and STmin (including max value) + # error guessing @pytest.mark.parametrize("payload, addressing_type", [ @@ -1342,3 +1452,7 @@ async def test_async_observe_tx_packet(self, example_addressing_information, exa # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert packet_record.transmission_time > datetime_before_send + + # TODO: Flow Control max WAIT, then continue transmission + # TODO: Flow Control spam WAIT till timeout + # TODO: Flow Control OVERFLOW diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py index 65be7de8..c1280276 100644 --- a/uds/segmentation/can_segmenter.py +++ b/uds/segmentation/can_segmenter.py @@ -14,6 +14,7 @@ CanFirstFrameHandler, CanSingleFrameHandler, PacketAIParamsAlias, + CanFlowStatus ) from uds.message import UdsMessage, UdsMessageRecord from uds.packet import ( @@ -161,6 +162,78 @@ def filler_byte(self, value: int): validate_raw_byte(value) self.__filler_byte: int = value + def __physical_segmentation(self, message: UdsMessage) -> Tuple[CanPacket, ...]: + """ + Segment physically addressed diagnostic message. + + :param message: UDS message to divide into UDS packets. + + :raise SegmentationError: Provided diagnostic message cannot be segmented. + + :return: CAN packets that are an outcome of UDS message segmentation. + """ + message_payload_size = len(message.payload) + if message_payload_size > CanFirstFrameHandler.MAX_LONG_FF_DL_VALUE: + raise SegmentationError("Provided diagnostic message cannot be segmented to CAN Packet as it is too big " + "to transmit it over CAN bus.") + if message_payload_size <= CanSingleFrameHandler.get_max_payload_size(addressing_format=self.addressing_format, + dlc=self.dlc): + single_frame = CanPacket(packet_type=CanPacketType.SINGLE_FRAME, + payload=message.payload, + filler_byte=self.filler_byte, + dlc=None if self.use_data_optimization else self.dlc, + **self.tx_packets_physical_ai) + return (single_frame,) + ff_payload_size = CanFirstFrameHandler.get_payload_size( + addressing_format=self.addressing_format, + dlc=self.dlc, + long_ff_dl_format=message_payload_size > CanFirstFrameHandler.MAX_SHORT_FF_DL_VALUE) + first_frame = CanPacket(packet_type=CanPacketType.FIRST_FRAME, + payload=message.payload[:ff_payload_size], + dlc=self.dlc, + data_length=message_payload_size, + **self.tx_packets_physical_ai) + cf_payload_size = CanConsecutiveFrameHandler.get_max_payload_size(addressing_format=self.addressing_format, + dlc=self.dlc) + total_cfs_number = (message_payload_size - ff_payload_size + cf_payload_size - 1) // cf_payload_size + consecutive_frames = [] + for cf_index in range(total_cfs_number): + sequence_number = (cf_index + 1) % 0x10 + payload_i_start = ff_payload_size + cf_index * cf_payload_size + payload_i_stop = payload_i_start + cf_payload_size + consecutive_frame = CanPacket(packet_type=CanPacketType.CONSECUTIVE_FRAME, + payload=message.payload[payload_i_start: payload_i_stop], + dlc=None if self.use_data_optimization and cf_index == total_cfs_number - 1 + else self.dlc, + sequence_number=sequence_number, + filler_byte=self.filler_byte, + **self.tx_packets_physical_ai) + consecutive_frames.append(consecutive_frame) + return (first_frame, *consecutive_frames) + + def __functional_segmentation(self, message: UdsMessage) -> Tuple[CanPacket, ...]: + """ + Segment functionally addressed diagnostic message. + + :param message: UDS message to divide into UDS packets. + + :raise SegmentationError: Provided diagnostic message cannot be segmented. + + :return: CAN packets that are an outcome of UDS message segmentation. + """ + max_payload_size = CanSingleFrameHandler.get_max_payload_size(addressing_format=self.addressing_format, + dlc=self.dlc) + message_payload_size = len(message.payload) + if message_payload_size > max_payload_size: + raise SegmentationError("Provided diagnostic message cannot be segmented using functional addressing " + "as it will not fit into a Single Frame.") + single_frame = CanPacket(packet_type=CanPacketType.SINGLE_FRAME, + payload=message.payload, + filler_byte=self.filler_byte, + dlc=None if self.use_data_optimization else self.dlc, + **self.tx_packets_functional_ai) + return (single_frame,) + # TODO: consider moving this method to AbstractAddressingInformation (if defined) def is_input_packet(self, can_id: int, data: RawBytesAlias) -> Optional[AddressingType]: # type: ignore # noqa """ @@ -225,6 +298,23 @@ def is_desegmented_message(self, packets: PacketsContainersSequence) -> bool: return payload_bytes_found >= total_payload_size # type: ignore raise NotImplementedError(f"Unknown packet type received: {packets[0].packet_type}") + def get_flow_control_packet(self, + flow_status: CanFlowStatus, + block_size: Optional[int] = None, + st_min: Optional[int] = None) -> CanPacket: + """ + Create Flow Control CAN packet. + + :param flow_status: Value of Flow Status parameter. + :param block_size: Value of Block Size parameter. + This parameter is only required with ContinueToSend Flow Status, leave None otherwise. + :param st_min: Value of Separation Time minimum (STmin) parameter. + This parameter is only required with ContinueToSend Flow Status, leave None otherwise. + + :return: Flow Control CAN packet with provided parameters. + """ + # TODO: add code + def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage, UdsMessageRecord]: """ Perform desegmentation of CAN packets. @@ -276,75 +366,3 @@ def segmentation(self, message: UdsMessage) -> Tuple[CanPacket, ...]: if message.addressing_type == AddressingType.FUNCTIONAL: return self.__functional_segmentation(message) raise NotImplementedError(f"Unknown addressing type received: {message.addressing_type}") - - def __physical_segmentation(self, message: UdsMessage) -> Tuple[CanPacket, ...]: - """ - Segment physically addressed diagnostic message. - - :param message: UDS message to divide into UDS packets. - - :raise SegmentationError: Provided diagnostic message cannot be segmented. - - :return: CAN packets that are an outcome of UDS message segmentation. - """ - message_payload_size = len(message.payload) - if message_payload_size > CanFirstFrameHandler.MAX_LONG_FF_DL_VALUE: - raise SegmentationError("Provided diagnostic message cannot be segmented to CAN Packet as it is too big " - "to transmit it over CAN bus.") - if message_payload_size <= CanSingleFrameHandler.get_max_payload_size(addressing_format=self.addressing_format, - dlc=self.dlc): - single_frame = CanPacket(packet_type=CanPacketType.SINGLE_FRAME, - payload=message.payload, - filler_byte=self.filler_byte, - dlc=None if self.use_data_optimization else self.dlc, - **self.tx_packets_physical_ai) - return (single_frame,) - ff_payload_size = CanFirstFrameHandler.get_payload_size( - addressing_format=self.addressing_format, - dlc=self.dlc, - long_ff_dl_format=message_payload_size > CanFirstFrameHandler.MAX_SHORT_FF_DL_VALUE) - first_frame = CanPacket(packet_type=CanPacketType.FIRST_FRAME, - payload=message.payload[:ff_payload_size], - dlc=self.dlc, - data_length=message_payload_size, - **self.tx_packets_physical_ai) - cf_payload_size = CanConsecutiveFrameHandler.get_max_payload_size(addressing_format=self.addressing_format, - dlc=self.dlc) - total_cfs_number = (message_payload_size - ff_payload_size + cf_payload_size - 1) // cf_payload_size - consecutive_frames = [] - for cf_index in range(total_cfs_number): - sequence_number = (cf_index + 1) % 0x10 - payload_i_start = ff_payload_size + cf_index * cf_payload_size - payload_i_stop = payload_i_start + cf_payload_size - consecutive_frame = CanPacket(packet_type=CanPacketType.CONSECUTIVE_FRAME, - payload=message.payload[payload_i_start: payload_i_stop], - dlc=None if self.use_data_optimization and cf_index == total_cfs_number - 1 - else self.dlc, - sequence_number=sequence_number, - filler_byte=self.filler_byte, - **self.tx_packets_physical_ai) - consecutive_frames.append(consecutive_frame) - return (first_frame, *consecutive_frames) - - def __functional_segmentation(self, message: UdsMessage) -> Tuple[CanPacket, ...]: - """ - Segment functionally addressed diagnostic message. - - :param message: UDS message to divide into UDS packets. - - :raise SegmentationError: Provided diagnostic message cannot be segmented. - - :return: CAN packets that are an outcome of UDS message segmentation. - """ - max_payload_size = CanSingleFrameHandler.get_max_payload_size(addressing_format=self.addressing_format, - dlc=self.dlc) - message_payload_size = len(message.payload) - if message_payload_size > max_payload_size: - raise SegmentationError("Provided diagnostic message cannot be segmented using functional addressing " - "as it will not fit into a Single Frame.") - single_frame = CanPacket(packet_type=CanPacketType.SINGLE_FRAME, - payload=message.payload, - filler_byte=self.filler_byte, - dlc=None if self.use_data_optimization else self.dlc, - **self.tx_packets_functional_ai) - return (single_frame,) diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index dfe513da..3800a325 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -724,7 +724,7 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: if len(packets_to_send) == 1: packet_record = self.send_packet(*packets_to_send) return UdsMessageRecord((packet_record,)) - raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/267") + # TODO: implementation def receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> UdsMessageRecord: """ From 5798df44e8bae5a3ce51fe22f50f213750f24989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Wed, 18 Sep 2024 11:35:58 +0200 Subject: [PATCH 02/20] Update test_python_can.py Prepare system tests --- .../test_python_can.py | 85 ++++++++++++++++--- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index a9215f25..e6d478f2 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -745,7 +745,7 @@ def test_send_message__multi_packets(self, example_addressing_information, example_addressing_information_2nd_node, message, send_after): """ - Check for a simple synchronous UDS message sending. + Check for a synchronous multi packet (FF + CF) UDS message sending. Procedure: 1. Schedule Flow Control CAN Packet with information to continue sending all consecutive frame packets at once. @@ -829,11 +829,11 @@ async def test_async_send_message__sf(self, example_addressing_information, mess ]) @pytest.mark.parametrize("send_after", [5, 45]) @pytest.mark.asyncio - async def test_send_message__multi_packets(self, example_addressing_information, + async def test_async_send_message__multi_packets(self, example_addressing_information, example_addressing_information_2nd_node, message, send_after): """ - Check for a simple synchronous UDS message sending. + Check for an asynchronous multi packet (FF + CF) UDS message sending. Procedure: 1. Schedule Flow Control CAN Packet with information to continue sending all consecutive frame packets at once. @@ -1178,8 +1178,7 @@ async def test_async_send_packet_on_one_receive_on_other_bus(self, example_addre @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), - # TODO: add more after https://github.com/mdabrowski1990/uds/issues/266 and - # https://github.com/mdabrowski1990/uds/issues/267 + # TODO: add more after https://github.com/mdabrowski1990/uds/issues/266 ]) def test_send_message_on_one_receive_on_other_bus(self, example_addressing_information, example_addressing_information_2nd_node, @@ -1217,8 +1216,7 @@ def test_send_message_on_one_receive_on_other_bus(self, example_addressing_infor @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), - # TODO: add more after https://github.com/mdabrowski1990/uds/issues/266 and - # https://github.com/mdabrowski1990/uds/issues/267 + # TODO: add more after https://github.com/mdabrowski1990/uds/issues/266 ]) @pytest.mark.asyncio async def test_async_send_message_on_one_receive_on_other_bus(self, example_addressing_information, @@ -1255,7 +1253,7 @@ async def test_async_send_message_on_one_receive_on_other_bus(self, example_addr assert message_record_1.addressing_type == message_record_2.addressing_type == message.addressing_type assert message_record_1.payload == message_record_2.payload == message.payload - # TODO: Flow Control CONTINUE TO SEND with changing block size and STmin (including max value) + # TODO: after https://github.com/mdabrowski1990/uds/issues/266: Flow Control CONTINUE TO SEND with changing block size and STmin (including max value) # error guessing @@ -1453,6 +1451,71 @@ async def test_async_observe_tx_packet(self, example_addressing_information, exa # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert packet_record.transmission_time > datetime_before_send - # TODO: Flow Control max WAIT, then continue transmission - # TODO: Flow Control spam WAIT till timeout - # TODO: Flow Control OVERFLOW + # TODO: after https://github.com/mdabrowski1990/uds/issues/266: Flow Control max WAIT, then continue transmission + # TODO: after https://github.com/mdabrowski1990/uds/issues/266: Flow Control spam WAIT till timeout + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("send_after", [5, 45]) + def test_overflow_during_message_sending(self, example_addressing_information, + example_addressing_information_2nd_node, + message, send_after): + """ + Check for handling Overflow status during synchronous multi packet (FF + CF) UDS message sending. + + Procedure: + 1. Schedule Flow Control CAN Packet with Overflow information. + 2. Send a UDS message using Transport Interface (via CAN Interface). + Expected: UDS message transmission stopped and an exception raised. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information_2nd_node: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + :param send_after: Delay to use for sending CAN flow control. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.Overflow) + flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) + Timer(interval=send_after / 1000., function=self.can_interface_2.send, args=(flow_control_frame,)).start() + with pytest.raises(Exception): # TODO: replace with overflow exception + can_transport_interface.send_message(message) + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("send_after", [5, 45]) + @pytest.mark.asyncio + async def test_overflow_during_async_message_sending(self, example_addressing_information, + example_addressing_information_2nd_node, + message, send_after): + """ + Check for handling Overflow status during asynchronous multi packet (FF + CF) UDS message sending. + + Procedure: + 1. Schedule Flow Control CAN Packet with Overflow information. + 2. Send (using async method) a UDS message using Transport Interface (via CAN Interface). + Expected: UDS message transmission stopped and an exception raised. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information_2nd_node: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + :param send_after: Delay to use for sending CAN flow control. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.ContinueToSend, + block_size=0, + st_min=0) + flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) + + async def _send_frame(): + await asyncio.sleep(send_after / 1000.) + self.can_interface_2.send(flow_control_frame) + + # TODO: schedule tasks and catch exception From cd896e0b5fc139d35adabca4b4ca9765d90949f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Wed, 18 Sep 2024 13:16:36 +0200 Subject: [PATCH 03/20] simple ff+cf message sending - Add creation of Flow Control packets (for the next task =/). - Define Transmission Error and Waning. - Core implementation of synchronous sending of FF+CF message. --- .../segmentation/test_can_segmenter.py | 420 ++++++++++-------- .../test_python_can.py | 12 +- uds/packet/can_packet.py | 2 +- uds/segmentation/can_segmenter.py | 10 +- .../can_transport_interface.py | 63 ++- uds/utilities/__init__.py | 10 +- uds/utilities/custom_exceptions.py | 8 + uds/utilities/custom_warnings.py | 8 + 8 files changed, 325 insertions(+), 208 deletions(-) diff --git a/tests/software_tests/segmentation/test_can_segmenter.py b/tests/software_tests/segmentation/test_can_segmenter.py index c86f4f15..7d72e0a2 100644 --- a/tests/software_tests/segmentation/test_can_segmenter.py +++ b/tests/software_tests/segmentation/test_can_segmenter.py @@ -199,6 +199,196 @@ def test_filler_byte__set(self, value): self.mock_validate_raw_byte.assert_called_once_with(value) assert self.mock_can_segmenter._CanSegmenter__filler_byte == value + # __physical_segmentation + + @pytest.mark.parametrize("message_payload_size", [CanFirstFrameHandler.MAX_LONG_FF_DL_VALUE + 1, + CanFirstFrameHandler.MAX_LONG_FF_DL_VALUE + 23]) + @patch(f"{SCRIPT_LOCATION}.len") + def test_physical_segmentation__too_long(self, mock_len, message_payload_size): + mock_len.return_value = message_payload_size + mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.PHYSICAL) + with pytest.raises(SegmentationError): + CanSegmenter._CanSegmenter__physical_segmentation(self=self.mock_can_segmenter, message=mock_message) + mock_len.assert_called_once_with(mock_message.payload) + + @pytest.mark.parametrize("message_payload_size, max_payload", [ + (2, 2), + (60, 62) + ]) + @pytest.mark.parametrize("physical_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) + @patch(f"{SCRIPT_LOCATION}.len") + def test_physical_segmentation__sf_with_data_optimization(self, mock_len, + message_payload_size, max_payload, physical_ai): + mock_len.return_value = message_payload_size + self.mock_get_max_sf_payload_size.return_value = max_payload + self.mock_can_segmenter.use_data_optimization = True + self.mock_can_segmenter.tx_packets_physical_ai = physical_ai + mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.PHYSICAL) + packets = CanSegmenter._CanSegmenter__physical_segmentation(self=self.mock_can_segmenter, + message=mock_message) + assert isinstance(packets, tuple) + assert len(packets) == 1 + assert packets[0] == self.mock_can_packet_class.return_value + self.mock_get_max_sf_payload_size.assert_called_once_with( + addressing_format=self.mock_can_segmenter.addressing_format, + dlc=self.mock_can_segmenter.dlc) + mock_len.assert_called_once_with(mock_message.payload) + self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.SINGLE_FRAME, + payload=mock_message.payload, + dlc=None, + filler_byte=self.mock_can_segmenter.filler_byte, + **physical_ai) + + @pytest.mark.parametrize("message_payload_size, max_payload", [ + (2, 2), + (60, 62) + ]) + @pytest.mark.parametrize("physical_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) + @patch(f"{SCRIPT_LOCATION}.len") + def test_physical_segmentation__sf_without_data_optimization(self, mock_len, + message_payload_size, max_payload, physical_ai): + mock_len.return_value = message_payload_size + self.mock_get_max_sf_payload_size.return_value = max_payload + self.mock_can_segmenter.use_data_optimization = False + self.mock_can_segmenter.tx_packets_physical_ai = physical_ai + mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.PHYSICAL) + packets = CanSegmenter._CanSegmenter__physical_segmentation(self=self.mock_can_segmenter, + message=mock_message) + assert isinstance(packets, tuple) + assert len(packets) == 1 + assert packets[0] == self.mock_can_packet_class.return_value + self.mock_get_max_sf_payload_size.assert_called_once_with( + addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) + mock_len.assert_called_once_with(mock_message.payload) + self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.SINGLE_FRAME, + payload=mock_message.payload, + dlc=self.mock_can_segmenter.dlc, + filler_byte=self.mock_can_segmenter.filler_byte, + **physical_ai) + + @pytest.mark.parametrize("message_payload_size, max_payload, ff_size, cf_size", [ + (3, 2, 1, 2), + (150, 7, 6, 7), + ]) + @pytest.mark.parametrize("physical_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) + @patch(f"{SCRIPT_LOCATION}.len") + def test_physical_segmentation__ff_cf_with_data_optimization(self, mock_len, physical_ai, + message_payload_size, max_payload, ff_size, + cf_size): + mock_len.return_value = message_payload_size + self.mock_get_max_sf_payload_size.return_value = max_payload + self.mock_get_ff_payload_size.return_value = ff_size + self.mock_get_max_cf_payload_size.return_value = cf_size + self.mock_can_segmenter.use_data_optimization = True + self.mock_can_segmenter.tx_packets_physical_ai = physical_ai + mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.PHYSICAL, + payload=range(message_payload_size)) + packets = CanSegmenter._CanSegmenter__physical_segmentation(self=self.mock_can_segmenter, + message=mock_message) + assert isinstance(packets, tuple) + cf_number = (message_payload_size - ff_size + cf_size - 1) // cf_size + assert len(packets) == 1 + cf_number + self.mock_get_max_sf_payload_size.assert_called_once_with( + addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) + mock_len.assert_called_once_with(mock_message.payload) + last_cf_payload = (message_payload_size - ff_size) % cf_size + if last_cf_payload == 0: + last_cf_payload = cf_size + ff_call = call(packet_type=CanPacketType.FIRST_FRAME, + payload=mock_message.payload[:ff_size], + dlc=self.mock_can_segmenter.dlc, + data_length=message_payload_size, + **physical_ai) + cf_calls = [] + for cf_i in range(cf_number - 1): + cf_payload_i_start = ff_size + cf_i * cf_size + cf_payload_i_stop = cf_payload_i_start + cf_size + cf_call = call(packet_type=CanPacketType.CONSECUTIVE_FRAME, + payload=mock_message.payload[cf_payload_i_start:cf_payload_i_stop], + sequence_number=(cf_i + 1) % 0x10, + dlc=self.mock_can_segmenter.dlc, + filler_byte=self.mock_can_segmenter.filler_byte, + **physical_ai) + cf_calls.append(cf_call) + last_cf_call = call(packet_type=CanPacketType.CONSECUTIVE_FRAME, + payload=mock_message.payload[-last_cf_payload:], + dlc=None, + filler_byte=self.mock_can_segmenter.filler_byte, + sequence_number=cf_number % 16, + **physical_ai) + self.mock_can_packet_class.assert_has_calls([ff_call, *cf_calls, last_cf_call]) + + # __functional_segmentation + + @pytest.mark.parametrize("message_payload_size, max_payload", [ + (2, 1), + (60, 59) + ]) + @patch(f"{SCRIPT_LOCATION}.len") + def test_functional_segmentation__too_long(self, mock_len, message_payload_size, max_payload): + mock_len.return_value = message_payload_size + self.mock_get_max_sf_payload_size.return_value = max_payload + mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.FUNCTIONAL) + with pytest.raises(SegmentationError): + CanSegmenter._CanSegmenter__functional_segmentation(self=self.mock_can_segmenter, message=mock_message) + self.mock_get_max_sf_payload_size.assert_called_once_with( + addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) + mock_len.assert_called_once_with(mock_message.payload) + + @pytest.mark.parametrize("message_payload_size, max_payload", [ + (2, 2), + (60, 62) + ]) + @pytest.mark.parametrize("functional_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) + @patch(f"{SCRIPT_LOCATION}.len") + def test_functional_segmentation__with_data_optimization(self, mock_len, message_payload_size, max_payload, + functional_ai): + mock_len.return_value = message_payload_size + self.mock_get_max_sf_payload_size.return_value = max_payload + self.mock_can_segmenter.use_data_optimization = True + self.mock_can_segmenter.tx_packets_functional_ai = functional_ai + mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.FUNCTIONAL) + packets = CanSegmenter._CanSegmenter__functional_segmentation(self=self.mock_can_segmenter, + message=mock_message) + assert isinstance(packets, tuple) + assert len(packets) == 1 + assert packets[0] == self.mock_can_packet_class.return_value + self.mock_get_max_sf_payload_size.assert_called_once_with( + addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) + mock_len.assert_called_once_with(mock_message.payload) + self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.SINGLE_FRAME, + payload=mock_message.payload, + dlc=None, + filler_byte=self.mock_can_segmenter.filler_byte, + **functional_ai) + + @pytest.mark.parametrize("message_payload_size, max_payload", [ + (2, 2), + (60, 62) + ]) + @pytest.mark.parametrize("functional_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) + @patch(f"{SCRIPT_LOCATION}.len") + def test_functional_segmentation__without_data_optimization(self, mock_len, message_payload_size, max_payload, + functional_ai): + mock_len.return_value = message_payload_size + self.mock_get_max_sf_payload_size.return_value = max_payload + self.mock_can_segmenter.use_data_optimization = False + self.mock_can_segmenter.tx_packets_functional_ai = functional_ai + mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.FUNCTIONAL) + packets = CanSegmenter._CanSegmenter__functional_segmentation(self=self.mock_can_segmenter, + message=mock_message) + assert isinstance(packets, tuple) + assert len(packets) == 1 + assert packets[0] == self.mock_can_packet_class.return_value + self.mock_get_max_sf_payload_size.assert_called_once_with( + addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) + mock_len.assert_called_once_with(mock_message.payload) + self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.SINGLE_FRAME, + payload=mock_message.payload, + dlc=self.mock_can_segmenter.dlc, + filler_byte=self.mock_can_segmenter.filler_byte, + **functional_ai) + # is_input_packet @pytest.mark.parametrize("frame_can_id, frame_data", [ @@ -346,6 +536,46 @@ def test_is_desegmented_message__first_frame__true(self, packets): self.mock_is_initial_packet_type.assert_has_calls( [call(packet.packet_type) for packet in packets]) + # get_flow_control_packet + + @pytest.mark.parametrize("flow_status, block_size, st_min", [ + (Mock(), Mock(), Mock()), + ("flow_status", "block_size", "st_min"), + ]) + @pytest.mark.parametrize("physical_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) + def test_get_flow_control_packet__data_optimization(self, flow_status, block_size, st_min, physical_ai): + self.mock_can_segmenter.tx_packets_physical_ai = physical_ai + self.mock_can_segmenter.use_data_optimization = True + assert CanSegmenter.get_flow_control_packet(self=self.mock_can_segmenter, flow_status=flow_status, + block_size=block_size, st_min=st_min) \ + == self.mock_can_packet_class.return_value + self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.FLOW_CONTROL, + dlc=None, + filler_byte=self.mock_can_segmenter.filler_byte, + flow_status=flow_status, + block_size=block_size, + st_min=st_min, + **physical_ai) + + @pytest.mark.parametrize("flow_status, block_size, st_min", [ + (Mock(), Mock(), Mock()), + ("flow_status", "block_size", "st_min"), + ]) + @pytest.mark.parametrize("physical_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) + def test_get_flow_control_packet__no_data_optimization(self, flow_status, block_size, st_min, physical_ai): + self.mock_can_segmenter.tx_packets_physical_ai = physical_ai + self.mock_can_segmenter.use_data_optimization = False + assert CanSegmenter.get_flow_control_packet(self=self.mock_can_segmenter, flow_status=flow_status, + block_size=block_size, st_min=st_min) \ + == self.mock_can_packet_class.return_value + self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.FLOW_CONTROL, + dlc=self.mock_can_segmenter.dlc, + filler_byte=self.mock_can_segmenter.filler_byte, + flow_status=flow_status, + block_size=block_size, + st_min=st_min, + **physical_ai) + # desegmentation @pytest.mark.parametrize("packets", [ @@ -468,196 +698,6 @@ def test_segmentation__physical(self, mock_isinstance): == self.mock_can_segmenter._CanSegmenter__physical_segmentation.return_value self.mock_can_segmenter._CanSegmenter__physical_segmentation.assert_called_once_with(mock_message) - # __physical_segmentation - - @pytest.mark.parametrize("message_payload_size", [CanFirstFrameHandler.MAX_LONG_FF_DL_VALUE + 1, - CanFirstFrameHandler.MAX_LONG_FF_DL_VALUE + 23]) - @patch(f"{SCRIPT_LOCATION}.len") - def test_physical_segmentation__too_long(self, mock_len, message_payload_size): - mock_len.return_value = message_payload_size - mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.PHYSICAL) - with pytest.raises(SegmentationError): - CanSegmenter._CanSegmenter__physical_segmentation(self=self.mock_can_segmenter, message=mock_message) - mock_len.assert_called_once_with(mock_message.payload) - - @pytest.mark.parametrize("message_payload_size, max_payload", [ - (2, 2), - (60, 62) - ]) - @pytest.mark.parametrize("physical_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) - @patch(f"{SCRIPT_LOCATION}.len") - def test_physical_segmentation__sf_with_data_optimization(self, mock_len, - message_payload_size, max_payload, physical_ai): - mock_len.return_value = message_payload_size - self.mock_get_max_sf_payload_size.return_value = max_payload - self.mock_can_segmenter.use_data_optimization = True - self.mock_can_segmenter.tx_packets_physical_ai = physical_ai - mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.PHYSICAL) - packets = CanSegmenter._CanSegmenter__physical_segmentation(self=self.mock_can_segmenter, - message=mock_message) - assert isinstance(packets, tuple) - assert len(packets) == 1 - assert packets[0] == self.mock_can_packet_class.return_value - self.mock_get_max_sf_payload_size.assert_called_once_with( - addressing_format=self.mock_can_segmenter.addressing_format, - dlc=self.mock_can_segmenter.dlc) - mock_len.assert_called_once_with(mock_message.payload) - self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.SINGLE_FRAME, - payload=mock_message.payload, - dlc=None, - filler_byte=self.mock_can_segmenter.filler_byte, - **physical_ai) - - @pytest.mark.parametrize("message_payload_size, max_payload", [ - (2, 2), - (60, 62) - ]) - @pytest.mark.parametrize("physical_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) - @patch(f"{SCRIPT_LOCATION}.len") - def test_physical_segmentation__sf_without_data_optimization(self, mock_len, - message_payload_size, max_payload, physical_ai): - mock_len.return_value = message_payload_size - self.mock_get_max_sf_payload_size.return_value = max_payload - self.mock_can_segmenter.use_data_optimization = False - self.mock_can_segmenter.tx_packets_physical_ai = physical_ai - mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.PHYSICAL) - packets = CanSegmenter._CanSegmenter__physical_segmentation(self=self.mock_can_segmenter, - message=mock_message) - assert isinstance(packets, tuple) - assert len(packets) == 1 - assert packets[0] == self.mock_can_packet_class.return_value - self.mock_get_max_sf_payload_size.assert_called_once_with( - addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) - mock_len.assert_called_once_with(mock_message.payload) - self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.SINGLE_FRAME, - payload=mock_message.payload, - dlc=self.mock_can_segmenter.dlc, - filler_byte=self.mock_can_segmenter.filler_byte, - **physical_ai) - - @pytest.mark.parametrize("message_payload_size, max_payload, ff_size, cf_size", [ - (3, 2, 1, 2), - (150, 7, 6, 7), - ]) - @pytest.mark.parametrize("physical_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) - @patch(f"{SCRIPT_LOCATION}.len") - def test_physical_segmentation__ff_cf_with_data_optimization(self, mock_len, physical_ai, - message_payload_size, max_payload, ff_size, - cf_size): - mock_len.return_value = message_payload_size - self.mock_get_max_sf_payload_size.return_value = max_payload - self.mock_get_ff_payload_size.return_value = ff_size - self.mock_get_max_cf_payload_size.return_value = cf_size - self.mock_can_segmenter.use_data_optimization = True - self.mock_can_segmenter.tx_packets_physical_ai = physical_ai - mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.PHYSICAL, - payload=range(message_payload_size)) - packets = CanSegmenter._CanSegmenter__physical_segmentation(self=self.mock_can_segmenter, - message=mock_message) - assert isinstance(packets, tuple) - cf_number = (message_payload_size - ff_size + cf_size - 1) // cf_size - assert len(packets) == 1 + cf_number - self.mock_get_max_sf_payload_size.assert_called_once_with( - addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) - mock_len.assert_called_once_with(mock_message.payload) - last_cf_payload = (message_payload_size - ff_size) % cf_size - if last_cf_payload == 0: - last_cf_payload = cf_size - ff_call = call(packet_type=CanPacketType.FIRST_FRAME, - payload=mock_message.payload[:ff_size], - dlc=self.mock_can_segmenter.dlc, - data_length=message_payload_size, - **physical_ai) - cf_calls = [] - for cf_i in range(cf_number - 1): - cf_payload_i_start = ff_size + cf_i * cf_size - cf_payload_i_stop = cf_payload_i_start + cf_size - cf_call = call(packet_type=CanPacketType.CONSECUTIVE_FRAME, - payload=mock_message.payload[cf_payload_i_start:cf_payload_i_stop], - sequence_number=(cf_i + 1) % 0x10, - dlc=self.mock_can_segmenter.dlc, - filler_byte=self.mock_can_segmenter.filler_byte, - **physical_ai) - cf_calls.append(cf_call) - last_cf_call = call(packet_type=CanPacketType.CONSECUTIVE_FRAME, - payload=mock_message.payload[-last_cf_payload:], - dlc=None, - filler_byte=self.mock_can_segmenter.filler_byte, - sequence_number=cf_number % 16, - **physical_ai) - self.mock_can_packet_class.assert_has_calls([ff_call, *cf_calls, last_cf_call]) - - # __functional_segmentation - - @pytest.mark.parametrize("message_payload_size, max_payload", [ - (2, 1), - (60, 59) - ]) - @patch(f"{SCRIPT_LOCATION}.len") - def test_functional_segmentation__too_long(self, mock_len, message_payload_size, max_payload): - mock_len.return_value = message_payload_size - self.mock_get_max_sf_payload_size.return_value = max_payload - mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.FUNCTIONAL) - with pytest.raises(SegmentationError): - CanSegmenter._CanSegmenter__functional_segmentation(self=self.mock_can_segmenter, message=mock_message) - self.mock_get_max_sf_payload_size.assert_called_once_with( - addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) - mock_len.assert_called_once_with(mock_message.payload) - - @pytest.mark.parametrize("message_payload_size, max_payload", [ - (2, 2), - (60, 62) - ]) - @pytest.mark.parametrize("functional_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) - @patch(f"{SCRIPT_LOCATION}.len") - def test_functional_segmentation__with_data_optimization(self, mock_len, message_payload_size, max_payload, - functional_ai): - mock_len.return_value = message_payload_size - self.mock_get_max_sf_payload_size.return_value = max_payload - self.mock_can_segmenter.use_data_optimization = True - self.mock_can_segmenter.tx_packets_functional_ai = functional_ai - mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.FUNCTIONAL) - packets = CanSegmenter._CanSegmenter__functional_segmentation(self=self.mock_can_segmenter, - message=mock_message) - assert isinstance(packets, tuple) - assert len(packets) == 1 - assert packets[0] == self.mock_can_packet_class.return_value - self.mock_get_max_sf_payload_size.assert_called_once_with( - addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) - mock_len.assert_called_once_with(mock_message.payload) - self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.SINGLE_FRAME, - payload=mock_message.payload, - dlc=None, - filler_byte=self.mock_can_segmenter.filler_byte, - **functional_ai) - - @pytest.mark.parametrize("message_payload_size, max_payload", [ - (2, 2), - (60, 62) - ]) - @pytest.mark.parametrize("functional_ai", [{"p1": 1, "p2": 2, "p3": 3}, {"abc": "something", "xyz": "else"}]) - @patch(f"{SCRIPT_LOCATION}.len") - def test_functional_segmentation__without_data_optimization(self, mock_len, message_payload_size, max_payload, - functional_ai): - mock_len.return_value = message_payload_size - self.mock_get_max_sf_payload_size.return_value = max_payload - self.mock_can_segmenter.use_data_optimization = False - self.mock_can_segmenter.tx_packets_functional_ai = functional_ai - mock_message = Mock(spec=UdsMessage, addressing_type=AddressingType.FUNCTIONAL) - packets = CanSegmenter._CanSegmenter__functional_segmentation(self=self.mock_can_segmenter, - message=mock_message) - assert isinstance(packets, tuple) - assert len(packets) == 1 - assert packets[0] == self.mock_can_packet_class.return_value - self.mock_get_max_sf_payload_size.assert_called_once_with( - addressing_format=self.mock_can_segmenter.addressing_format, dlc=self.mock_can_segmenter.dlc) - mock_len.assert_called_once_with(mock_message.payload) - self.mock_can_packet_class.assert_called_once_with(packet_type=CanPacketType.SINGLE_FRAME, - payload=mock_message.payload, - dlc=self.mock_can_segmenter.dlc, - filler_byte=self.mock_can_segmenter.filler_byte, - **functional_ai) - @pytest.mark.integration class TestCanSegmenterIntegration: diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index e6d478f2..e7c5067e 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -740,7 +740,7 @@ def test_send_message__sf(self, example_addressing_information, message): UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), ]) - @pytest.mark.parametrize("send_after", [5, 45]) + @pytest.mark.parametrize("send_after", [5, 970]) def test_send_message__multi_packets(self, example_addressing_information, example_addressing_information_2nd_node, message, send_after): @@ -786,6 +786,8 @@ def test_send_message__multi_packets(self, example_addressing_information, # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send + # TODO: timeout after FF + # async_send_message @pytest.mark.parametrize("message", [ @@ -827,7 +829,7 @@ async def test_async_send_message__sf(self, example_addressing_information, mess UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), ]) - @pytest.mark.parametrize("send_after", [5, 45]) + @pytest.mark.parametrize("send_after", [5, 970]) @pytest.mark.asyncio async def test_async_send_message__multi_packets(self, example_addressing_information, example_addressing_information_2nd_node, @@ -1458,7 +1460,7 @@ async def test_async_observe_tx_packet(self, example_addressing_information, exa UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), ]) - @pytest.mark.parametrize("send_after", [5, 45]) + @pytest.mark.parametrize("send_after", [5, 970]) def test_overflow_during_message_sending(self, example_addressing_information, example_addressing_information_2nd_node, message, send_after): @@ -1481,14 +1483,14 @@ def test_overflow_during_message_sending(self, example_addressing_information, flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.Overflow) flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) Timer(interval=send_after / 1000., function=self.can_interface_2.send, args=(flow_control_frame,)).start() - with pytest.raises(Exception): # TODO: replace with overflow exception + with pytest.raises(OverflowError): can_transport_interface.send_message(message) @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), ]) - @pytest.mark.parametrize("send_after", [5, 45]) + @pytest.mark.parametrize("send_after", [5, 970]) @pytest.mark.asyncio async def test_overflow_during_async_message_sending(self, example_addressing_information, example_addressing_information_2nd_node, diff --git a/uds/packet/can_packet.py b/uds/packet/can_packet.py index 1ef74666..f5e3d26c 100644 --- a/uds/packet/can_packet.py +++ b/uds/packet/can_packet.py @@ -85,7 +85,7 @@ def __init__(self, *, Flow status information carried by this Flow Control frame. - :parameter block_size: (required for: FC with ContinueToSend Flow Status) Block size information carried by this Flow Control frame. - - :parameter st_min: (required for: FC with ContinueToSend Flow Status) + - :parameter **: (required for: FC with ContinueToSend Flow Status) Separation Time minimum information carried by this Flow Control frame. """ # initialize the variables diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py index c1280276..208fbca8 100644 --- a/uds/segmentation/can_segmenter.py +++ b/uds/segmentation/can_segmenter.py @@ -12,9 +12,9 @@ CanConsecutiveFrameHandler, CanDlcHandler, CanFirstFrameHandler, + CanFlowStatus, CanSingleFrameHandler, PacketAIParamsAlias, - CanFlowStatus ) from uds.message import UdsMessage, UdsMessageRecord from uds.packet import ( @@ -313,7 +313,13 @@ def get_flow_control_packet(self, :return: Flow Control CAN packet with provided parameters. """ - # TODO: add code + return CanPacket(packet_type=CanPacketType.FLOW_CONTROL, + flow_status=flow_status, + block_size=block_size, + st_min=st_min, + filler_byte=self.filler_byte, + dlc=None if self.use_data_optimization else self.dlc, + **self.tx_packets_physical_ai) def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage, UdsMessageRecord]: """ diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 3800a325..b96d1d95 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -5,8 +5,8 @@ from abc import abstractmethod from asyncio import AbstractEventLoop, get_running_loop, wait_for from datetime import datetime -from time import time -from typing import Any, Optional +from time import sleep, time +from typing import Any, List, Optional, Tuple from warnings import warn from can import AsyncBufferedReader, BufferedReader, BusABC, Message, Notifier @@ -14,14 +14,21 @@ AbstractCanAddressingInformation, AbstractFlowControlParametersGenerator, CanDlcHandler, + CanFlowStatus, CanIdHandler, + CanSTminTranslator, DefaultFlowControlParametersGenerator, ) from uds.message import UdsMessage, UdsMessageRecord from uds.packet import CanPacket, CanPacketRecord, CanPacketType from uds.segmentation import CanSegmenter from uds.transmission_attributes import TransmissionDirection -from uds.utilities import TimeMillisecondsAlias, ValueWarning +from uds.utilities import ( + TimeMillisecondsAlias, + TransmissionInterruptionError, + TransmissionInterruptionWarning, + ValueWarning, +) from .abstract_transport_interface import AbstractTransportInterface @@ -527,6 +534,26 @@ def _setup_async_notifier(self, loop: AbstractEventLoop) -> None: timeout=self._MIN_NOTIFIER_TIMEOUT, loop=loop) + def _send_packets_block(self, + packets: List[CanPacket], + delay: TimeMillisecondsAlias) -> Tuple[CanPacketRecord, ...]: + """ + Send block of packets + + :param packets: CAN packets to send. + :param delay: Minimal delay between sending following packets [ms]. + + :return: Records with historic information about transmitted CAN packets. + """ + packet_records = [] + for packet in packets: + sleep(delay / 1000.) + if self.__frames_buffer.buffer.qsize(): + ... + # TODO: handle packets that were received + packet_records.append(self.send_packet(packet)) + return tuple(packet_records) + def clear_frames_buffers(self) -> None: """ Clear buffers with transmitted and received frames. @@ -718,13 +745,33 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: :param message: A message to send. + :raise OverflowError: TODO + :raise TODO: ERROR + :return: Record with historic information about transmitted UDS message. """ - packets_to_send = self.segmenter.segmentation(message) - if len(packets_to_send) == 1: - packet_record = self.send_packet(*packets_to_send) - return UdsMessageRecord((packet_record,)) - # TODO: implementation + # TODO: update: n_as_measured, n_bs_measured + packets_to_send = list(self.segmenter.segmentation(message)) + packet_records = [self.send_packet(packets_to_send.pop(0))] + while packets_to_send: + fc_record = self.receive_packet(timeout=self.N_BS_TIMEOUT) + packet_records.append(fc_record) + if fc_record.packet_type != fc_record.packet_type.FLOW_CONTROL: + if CanPacketType.is_initial_packet_type(fc_record.packet_type): + raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") + warn(message="CAN message transmission interrupted by an unrelated CAN packet.", + category=TransmissionInterruptionWarning) + if fc_record.flow_status == CanFlowStatus.Overflow: + raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") + if fc_record.flow_status == CanFlowStatus.ContinueToSend: + number_of_packets = len(packets_to_send) if fc_record.block_size == 0 else fc_record.block_size + delay_between_cf = CanSTminTranslator.decode(fc_record.flow_status) if self.n_cs is None else self.n_cs + packet_records.extend(self._send_packets_block(packets=packets_to_send[:number_of_packets], + delay=delay_between_cf)) + packets_to_send = packets_to_send[number_of_packets:] + elif fc_record.flow_status != CanFlowStatus.Wait: + raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") + return UdsMessageRecord(packet_records) def receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> UdsMessageRecord: """ diff --git a/uds/utilities/__init__.py b/uds/utilities/__init__.py index 519ac3a5..4ef562a6 100644 --- a/uds/utilities/__init__.py +++ b/uds/utilities/__init__.py @@ -11,6 +11,12 @@ validate_raw_byte, validate_raw_bytes, ) -from .custom_exceptions import AmbiguityError, InconsistentArgumentsError, ReassignmentError, UnusedArgumentError -from .custom_warnings import UnusedArgumentWarning, ValueWarning +from .custom_exceptions import ( + AmbiguityError, + InconsistentArgumentsError, + ReassignmentError, + TransmissionInterruptionError, + UnusedArgumentError, +) +from .custom_warnings import TransmissionInterruptionWarning, UnusedArgumentWarning, ValueWarning from .enums import ByteEnum, ExtendableEnum, NibbleEnum, ValidatedEnum diff --git a/uds/utilities/custom_exceptions.py b/uds/utilities/custom_exceptions.py index 62d53ee6..10c0f5a7 100644 --- a/uds/utilities/custom_exceptions.py +++ b/uds/utilities/custom_exceptions.py @@ -43,3 +43,11 @@ class UnusedArgumentError(ValueError): class AmbiguityError(ValueError): """Operation cannot be executed because it is ambiguous.""" + + +class TransmissionInterruptionError(RuntimeError): + """ + An unexpected packet was received during UDS message transmission. + + According to UDS ISO Standards the transmission shall be stopped. + """ diff --git a/uds/utilities/custom_warnings.py b/uds/utilities/custom_warnings.py index 8b41836e..593ba806 100644 --- a/uds/utilities/custom_warnings.py +++ b/uds/utilities/custom_warnings.py @@ -18,3 +18,11 @@ class UnusedArgumentWarning(Warning): class ValueWarning(Warning): """Value of the argument is out of typical range, but the package is able to handle it.""" + + +class TransmissionInterruptionWarning(RuntimeWarning): + """ + An unexpected packet was received during UDS message transmission. + + According to UDS ISO Standards it shall be ignored. + """ From 7ce29dfc3f1137faa627f21c2f1f29a6f61dd9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Wed, 18 Sep 2024 18:14:10 +0200 Subject: [PATCH 04/20] add async message sending - Implement async message sending - add checks for n_br_measured - rework n_cr_measured and n_bs_measured - add timeout test after ff (detecting fc is received too late) --- .../test_python_can.py | 105 ++++++++++++-- .../can_transport_interface.py | 128 ++++++++++++++---- 2 files changed, 197 insertions(+), 36 deletions(-) diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index e7c5067e..b76c5755 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -14,7 +14,7 @@ from uds.transport_interface import PyCanTransportInterface -class TestPythonCanKvaser: +class TestPythonCanKvaser: # TODO: add checks for all measured times (n_br, n_cs, etc) """ System Tests for `PyCanTransportInterface` with Kvaser as bus manager. @@ -731,6 +731,8 @@ def test_send_message__sf(self, example_addressing_information, message): assert message_record.transmission_start == message_record.transmission_end assert len(message_record.packets_records) == 1 assert message_record.packets_records[0].packet_type == CanPacketType.SINGLE_FRAME + # timing parameters + assert can_transport_interface.n_bs_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start @@ -781,12 +783,51 @@ def test_send_message__multi_packets(self, example_addressing_information, assert message_record.packets_records[1].direction == TransmissionDirection.RECEIVED assert all(following_packet.packet_type == CanPacketType.CONSECUTIVE_FRAME for following_packet in message_record.packets_records[2:]) + # timing parameters + assert isinstance(can_transport_interface.n_bs_measured, tuple) + assert len(can_transport_interface.n_bs_measured) == 1 # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send + # assert send_after - 5 <= can_transport_interface.n_bs_measured[0] <= send_after + 5 - # TODO: timeout after FF + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), + ]) + def test_send_message__multi_packets__timeout(self, example_addressing_information, + example_addressing_information_2nd_node, + message): + """ + Check for a timeout (N_Bs timeout exceeded) during synchronous multi packet (FF + CF) UDS message sending. + + Procedure: + 1. Schedule Flow Control CAN Packet just after N_Bs timeout. + 2. Send a UDS message using Transport Interface (via CAN Interface). + Expected: Timeout exception is raised. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information_2nd_node: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.ContinueToSend, + block_size=0, + st_min=0) + flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) + Timer(interval=(can_transport_interface.n_bs_timeout + 1) / 1000., + function=self.can_interface_2.send, args=(flow_control_frame,)).start() + time_before_receive = time() + with pytest.raises(TimeoutError): + can_transport_interface.send_message(message) + time_after_receive = time() + assert (can_transport_interface.n_bs_timeout + < (time_after_receive - time_before_receive) * 1000. + < can_transport_interface.n_bs_timeout + self.TASK_TIMING_TOLERANCE) + sleep(self.TASK_TIMING_TOLERANCE / 1000.) # wait till packet arrives # async_send_message @@ -820,6 +861,8 @@ async def test_async_send_message__sf(self, example_addressing_information, mess assert message_record.transmission_start == message_record.transmission_end assert len(message_record.packets_records) == 1 assert message_record.packets_records[0].packet_type == CanPacketType.SINGLE_FRAME + # timing parameters + assert can_transport_interface.n_bs_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start @@ -880,10 +923,58 @@ async def _send_frame(): assert message_record.packets_records[1].direction == TransmissionDirection.RECEIVED assert all(following_packet.packet_type == CanPacketType.CONSECUTIVE_FRAME for following_packet in message_record.packets_records[2:]) + # timing parameters + assert isinstance(can_transport_interface.n_bs_measured, tuple) + assert len(can_transport_interface.n_bs_measured) == 1 # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send + # assert send_after - 5 <= can_transport_interface.n_bs_measured[0] <= send_after + 5 + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.asyncio + async def test_async_send_message__multi_packets__timeout(self, example_addressing_information, + example_addressing_information_2nd_node, + message): + """ + Check for a timeout (N_Bs timeout exceeded) during asynchronous multi packet (FF + CF) UDS message sending. + + Procedure: + 1. Schedule Flow Control CAN Packet just after N_Bs timeout. + 2. Send (using async method) a UDS message using Transport Interface (via CAN Interface). + Expected: Timeout exception is raised. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information_2nd_node: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.ContinueToSend, + block_size=0, + st_min=0) + flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) + + async def _send_frame(): + await asyncio.sleep((can_transport_interface.n_bs_timeout + 1) / 1000.) + self.can_interface_2.send(flow_control_frame) + + future_record = can_transport_interface.async_send_message(message) + frame_sending_task = asyncio.create_task(_send_frame()) + time_before_receive = time() + with pytest.raises(TimeoutError): + await future_record + time_after_receive = time() + assert (can_transport_interface.n_bs_timeout + < (time_after_receive - time_before_receive) * 1000. + < can_transport_interface.n_bs_timeout + self.TASK_TIMING_TOLERANCE) + await frame_sending_task + sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.) # receive_message @@ -966,14 +1057,11 @@ def test_receive_message__sf(self, example_addressing_information, example_addre assert message_record.direction == TransmissionDirection.RECEIVED assert message_record.payload == message.payload assert message_record.addressing_type == message.addressing_type + assert message_record.transmission_start == message_record.transmission_end # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send - if len(message_record.packets_records) == 1: - assert message_record.transmission_start == message_record.transmission_end - else: - assert message_record.transmission_start < message_record.transmission_end # TODO: add more with https://github.com/mdabrowski1990/uds/issues/266 @@ -1077,14 +1165,11 @@ async def _send_frame(): assert message_record.direction == TransmissionDirection.RECEIVED assert message_record.payload == message.payload assert message_record.addressing_type == message.addressing_type + assert message_record.transmission_start == message_record.transmission_end # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send - if len(message_record.packets_records) == 1: - assert message_record.transmission_start == message_record.transmission_end - else: - assert message_record.transmission_start < message_record.transmission_end # TODO: add more with https://github.com/mdabrowski1990/uds/issues/266 diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index b96d1d95..f2ac9f04 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -2,6 +2,7 @@ __all__ = ["AbstractCanTransportInterface", "PyCanTransportInterface"] +import asyncio from abc import abstractmethod from asyncio import AbstractEventLoop, get_running_loop, wait_for from datetime import datetime @@ -195,11 +196,11 @@ def n_bs_timeout(self, value: TimeMillisecondsAlias): @property # noqa @abstractmethod - def n_bs_measured(self) -> Optional[TimeMillisecondsAlias]: + def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: """ - Get the last measured value of N_Bs time parameter. + Get the last measured values (during the last message transmission) of N_Bs time parameter. - :return: Time in milliseconds or None if the value was never measured. + :return: Tuple with times in milliseconds or None if the values were never measured. """ @property @@ -309,11 +310,11 @@ def n_cr_timeout(self, value: TimeMillisecondsAlias): @property # noqa @abstractmethod - def n_cr_measured(self) -> Optional[TimeMillisecondsAlias]: + def n_cr_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: """ - Get the last measured value of N_Cr time parameter. + Get the last measured values (during the last message reception) of N_Cr time parameter. - :return: Time in milliseconds or None if the value was never measured. + :return: Tuple with times in milliseconds or None if the values were never measured. """ # Communication parameters @@ -430,8 +431,8 @@ def __init__(self, """ self.__n_as_measured: Optional[TimeMillisecondsAlias] = None self.__n_ar_measured: Optional[TimeMillisecondsAlias] = None - self.__n_bs_measured: Optional[TimeMillisecondsAlias] = None - self.__n_cr_measured: Optional[TimeMillisecondsAlias] = None + self.__n_bs_measured: Optional[Tuple[TimeMillisecondsAlias]] = None + self.__n_cr_measured: Optional[Tuple[TimeMillisecondsAlias]] = None super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, **kwargs) @@ -463,8 +464,8 @@ def n_ar_measured(self) -> Optional[TimeMillisecondsAlias]: """ return self.__n_ar_measured - @property # noqa - def n_bs_measured(self) -> Optional[TimeMillisecondsAlias]: + @property + def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: """ Get the last measured value of N_Bs time parameter. @@ -472,8 +473,8 @@ def n_bs_measured(self) -> Optional[TimeMillisecondsAlias]: """ return self.__n_bs_measured - @property # noqa - def n_cr_measured(self) -> Optional[TimeMillisecondsAlias]: + @property + def n_cr_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: """ Get the last measured value of N_Cr time parameter. @@ -538,7 +539,7 @@ def _send_packets_block(self, packets: List[CanPacket], delay: TimeMillisecondsAlias) -> Tuple[CanPacketRecord, ...]: """ - Send block of packets + Send block of CAN packets. :param packets: CAN packets to send. :param delay: Minimal delay between sending following packets [ms]. @@ -548,12 +549,57 @@ def _send_packets_block(self, packet_records = [] for packet in packets: sleep(delay / 1000.) - if self.__frames_buffer.buffer.qsize(): - ... - # TODO: handle packets that were received + while self.__frames_buffer.buffer.qsize() > 0: + received_frame = self.__frames_buffer.buffer.get_nowait() + packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, + data=received_frame.data) + if packet_addressing_type is not None: + received_packet = CanPacketRecord(frame=received_frame, + direction=TransmissionDirection.RECEIVED, + addressing_type=packet_addressing_type, + addressing_format=self.segmenter.addressing_format, + transmission_time=datetime.fromtimestamp( + received_frame.timestamp)) + if CanPacketType.is_initial_packet_type(received_packet.packet_type): + raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") + warn(message="CAN message transmission interrupted by an unrelated CAN packet.", + category=TransmissionInterruptionWarning) packet_records.append(self.send_packet(packet)) return tuple(packet_records) + async def _async_send_packets_block(self, packets: List[CanPacket], + delay: TimeMillisecondsAlias, + loop: Optional[AbstractEventLoop] = None) -> Tuple[CanPacketRecord, ...]: + """ + Send block of CAN packets asynchronously. + + :param packets: CAN packets to send. + :param delay: Minimal delay between sending following packets [ms]. + :param loop: An asyncio event loop to use for scheduling this task. + + :return: Records with historic information about transmitted CAN packets. + """ + packet_records = [] + for packet in packets: + await asyncio.sleep(delay / 1000.) + while self.__frames_buffer.buffer.qsize() > 0: + received_frame = self.__frames_buffer.buffer.get_nowait() + packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, + data=received_frame.data) + if packet_addressing_type is not None: + received_packet = CanPacketRecord(frame=received_frame, + direction=TransmissionDirection.RECEIVED, + addressing_type=packet_addressing_type, + addressing_format=self.segmenter.addressing_format, + transmission_time=datetime.fromtimestamp( + received_frame.timestamp)) + if CanPacketType.is_initial_packet_type(received_packet.packet_type): + raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") + warn(message="CAN message transmission interrupted by an unrelated CAN packet.", + category=TransmissionInterruptionWarning) + packet_records.append(await self.async_send_packet(packet, loop=loop)) + return tuple(packet_records) + def clear_frames_buffers(self) -> None: """ Clear buffers with transmitted and received frames. @@ -586,7 +632,6 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore :return: Record with historic information about transmitted CAN packet. """ - time_start = time() if not isinstance(packet, CanPacket): raise TypeError("Provided packet value does not contain a CAN Packet.") is_flow_control_packet = packet.packet_type == CanPacketType.FLOW_CONTROL @@ -597,6 +642,7 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) self._setup_notifier() self.clear_frames_buffers() + time_start = time() self.bus_manager.send(can_frame) observed_frame = None while observed_frame is None \ @@ -668,7 +714,6 @@ async def async_send_packet(self, :return: Record with historic information about transmitted CAN packet. """ - time_start = time() if not isinstance(packet, CanPacket): raise TypeError("Provided packet value does not contain a CAN Packet.") loop = get_running_loop() if loop is None else loop @@ -680,6 +725,7 @@ async def async_send_packet(self, is_extended_id=CanIdHandler.is_extended_can_id(packet.can_id), data=packet.raw_frame_data, is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) + time_start = time() self.bus_manager.send(can_frame) observed_frame = None while observed_frame is None \ @@ -745,16 +791,18 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: :param message: A message to send. - :raise OverflowError: TODO - :raise TODO: ERROR + :raise OverflowError: Flow Control packet Flow Status equal to OVERFLOW was received. + :raise TransmissionInterruptionError: A new UDS message transmission interrupted this message sending. :return: Record with historic information about transmitted UDS message. """ - # TODO: update: n_as_measured, n_bs_measured + n_bs_measurements = [] packets_to_send = list(self.segmenter.segmentation(message)) packet_records = [self.send_packet(packets_to_send.pop(0))] + time_n_bs_measurement_start = time() while packets_to_send: fc_record = self.receive_packet(timeout=self.N_BS_TIMEOUT) + n_bs_measurements.append((time()-time_n_bs_measurement_start)*1000.) packet_records.append(fc_record) if fc_record.packet_type != fc_record.packet_type.FLOW_CONTROL: if CanPacketType.is_initial_packet_type(fc_record.packet_type): @@ -768,9 +816,13 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: delay_between_cf = CanSTminTranslator.decode(fc_record.flow_status) if self.n_cs is None else self.n_cs packet_records.extend(self._send_packets_block(packets=packets_to_send[:number_of_packets], delay=delay_between_cf)) + time_n_bs_measurement_start = time() packets_to_send = packets_to_send[number_of_packets:] elif fc_record.flow_status != CanFlowStatus.Wait: raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") + else: + time_n_bs_measurement_start = time() + self.__n_bs_measured = tuple(n_bs_measurements) if n_bs_measurements else None return UdsMessageRecord(packet_records) def receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> UdsMessageRecord: @@ -809,11 +861,35 @@ async def async_send_message(self, :return: Record with historic information about transmitted UDS message. """ - packets_to_send = self.segmenter.segmentation(message) - if len(packets_to_send) == 1: - packet_record = await self.async_send_packet(*packets_to_send, loop=loop) - return UdsMessageRecord((packet_record,)) # type - raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/267") + n_bs_measurements = [] + packets_to_send = list(self.segmenter.segmentation(message)) + packet_records = [await self.async_send_packet(packets_to_send.pop(0))] + time_n_bs_measurement_start = time() + while packets_to_send: + fc_record = await self.async_receive_packet(timeout=self.N_BS_TIMEOUT, loop=loop) + n_bs_measurements.append((time() - time_n_bs_measurement_start) * 1000.) + packet_records.append(fc_record) + if fc_record.packet_type != fc_record.packet_type.FLOW_CONTROL: + if CanPacketType.is_initial_packet_type(fc_record.packet_type): + raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") + warn(message="CAN message transmission interrupted by an unrelated CAN packet.", + category=TransmissionInterruptionWarning) + if fc_record.flow_status == CanFlowStatus.Overflow: + raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") + if fc_record.flow_status == CanFlowStatus.ContinueToSend: + number_of_packets = len(packets_to_send) if fc_record.block_size == 0 else fc_record.block_size + delay_between_cf = CanSTminTranslator.decode(fc_record.flow_status) if self.n_cs is None else self.n_cs + packet_records.extend(await self._async_send_packets_block(packets=packets_to_send[:number_of_packets], + delay=delay_between_cf, + loop=loop)) + time_n_bs_measurement_start = time() + packets_to_send = packets_to_send[number_of_packets:] + elif fc_record.flow_status != CanFlowStatus.Wait: + raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") + else: + time_n_bs_measurement_start = time() + self.__n_bs_measured = tuple(n_bs_measurements) if n_bs_measurements else None + return UdsMessageRecord(packet_records) async def async_receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None, From 8b82b4981f1f6165c86223be18abf2215fe41939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Thu, 19 Sep 2024 12:57:35 +0200 Subject: [PATCH 05/20] updated tests - Adjusted expectations in system tests - Provided examples of timing issues with python-can --- .../kvaser/python_can_timing_issue.py | 6 ++- .../kvaser/python_can_timing_issue_2.py | 32 ++++++++++++ .../test_python_can.py | 49 +++++++++++++++++-- 3 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 examples/can/python-can/kvaser/python_can_timing_issue_2.py diff --git a/examples/can/python-can/kvaser/python_can_timing_issue.py b/examples/can/python-can/kvaser/python_can_timing_issue.py index c4e06425..4aa70371 100644 --- a/examples/can/python-can/kvaser/python_can_timing_issue.py +++ b/examples/can/python-can/kvaser/python_can_timing_issue.py @@ -14,7 +14,8 @@ message = Message(data=[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], arbitration_id=0x100) - for _ in range(100): + for _ in range(10): + timestamp_before_send = time() Timer(interval=0.1, function=kvaser_interface_1.send, args=(message,)).start() sent_message = buffered_reader.get_message(timeout=1) @@ -22,9 +23,10 @@ print(f"-----------------------------------------------\n" f"Result:\n" + f"Timestamp before send: {timestamp_before_send}\n" f"Message timestamp: {sent_message.timestamp}\n" f"Current timestamp: {timestamp_after_send}\n" - f"Message timestamp <= Current timestamp: {sent_message.timestamp <= timestamp_after_send} (expected `True`)") + f"Timestamp before send <= Message timestamp <= Current timestamp: {timestamp_before_send <= sent_message.timestamp <= timestamp_after_send} (expected `True`)") kvaser_interface_1.shutdown() kvaser_interface_2.shutdown() diff --git a/examples/can/python-can/kvaser/python_can_timing_issue_2.py b/examples/can/python-can/kvaser/python_can_timing_issue_2.py new file mode 100644 index 00000000..13a65e24 --- /dev/null +++ b/examples/can/python-can/kvaser/python_can_timing_issue_2.py @@ -0,0 +1,32 @@ +"""UDS Issue: https://github.com/mdabrowski1990/uds/issues/228""" + +from time import time + +from can import BufferedReader, Bus, Message, Notifier + +if __name__ == "__main__": + kvaser_interface_1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) + kvaser_interface_2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) # connected with bus 1 + + buffered_reader = BufferedReader() + notifier = Notifier(bus=kvaser_interface_1, listeners=[buffered_reader]) + + message = Message(data=[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], arbitration_id=0x100) + + for _ in range(10): + timestamp_before_send = time() + kvaser_interface_1.send(message) + sent_message = buffered_reader.get_message(timeout=1) + timestamp_after_send = time() + + print(f"-----------------------------------------------\n" + f"Result:\n" + f"Timestamp before send: {timestamp_before_send}\n" + f"Message timestamp: {sent_message.timestamp}\n" + f"Current timestamp: {timestamp_after_send}\n" + f"Message timestamp - Timestamp before send: {sent_message.timestamp - timestamp_before_send} (expected > 0)\n" + f"Current timestamp - Message timestamp: {timestamp_after_send - sent_message.timestamp} (expected > 0)\n" + f"Timestamp before send <= Message timestamp <= Current timestamp: {timestamp_before_send <= sent_message.timestamp <= timestamp_after_send} (expected `True`)") + + kvaser_interface_1.shutdown() + kvaser_interface_2.shutdown() diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index b76c5755..b1ba0aac 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -14,7 +14,7 @@ from uds.transport_interface import PyCanTransportInterface -class TestPythonCanKvaser: # TODO: add checks for all measured times (n_br, n_cs, etc) +class TestPythonCanKvaser: """ System Tests for `PyCanTransportInterface` with Kvaser as bus manager. @@ -132,6 +132,15 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, assert packet_record.target_address == packet.target_address == target_address assert packet_record.source_address == packet.source_address == source_address assert packet_record.address_extension == packet.address_extension == address_extension + # timing parameters + if packet_type == CanPacketType.FLOW_CONTROL: + assert can_transport_interface.n_as_measured is None + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert 0 < can_transport_interface.n_ar_measured + else: + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert 0 < can_transport_interface.n_as_measured + assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < packet_record.transmission_time < datetime_after_send @@ -212,7 +221,11 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, can_transport_interface.receive_packet(timeout=timeout) time_after_receive = time() assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE - sleep((send_after - timeout) * 2 / 1000.) # wait till packet arrives + # timing parameters + assert can_transport_interface.n_as_measured is None + assert can_transport_interface.n_ar_measured is None + # wait till packet arrives + sleep((send_after - timeout) * 2 / 1000.) @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -285,6 +298,9 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, assert packet_record.target_address == addressing_information.rx_packets_physical_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] + # timing parameters + assert can_transport_interface.n_as_measured is None + assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout @@ -360,6 +376,9 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout assert packet_record.target_address == addressing_information.rx_packets_functional_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] + # timing parameters + assert can_transport_interface.n_as_measured is None + assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout @@ -459,10 +478,20 @@ async def test_async_send_packet(self, packet_type, addressing_type, addressing_ assert packet_record.target_address == packet.target_address == target_address assert packet_record.source_address == packet.source_address == source_address assert packet_record.address_extension == packet.address_extension == address_extension + # timing parameters + if packet_type == CanPacketType.FLOW_CONTROL: + assert can_transport_interface.n_as_measured is None + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert 0 < can_transport_interface.n_ar_measured + else: + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert 0 < can_transport_interface.n_as_measured + assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < packet_record.transmission_time < datetime_after_send + # async_receive_packet @pytest.mark.parametrize("addressing_type, addressing_information, frame", [ @@ -523,6 +552,10 @@ async def _send_frame(): await future_record time_after_receive = time() assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE + # timing parameters + assert can_transport_interface.n_as_measured is None + assert can_transport_interface.n_ar_measured is None + # wait till frame arrives await frame_sending_task sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.) @@ -608,6 +641,9 @@ async def _send_frame(): assert packet_record.target_address == addressing_information.rx_packets_physical_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] + # timing parameters + assert can_transport_interface.n_as_measured is None + assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout @@ -695,6 +731,9 @@ async def _send_frame(): assert packet_record.target_address == addressing_information.rx_packets_functional_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] + # timing parameters + assert can_transport_interface.n_as_measured is None + assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout @@ -869,8 +908,8 @@ async def test_async_send_message__sf(self, example_addressing_information, mess # assert message_record.transmission_end < datetime_after_send @pytest.mark.parametrize("message", [ - UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), - UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), ]) @pytest.mark.parametrize("send_after", [5, 970]) @pytest.mark.asyncio @@ -905,7 +944,7 @@ async def _send_frame(): self.can_interface_2.send(flow_control_frame) future_record = can_transport_interface.async_send_message(message) - tasks = [asyncio.create_task(_send_frame()), asyncio.create_task(future_record)] + tasks = [asyncio.create_task(future_record), asyncio.create_task(_send_frame())] datetime_before_send = datetime.now() done_tasks, _ = await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) datetime_after_send = datetime.now() From 1d227a7536b26f64daa4baaf4f9a7aad20488b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Fri, 20 Sep 2024 18:52:01 +0200 Subject: [PATCH 06/20] add software tests - unit tests for _send_packets_block - unit tests for _async_send_packets_block - unit tests for send_message (error handling - unexpected packet and transmission interrupted) --- .../test_can_transport_interface.py | 289 ++++++++++++++++-- .../can_transport_interface.py | 166 +++++----- 2 files changed, 353 insertions(+), 102 deletions(-) diff --git a/tests/software_tests/transport_interface/test_can_transport_interface.py b/tests/software_tests/transport_interface/test_can_transport_interface.py index b6c4d029..6479671b 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -1,7 +1,7 @@ -from random import randint +from random import choice, randint import pytest -from mock import AsyncMock, MagicMock, Mock, patch +from mock import AsyncMock, MagicMock, Mock, call, patch from uds.can import CanAddressingFormat, CanAddressingInformation from uds.transmission_attributes import AddressingType @@ -10,12 +10,15 @@ AbstractCanTransportInterface, AbstractFlowControlParametersGenerator, BusABC, + CanFlowStatus, CanPacket, CanPacketRecord, CanPacketType, DefaultFlowControlParametersGenerator, + Message, PyCanTransportInterface, TransmissionDirection, + TransmissionInterruptionError, UdsMessage, UdsMessageRecord, ) @@ -542,6 +545,10 @@ def setup_method(self): self.mock_wait_for = self._patcher_wait_for.start() self._patcher_time = patch(f"{SCRIPT_LOCATION}.time") self.mock_time = self._patcher_time.start() + self._patcher_sleep = patch(f"{SCRIPT_LOCATION}.sleep") + self.mock_sleep = self._patcher_sleep.start() + self._patcher_asyncio_sleep = patch(f"{SCRIPT_LOCATION}.asyncio.sleep") + self.mock_asyncio_sleep = self._patcher_asyncio_sleep.start() self._patcher_datetime = patch(f"{SCRIPT_LOCATION}.datetime") self.mock_datetime = self._patcher_datetime.start() self._patcher_abstract_can_ti_init = patch(f"{SCRIPT_LOCATION}.AbstractCanTransportInterface.__init__") @@ -556,6 +563,8 @@ def setup_method(self): self.mock_can_dlc_handler = self._patcher_can_dlc_handler.start() self._patcher_can_packet_record = patch(f"{SCRIPT_LOCATION}.CanPacketRecord") self.mock_can_packet_record = self._patcher_can_packet_record.start() + self._patcher_can_packet_type_is_initial_packet_type = patch(f"{SCRIPT_LOCATION}.CanPacketType.is_initial_packet_type") + self.mock_can_packet_type_is_initial_packet_type = self._patcher_can_packet_type_is_initial_packet_type.start() self._patcher_notifier = patch(f"{SCRIPT_LOCATION}.Notifier") self.mock_notifier = self._patcher_notifier.start() self._patcher_message = patch(f"{SCRIPT_LOCATION}.Message") @@ -565,6 +574,7 @@ def teardown_method(self): self._patcher_warn.stop() self._patcher_wait_for.stop() self._patcher_time.stop() + self._patcher_sleep.stop() self._patcher_datetime.stop() self._patcher_abstract_can_ti_init.stop() self._patcher_uds_message.stop() @@ -572,6 +582,7 @@ def teardown_method(self): self._patcher_can_id_handler.stop() self._patcher_can_dlc_handler.stop() self._patcher_can_packet_record.stop() + self._patcher_can_packet_type_is_initial_packet_type.stop() self._patcher_notifier.stop() self._patcher_message.stop() @@ -742,6 +753,189 @@ def test_setup_async_notifier__notifier_exists(self, loop): self.mock_notifier.assert_not_called() self.mock_can_transport_interface._teardown_notifier.assert_called_once_with() + # _send_packets_block + + @pytest.mark.parametrize("packets", [ + (Mock(spec=CanPacket), Mock(spec=CanPacket)), + (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), + ]) + @pytest.mark.parametrize("delay", [0, 1234]) + def test_send_packets_block(self, packets, delay): + mock_qsize = Mock(return_value=0) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock(qsize=mock_qsize)) + packet_records = PyCanTransportInterface._send_packets_block(self=self.mock_can_transport_interface, + packets=packets, delay=delay) + assert isinstance(packet_records, tuple) + assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value + for packet_record in packet_records) + self.mock_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) + assert self.mock_sleep.call_count == len(packets) + self.mock_can_transport_interface.send_packet.assert_has_calls(calls=[call(packet) for packet in packets]) + self.mock_warn.assert_not_called() + + @pytest.mark.parametrize("packets", [ + (Mock(spec=CanPacket), Mock(spec=CanPacket)), + (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), + ]) + @pytest.mark.parametrize("delay", [0, 1234]) + def test_send_packets_block__other_traffic(self, packets, delay): + mock_get_nowait = Mock(return_value=Mock(spec=Message)) + mock_qsize = Mock(side_effect=[3, 2, 1, 0] * len(packets)) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + get_nowait=mock_get_nowait, + qsize=mock_qsize + )) + self.mock_can_transport_interface.segmenter.is_input_packet = Mock(return_value=None) + packet_records = PyCanTransportInterface._send_packets_block(self=self.mock_can_transport_interface, + packets=packets, delay=delay) + assert isinstance(packet_records, tuple) + assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value + for packet_record in packet_records) + self.mock_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) + assert self.mock_sleep.call_count == len(packets) + self.mock_can_transport_interface.send_packet.assert_has_calls(calls=[call(packet) for packet in packets]) + self.mock_warn.assert_not_called() + + @pytest.mark.parametrize("packets", [ + (Mock(spec=CanPacket), Mock(spec=CanPacket)), + (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), + ]) + @pytest.mark.parametrize("delay", [0, 1234]) + def test_send_packets_block__packets_traffic(self, packets, delay): + mock_get_nowait = Mock(return_value=Mock(spec=Message)) + mock_qsize = Mock(side_effect=[1, 0] * len(packets)) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + get_nowait=mock_get_nowait, + qsize=mock_qsize + )) + self.mock_can_packet_type_is_initial_packet_type.return_value = False + self.mock_can_transport_interface.segmenter.is_input_packet = Mock( + return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) + packet_records = PyCanTransportInterface._send_packets_block(self=self.mock_can_transport_interface, + packets=packets, delay=delay) + assert isinstance(packet_records, tuple) + assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value + for packet_record in packet_records) + self.mock_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) + assert self.mock_sleep.call_count == len(packets) + self.mock_can_transport_interface.send_packet.assert_has_calls(calls=[call(packet) for packet in packets]) + self.mock_warn.assert_called() + + @pytest.mark.parametrize("packets", [ + (Mock(spec=CanPacket), Mock(spec=CanPacket)), + (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), + ]) + @pytest.mark.parametrize("delay", [0, 1234]) + def test_send_packets_block__transmission_interruption(self, packets, delay): + mock_get_nowait = Mock(return_value=Mock(spec=Message)) + mock_qsize = Mock(side_effect=[1, 0] * len(packets)) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + get_nowait=mock_get_nowait, + qsize=mock_qsize + )) + self.mock_can_packet_type_is_initial_packet_type.return_value = True + self.mock_can_transport_interface.segmenter.is_input_packet = Mock( + return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) + with pytest.raises(TransmissionInterruptionError): + PyCanTransportInterface._send_packets_block(self=self.mock_can_transport_interface, + packets=packets, delay=delay) + self.mock_warn.assert_not_called() + + # _async_send_packets_block + + @pytest.mark.parametrize("packets", [ + (Mock(spec=CanPacket), Mock(spec=CanPacket)), + (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), + ]) + @pytest.mark.parametrize("delay", [0, 1234]) + @pytest.mark.asyncio + async def test_async_send_packets_block(self, packets, delay): + mock_qsize = Mock(return_value=0) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock(qsize=mock_qsize)) + packet_records = await PyCanTransportInterface._async_send_packets_block(self=self.mock_can_transport_interface, + packets=packets, delay=delay) + assert isinstance(packet_records, tuple) + assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value + for packet_record in packet_records) + self.mock_asyncio_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) + assert self.mock_asyncio_sleep.call_count == len(packets) + self.mock_can_transport_interface.async_send_packet.assert_has_calls(calls=[call(packet, loop=None) + for packet in packets]) + self.mock_warn.assert_not_called() + + @pytest.mark.parametrize("packets", [ + (Mock(spec=CanPacket), Mock(spec=CanPacket)), + (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), + ]) + @pytest.mark.parametrize("delay", [0, 1234]) + @pytest.mark.asyncio + async def test_async_send_packets_block__other_traffic(self, packets, delay): + mock_get_nowait = Mock(return_value=Mock(spec=Message)) + mock_qsize = Mock(side_effect=[3, 2, 1, 0] * len(packets)) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + get_nowait=mock_get_nowait, + qsize=mock_qsize + )) + self.mock_can_transport_interface.segmenter.is_input_packet = Mock(return_value=None) + packet_records = await PyCanTransportInterface._async_send_packets_block(self=self.mock_can_transport_interface, + packets=packets, delay=delay) + assert isinstance(packet_records, tuple) + assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value + for packet_record in packet_records) + self.mock_asyncio_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) + assert self.mock_asyncio_sleep.call_count == len(packets) + self.mock_can_transport_interface.async_send_packet.assert_has_calls(calls=[call(packet, loop=None) + for packet in packets]) + self.mock_warn.assert_not_called() + + @pytest.mark.parametrize("packets", [ + (Mock(spec=CanPacket), Mock(spec=CanPacket)), + (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), + ]) + @pytest.mark.parametrize("delay", [0, 1234]) + @pytest.mark.asyncio + async def test_async_send_packets_block__packets_traffic(self, packets, delay): + mock_get_nowait = Mock(return_value=Mock(spec=Message)) + mock_qsize = Mock(side_effect=[1, 0] * len(packets)) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + get_nowait=mock_get_nowait, + qsize=mock_qsize + )) + self.mock_can_packet_type_is_initial_packet_type.return_value = False + self.mock_can_transport_interface.segmenter.is_input_packet = Mock( + return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) + packet_records = await PyCanTransportInterface._async_send_packets_block(self=self.mock_can_transport_interface, + packets=packets, delay=delay) + assert isinstance(packet_records, tuple) + assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value + for packet_record in packet_records) + self.mock_asyncio_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) + assert self.mock_asyncio_sleep.call_count == len(packets) + self.mock_can_transport_interface.async_send_packet.assert_has_calls(calls=[call(packet, loop=None) + for packet in packets]) + self.mock_warn.assert_called() + + @pytest.mark.parametrize("packets", [ + (Mock(spec=CanPacket), Mock(spec=CanPacket)), + (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), + ]) + @pytest.mark.parametrize("delay", [0, 1234]) + @pytest.mark.asyncio + async def test_async_send_packets_block__transmission_interruption(self, packets, delay): + mock_get_nowait = Mock(return_value=Mock(spec=Message)) + mock_qsize = Mock(side_effect=[1, 0] * len(packets)) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + get_nowait=mock_get_nowait, + qsize=mock_qsize + )) + self.mock_can_packet_type_is_initial_packet_type.return_value = True + self.mock_can_transport_interface.segmenter.is_input_packet = Mock( + return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) + with pytest.raises(TransmissionInterruptionError): + await PyCanTransportInterface._async_send_packets_block(self=self.mock_can_transport_interface, + packets=packets, delay=delay) + self.mock_warn.assert_not_called() + # clear_frames_buffers @pytest.mark.parametrize("sync_queue_size", [0, 1, 7]) @@ -838,14 +1032,10 @@ def test_send_packet(self, packet): transmission_time=self.mock_datetime.fromtimestamp.return_value) if packet.packet_type == CanPacketType.FLOW_CONTROL: assert self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured is None - assert self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured \ - == mock_get_message.return_value.timestamp.__sub__.return_value - mock_get_message.return_value.timestamp.__sub__.assert_called_once_with(self.mock_time.return_value) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured is not None else: - assert self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured \ - == mock_get_message.return_value.timestamp.__sub__.return_value + assert self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured is not None assert self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured is None - mock_get_message.return_value.timestamp.__sub__.assert_called_once_with(self.mock_time.return_value) # receive_packet @@ -950,14 +1140,10 @@ async def test_async_send_packet(self, packet): transmission_time=self.mock_datetime.fromtimestamp.return_value) if packet.packet_type == CanPacketType.FLOW_CONTROL: assert self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured is None - assert self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured \ - == mock_get_message.return_value.timestamp.__sub__.return_value - mock_get_message.return_value.timestamp.__sub__.assert_called_once_with(self.mock_time.return_value) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured is not None else: - assert self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured \ - == mock_get_message.return_value.timestamp.__sub__.return_value + assert self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured is not None assert self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured is None - mock_get_message.return_value.timestamp.__sub__.assert_called_once_with(self.mock_time.return_value) # async_receive_packet @@ -1024,18 +1210,71 @@ def test_send_message__single_frame(self, message): message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.send_packet.assert_called_once_with(mock_segmented_message[0]) - self.mock_uds_message_record.assert_called_once_with((self.mock_can_transport_interface.send_packet.return_value, )) + self.mock_uds_message_record.assert_called_once_with([self.mock_can_transport_interface.send_packet.return_value]) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is None @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), ]) def test_send_message__multiple_packets(self, message): - mock_segmented_message = [Mock(spec=CanPacket) for _ in range(randint(2, 20))] + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) - with pytest.raises(NotImplementedError): + mock_flow_control_record = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.ContinueToSend, + block_size=0) + self.mock_can_transport_interface.receive_packet = Mock(return_value=mock_flow_control_record) + assert PyCanTransportInterface.send_message(self.mock_can_transport_interface, + message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + def test_send_message__multiple_packets__unexpected_packet(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_flow_control_record = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.ContinueToSend, + block_size=0) + mock_interrupting_record = Mock(spec=CanPacketRecord, + packet_type=Mock()) + self.mock_can_transport_interface.receive_packet = Mock(side_effect=[mock_interrupting_record, + mock_flow_control_record]) + self.mock_can_packet_type_is_initial_packet_type.return_value = False + assert PyCanTransportInterface.send_message(self.mock_can_transport_interface, + message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with(mock_interrupting_record.packet_type) + self.mock_warn.assert_called_once() + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + def test_send_message__multiple_packets__transmission_interruption(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_interrupting_record = Mock(spec=CanPacketRecord, + packet_type=Mock()) + self.mock_can_transport_interface.receive_packet = Mock(return_value=mock_interrupting_record) + self.mock_can_packet_type_is_initial_packet_type.return_value = True + with pytest.raises(TransmissionInterruptionError): PyCanTransportInterface.send_message(self.mock_can_transport_interface, message) self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with(mock_interrupting_record.packet_type) + self.mock_warn.assert_not_called() # async_send_message @@ -1051,7 +1290,7 @@ async def test_async_send_message__single_frame(self, message): == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.async_send_packet.assert_called_once_with(mock_segmented_message[0], loop=None) - self.mock_uds_message_record.assert_called_once_with((self.mock_can_transport_interface.async_send_packet.return_value, )) + self.mock_uds_message_record.assert_called_once_with([self.mock_can_transport_interface.async_send_packet.return_value]) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1059,11 +1298,19 @@ async def test_async_send_message__single_frame(self, message): ]) @pytest.mark.asyncio async def test_async_send_message__multiple_packets(self, message): - mock_segmented_message = [Mock(spec=CanPacket) for _ in range(randint(2, 20))] + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) - with pytest.raises(NotImplementedError): - await PyCanTransportInterface.async_send_message(self.mock_can_transport_interface, message) + mock_flow_control_record = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.ContinueToSend, + block_size=0) + self.mock_can_transport_interface.async_receive_packet = AsyncMock(return_value=mock_flow_control_record) + assert await PyCanTransportInterface.async_send_message(self.mock_can_transport_interface, + message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + # TODO: more checks # receive_message diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index f2ac9f04..673dbfe0 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -654,53 +654,17 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore raise TimeoutError("Timeout was reached before observing a CAN Packet being transmitted.") observed_frame = self.__frames_buffer.get_message(timeout=timeout_left) if is_flow_control_packet: - self.__n_ar_measured = observed_frame.timestamp - time_start + # self.__n_ar_measured = observed_frame.timestamp - time_start + self.__n_ar_measured = time() - time_start else: - self.__n_as_measured = observed_frame.timestamp - time_start + # self.__n_as_measured = observed_frame.timestamp - time_start + self.__n_as_measured = time() - time_start return CanPacketRecord(frame=observed_frame, direction=TransmissionDirection.TRANSMITTED, addressing_type=packet.addressing_type, addressing_format=packet.addressing_format, transmission_time=datetime.fromtimestamp(observed_frame.timestamp)) - def receive_packet(self, timeout: Optional[TimeMillisecondsAlias] = None) -> CanPacketRecord: - """ - Receive CAN packet. - - .. warning:: Must not be called within an asynchronous function. - - :param timeout: Maximal time (in milliseconds) to wait. - - :raise TypeError: Provided timeout value is not None neither int nor float type. - :raise ValueError: Provided timeout value is less or equal 0. - :raise TimeoutError: Timeout was reached. - - :return: Record with historic information about received CAN packet. - """ - time_start = time() - if timeout is not None: - if not isinstance(timeout, (int, float)): - raise TypeError("Provided timeout value is not None neither int nor float type.") - if timeout <= 0: - raise ValueError("Provided timeout value is less or equal 0.") - self._setup_notifier() - packet_addressing_type = None - while packet_addressing_type is None: - time_now = time() - timeout_left = self._MAX_LISTENER_TIMEOUT if timeout is None else timeout / 1000. - (time_now - time_start) - if timeout_left <= 0: - raise TimeoutError("Timeout was reached before a CAN Packet was received.") - received_frame = self.__frames_buffer.get_message(timeout=timeout_left) - if received_frame is None: - raise TimeoutError("Timeout was reached before a CAN Packet was received.") - packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, - data=received_frame.data) - return CanPacketRecord(frame=received_frame, - direction=TransmissionDirection.RECEIVED, - addressing_type=packet_addressing_type, - addressing_format=self.segmenter.addressing_format, - transmission_time=datetime.fromtimestamp(received_frame.timestamp)) - async def async_send_packet(self, packet: CanPacket, # type: ignore loop: Optional[AbstractEventLoop] = None) -> CanPacketRecord: @@ -735,15 +699,55 @@ async def async_send_packet(self, timeout_left = timeout / 1000. - (time() - time_start) observed_frame = await wait_for(self.__async_frames_buffer.get_message(), timeout=timeout_left) if is_flow_control_packet: - self.__n_ar_measured = observed_frame.timestamp - time_start + # self.__n_ar_measured = observed_frame.timestamp - time_start + self.__n_ar_measured = time() - time_start else: - self.__n_as_measured = observed_frame.timestamp - time_start + # self.__n_as_measured = observed_frame.timestamp - time_start + self.__n_as_measured = time() - time_start return CanPacketRecord(frame=observed_frame, direction=TransmissionDirection.TRANSMITTED, addressing_type=packet.addressing_type, addressing_format=packet.addressing_format, transmission_time=datetime.fromtimestamp(observed_frame.timestamp)) + def receive_packet(self, timeout: Optional[TimeMillisecondsAlias] = None) -> CanPacketRecord: + """ + Receive CAN packet. + + .. warning:: Must not be called within an asynchronous function. + + :param timeout: Maximal time (in milliseconds) to wait. + + :raise TypeError: Provided timeout value is not None neither int nor float type. + :raise ValueError: Provided timeout value is less or equal 0. + :raise TimeoutError: Timeout was reached. + + :return: Record with historic information about received CAN packet. + """ + time_start = time() + if timeout is not None: + if not isinstance(timeout, (int, float)): + raise TypeError("Provided timeout value is not None neither int nor float type.") + if timeout <= 0: + raise ValueError("Provided timeout value is less or equal 0.") + self._setup_notifier() + packet_addressing_type = None + while packet_addressing_type is None: + time_now = time() + timeout_left = self._MAX_LISTENER_TIMEOUT if timeout is None else timeout / 1000. - (time_now - time_start) + if timeout_left <= 0: + raise TimeoutError("Timeout was reached before a CAN Packet was received.") + received_frame = self.__frames_buffer.get_message(timeout=timeout_left) + if received_frame is None: + raise TimeoutError("Timeout was reached before a CAN Packet was received.") + packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, + data=received_frame.data) + return CanPacketRecord(frame=received_frame, + direction=TransmissionDirection.RECEIVED, + addressing_type=packet_addressing_type, + addressing_format=self.segmenter.addressing_format, + transmission_time=datetime.fromtimestamp(received_frame.timestamp)) + async def async_receive_packet(self, timeout: Optional[TimeMillisecondsAlias] = None, loop: Optional[AbstractEventLoop] = None) -> CanPacketRecord: @@ -804,52 +808,27 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: fc_record = self.receive_packet(timeout=self.N_BS_TIMEOUT) n_bs_measurements.append((time()-time_n_bs_measurement_start)*1000.) packet_records.append(fc_record) - if fc_record.packet_type != fc_record.packet_type.FLOW_CONTROL: + if fc_record.packet_type != CanPacketType.FLOW_CONTROL: if CanPacketType.is_initial_packet_type(fc_record.packet_type): raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") warn(message="CAN message transmission interrupted by an unrelated CAN packet.", category=TransmissionInterruptionWarning) - if fc_record.flow_status == CanFlowStatus.Overflow: - raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") - if fc_record.flow_status == CanFlowStatus.ContinueToSend: + elif fc_record.flow_status == CanFlowStatus.ContinueToSend: number_of_packets = len(packets_to_send) if fc_record.block_size == 0 else fc_record.block_size delay_between_cf = CanSTminTranslator.decode(fc_record.flow_status) if self.n_cs is None else self.n_cs packet_records.extend(self._send_packets_block(packets=packets_to_send[:number_of_packets], delay=delay_between_cf)) time_n_bs_measurement_start = time() packets_to_send = packets_to_send[number_of_packets:] - elif fc_record.flow_status != CanFlowStatus.Wait: - raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") - else: + elif fc_record.flow_status == CanFlowStatus.Wait: time_n_bs_measurement_start = time() + elif fc_record.flow_status == CanFlowStatus.Overflow: + raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") + else: + raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") self.__n_bs_measured = tuple(n_bs_measurements) if n_bs_measurements else None return UdsMessageRecord(packet_records) - def receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> UdsMessageRecord: - """ - Receive UDS message over CAN. - - :param timeout: Maximal time (in milliseconds) to wait for UDS message transmission to start. - This means that receiving might last longer if First Frame was received within provided time. - - :raise TypeError: Provided timeout value is not None neither int nor float type. - :raise ValueError: Provided timeout value is less or equal 0. - :raise TimeoutError: Timeout was reached. - Either Single Frame / First Frame not received within [timeout] ms - or N_As, N_Ar, N_Bs, N_Cr timeout reached. - - :return: Record with historic information about received UDS message. - """ - if timeout is not None: - if not isinstance(timeout, (int, float)): - raise TypeError("Provided timeout value is not None neither int nor float type.") - if timeout <= 0: - raise ValueError("Provided timeout value is less or equal 0.") - received_packet = self.receive_packet(timeout=timeout) - if received_packet.packet_type == CanPacketType.SINGLE_FRAME: - return UdsMessageRecord([received_packet]) - raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/266") - async def async_send_message(self, message: UdsMessage, loop: Optional[AbstractEventLoop] = None) -> UdsMessageRecord: @@ -863,19 +842,17 @@ async def async_send_message(self, """ n_bs_measurements = [] packets_to_send = list(self.segmenter.segmentation(message)) - packet_records = [await self.async_send_packet(packets_to_send.pop(0))] + packet_records = [await self.async_send_packet(packets_to_send.pop(0), loop=loop)] time_n_bs_measurement_start = time() while packets_to_send: fc_record = await self.async_receive_packet(timeout=self.N_BS_TIMEOUT, loop=loop) n_bs_measurements.append((time() - time_n_bs_measurement_start) * 1000.) packet_records.append(fc_record) - if fc_record.packet_type != fc_record.packet_type.FLOW_CONTROL: + if fc_record.packet_type != CanPacketType.FLOW_CONTROL: if CanPacketType.is_initial_packet_type(fc_record.packet_type): raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") warn(message="CAN message transmission interrupted by an unrelated CAN packet.", category=TransmissionInterruptionWarning) - if fc_record.flow_status == CanFlowStatus.Overflow: - raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") if fc_record.flow_status == CanFlowStatus.ContinueToSend: number_of_packets = len(packets_to_send) if fc_record.block_size == 0 else fc_record.block_size delay_between_cf = CanSTminTranslator.decode(fc_record.flow_status) if self.n_cs is None else self.n_cs @@ -884,13 +861,40 @@ async def async_send_message(self, loop=loop)) time_n_bs_measurement_start = time() packets_to_send = packets_to_send[number_of_packets:] - elif fc_record.flow_status != CanFlowStatus.Wait: - raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") - else: + elif fc_record.flow_status == CanFlowStatus.Wait: time_n_bs_measurement_start = time() + elif fc_record.flow_status == CanFlowStatus.Overflow: + raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") + else: + raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") self.__n_bs_measured = tuple(n_bs_measurements) if n_bs_measurements else None return UdsMessageRecord(packet_records) + def receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> UdsMessageRecord: + """ + Receive UDS message over CAN. + + :param timeout: Maximal time (in milliseconds) to wait for UDS message transmission to start. + This means that receiving might last longer if First Frame was received within provided time. + + :raise TypeError: Provided timeout value is not None neither int nor float type. + :raise ValueError: Provided timeout value is less or equal 0. + :raise TimeoutError: Timeout was reached. + Either Single Frame / First Frame not received within [timeout] ms + or N_As, N_Ar, N_Bs, N_Cr timeout reached. + + :return: Record with historic information about received UDS message. + """ + if timeout is not None: + if not isinstance(timeout, (int, float)): + raise TypeError("Provided timeout value is not None neither int nor float type.") + if timeout <= 0: + raise ValueError("Provided timeout value is less or equal 0.") + received_packet = self.receive_packet(timeout=timeout) + if received_packet.packet_type == CanPacketType.SINGLE_FRAME: + return UdsMessageRecord([received_packet]) + raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/266") + async def async_receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None, loop: Optional[AbstractEventLoop] = None) -> UdsMessageRecord: From f9a79a996c723ee27c970ee6217507f5205a1da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sat, 21 Sep 2024 12:27:48 +0200 Subject: [PATCH 07/20] unit tests Provide all unit tests for sending message feature. Fix a few defects in code. --- .../test_can_transport_interface.py | 362 +++++++++++++++++- .../can_transport_interface.py | 101 ++--- 2 files changed, 398 insertions(+), 65 deletions(-) diff --git a/tests/software_tests/transport_interface/test_can_transport_interface.py b/tests/software_tests/transport_interface/test_can_transport_interface.py index 6479671b..6ac6354b 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -561,6 +561,8 @@ def setup_method(self): self.mock_can_id_handler = self._patcher_can_id_handler.start() self._patcher_can_dlc_handler = patch(f"{SCRIPT_LOCATION}.CanDlcHandler") self.mock_can_dlc_handler = self._patcher_can_dlc_handler.start() + self._patcher_can_st_min_handler = patch(f"{SCRIPT_LOCATION}.CanSTminTranslator") + self.mock_can_st_min_handler = self._patcher_can_st_min_handler.start() self._patcher_can_packet_record = patch(f"{SCRIPT_LOCATION}.CanPacketRecord") self.mock_can_packet_record = self._patcher_can_packet_record.start() self._patcher_can_packet_type_is_initial_packet_type = patch(f"{SCRIPT_LOCATION}.CanPacketType.is_initial_packet_type") @@ -581,6 +583,7 @@ def teardown_method(self): self._patcher_uds_message_record.stop() self._patcher_can_id_handler.stop() self._patcher_can_dlc_handler.stop() + self._patcher_can_st_min_handler.stop() self._patcher_can_packet_record.stop() self._patcher_can_packet_type_is_initial_packet_type.stop() self._patcher_notifier.stop() @@ -1206,8 +1209,8 @@ async def test_async_receive_packet(self, timeout): def test_send_message__single_frame(self, message): mock_segmented_message = [Mock(spec=CanPacket)] self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) - assert PyCanTransportInterface.send_message(self.mock_can_transport_interface, - message) == self.mock_uds_message_record.return_value + assert PyCanTransportInterface.send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.send_packet.assert_called_once_with(mock_segmented_message[0]) self.mock_uds_message_record.assert_called_once_with([self.mock_can_transport_interface.send_packet.return_value]) @@ -1217,19 +1220,108 @@ def test_send_message__single_frame(self, message): Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), ]) - def test_send_message__multiple_packets(self, message): + @pytest.mark.parametrize("st_min", [0x00, 0xFF]) + def test_send_message__multiple_packets__st_min__block_size_0(self, message, st_min): mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) for _ in range(randint(1, 20))]) self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + self.mock_can_transport_interface.n_cs = None mock_flow_control_record = Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, flow_status=CanFlowStatus.ContinueToSend, - block_size=0) + block_size=0, + st_min=st_min) + self.mock_can_transport_interface.receive_packet = Mock(return_value=mock_flow_control_record) + mock_sent_packet_record = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface._send_packets_block.return_value = [mock_sent_packet_record] + assert PyCanTransportInterface.send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.receive_packet.assert_called_once_with( + timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) + self.mock_can_transport_interface._send_packets_block.assert_called_once_with( + packets=mock_segmented_message[1:], + delay=self.mock_can_st_min_handler.decode.return_value) + self.mock_can_st_min_handler.decode.assert_called_once_with(st_min) + self.mock_uds_message_record.assert_called_once_with([ + self.mock_can_transport_interface.send_packet.return_value, + mock_flow_control_record, + mock_sent_packet_record + ]) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("n_cs, st_min", [ + (0, 0xFF), + (5, 0x00), + ]) + def test_send_message__multiple_packets__n_cs__block_size_1(self, message, n_cs, st_min): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message[:]) + self.mock_can_transport_interface.n_cs = n_cs + mock_flow_control_record = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.ContinueToSend, + block_size=1, + st_min=st_min) self.mock_can_transport_interface.receive_packet = Mock(return_value=mock_flow_control_record) - assert PyCanTransportInterface.send_message(self.mock_can_transport_interface, - message) == self.mock_uds_message_record.return_value + mock_sent_packet_record = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface._send_packets_block.return_value = [mock_sent_packet_record] + assert PyCanTransportInterface.send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.receive_packet.assert_has_calls([ + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) for _ in mock_segmented_message[1:] + ]) + self.mock_can_transport_interface._send_packets_block.assert_has_calls([ + call(packets=[packet], delay=n_cs) for packet in mock_segmented_message[1:] + ]) + self.mock_can_st_min_handler.decode.assert_not_called() + self.mock_uds_message_record.assert_called_once_with([ + self.mock_can_transport_interface.send_packet.return_value, + *([mock_flow_control_record, mock_sent_packet_record]*len(mock_segmented_message[1:])) + ]) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + def test_send_message__multiple_packets__wait(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_flow_control_record_wait = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.Wait) + mock_flow_control_record_continue = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.ContinueToSend, + block_size=0) + self.mock_can_transport_interface.receive_packet = Mock( + side_effect=[mock_flow_control_record_wait, mock_flow_control_record_continue]) + mock_sent_packet_record = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface._send_packets_block.return_value = [mock_sent_packet_record] + assert PyCanTransportInterface.send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.receive_packet.assert_has_calls([ + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT), + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) + ]) + self.mock_uds_message_record.assert_called_once_with([ + self.mock_can_transport_interface.send_packet.return_value, + mock_flow_control_record_wait, + mock_flow_control_record_continue, + mock_sent_packet_record + ]) assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None @pytest.mark.parametrize("message", [ @@ -1247,15 +1339,24 @@ def test_send_message__multiple_packets__unexpected_packet(self, message): block_size=0) mock_interrupting_record = Mock(spec=CanPacketRecord, packet_type=Mock()) - self.mock_can_transport_interface.receive_packet = Mock(side_effect=[mock_interrupting_record, - mock_flow_control_record]) + self.mock_can_transport_interface.receive_packet = Mock( + side_effect=[mock_interrupting_record, mock_flow_control_record]) self.mock_can_packet_type_is_initial_packet_type.return_value = False - assert PyCanTransportInterface.send_message(self.mock_can_transport_interface, - message) == self.mock_uds_message_record.return_value + mock_sent_packet_record = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface._send_packets_block.return_value = [mock_sent_packet_record] + assert PyCanTransportInterface.send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.receive_packet.assert_has_calls([ + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT), + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) + ]) + self.mock_uds_message_record.assert_called_once_with([ + self.mock_can_transport_interface.send_packet.return_value, + mock_flow_control_record, + mock_sent_packet_record + ]) assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None - self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with(mock_interrupting_record.packet_type) - self.mock_warn.assert_called_once() @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1276,6 +1377,44 @@ def test_send_message__multiple_packets__transmission_interruption(self, message self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with(mock_interrupting_record.packet_type) self.mock_warn.assert_not_called() + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + def test_send_message__multiple_packets__overflow(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_flow_control_record_overflow = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.Overflow) + self.mock_can_transport_interface.receive_packet = Mock(return_value=mock_flow_control_record_overflow) + with pytest.raises(OverflowError): + PyCanTransportInterface.send_message(self.mock_can_transport_interface, message) + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.receive_packet.assert_called_once_with( + timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + def test_send_message__multiple_packets__unknown_flow_status(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_flow_control_record_overflow = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=Mock()) + self.mock_can_transport_interface.receive_packet = Mock(return_value=mock_flow_control_record_overflow) + with pytest.raises(NotImplementedError): + PyCanTransportInterface.send_message(self.mock_can_transport_interface, message) + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.receive_packet.assert_called_once_with( + timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) + # async_send_message @pytest.mark.parametrize("message", [ @@ -1296,21 +1435,210 @@ async def test_async_send_message__single_frame(self, message): Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), ]) + @pytest.mark.parametrize("st_min", [0x00, 0xFF]) @pytest.mark.asyncio - async def test_async_send_message__multiple_packets(self, message): + async def test_async_send_message__multiple_packets__st_min__block_size_0(self, message, st_min): mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) for _ in range(randint(1, 20))]) self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + self.mock_can_transport_interface.n_cs = None mock_flow_control_record = Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, flow_status=CanFlowStatus.ContinueToSend, - block_size=0) + block_size=0, + st_min=st_min) self.mock_can_transport_interface.async_receive_packet = AsyncMock(return_value=mock_flow_control_record) - assert await PyCanTransportInterface.async_send_message(self.mock_can_transport_interface, - message) == self.mock_uds_message_record.return_value + mock_sent_packet_record = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface._async_send_packets_block.return_value = [mock_sent_packet_record] + assert await PyCanTransportInterface.async_send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.async_receive_packet.assert_called_once_with( + timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) + self.mock_can_transport_interface._async_send_packets_block.assert_called_once_with( + packets=mock_segmented_message[1:], + delay=self.mock_can_st_min_handler.decode.return_value, + loop=None) + self.mock_can_st_min_handler.decode.assert_called_once_with(st_min) + self.mock_uds_message_record.assert_called_once_with([ + self.mock_can_transport_interface.async_send_packet.return_value, + mock_flow_control_record, + mock_sent_packet_record + ]) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("n_cs, st_min", [ + (0, 0xFF), + (5, 0x00), + ]) + @pytest.mark.asyncio + async def test_async_send_message__multiple_packets__n_cs__block_size_1(self, message, n_cs, st_min): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message[:]) + self.mock_can_transport_interface.n_cs = n_cs + mock_flow_control_record = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.ContinueToSend, + block_size=1, + st_min=st_min) + self.mock_can_transport_interface.async_receive_packet = AsyncMock(return_value=mock_flow_control_record) + mock_sent_packet_record = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface._async_send_packets_block.return_value = [mock_sent_packet_record] + assert await PyCanTransportInterface.async_send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.async_receive_packet.assert_has_calls([ + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) for _ in mock_segmented_message[1:] + ]) + self.mock_can_transport_interface._async_send_packets_block.assert_has_calls([ + call(packets=[packet], delay=n_cs, loop=None) for packet in mock_segmented_message[1:] + ]) + self.mock_can_st_min_handler.decode.assert_not_called() + self.mock_uds_message_record.assert_called_once_with([ + self.mock_can_transport_interface.async_send_packet.return_value, + *([mock_flow_control_record, mock_sent_packet_record]*len(mock_segmented_message[1:])) + ]) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.asyncio + async def test_async_send_message__multiple_packets__wait(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_flow_control_record_wait = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.Wait) + mock_flow_control_record_continue = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.ContinueToSend, + block_size=0) + self.mock_can_transport_interface.async_receive_packet = AsyncMock( + side_effect=[mock_flow_control_record_wait, mock_flow_control_record_continue]) + mock_sent_packet_record = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface._async_send_packets_block.return_value = [mock_sent_packet_record] + assert await PyCanTransportInterface.async_send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.async_receive_packet.assert_has_calls([ + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None), + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) + ]) + self.mock_uds_message_record.assert_called_once_with([ + self.mock_can_transport_interface.async_send_packet.return_value, + mock_flow_control_record_wait, + mock_flow_control_record_continue, + mock_sent_packet_record + ]) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.asyncio + async def test_async_send_message__multiple_packets__unexpected_packet(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_flow_control_record = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.ContinueToSend, + block_size=0) + mock_interrupting_record = Mock(spec=CanPacketRecord, + packet_type=Mock()) + self.mock_can_transport_interface.async_receive_packet = AsyncMock( + side_effect=[mock_interrupting_record, mock_flow_control_record]) + self.mock_can_packet_type_is_initial_packet_type.return_value = False + mock_sent_packet_record = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface._async_send_packets_block.return_value = [mock_sent_packet_record] + assert await PyCanTransportInterface.async_send_message( + self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.async_receive_packet.assert_has_calls([ + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None), + call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) + ]) + self.mock_uds_message_record.assert_called_once_with([ + self.mock_can_transport_interface.async_send_packet.return_value, + mock_flow_control_record, + mock_sent_packet_record + ]) + assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.asyncio + async def test_async_send_message__multiple_packets__transmission_interruption(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_interrupting_record = Mock(spec=CanPacketRecord, + packet_type=Mock()) + self.mock_can_transport_interface.async_receive_packet = AsyncMock(return_value=mock_interrupting_record) + self.mock_can_packet_type_is_initial_packet_type.return_value = True + with pytest.raises(TransmissionInterruptionError): + await PyCanTransportInterface.async_send_message(self.mock_can_transport_interface, message) + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with(mock_interrupting_record.packet_type) + self.mock_warn.assert_not_called() + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.asyncio + async def test_async_send_message__multiple_packets__overflow(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_flow_control_record_overflow = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=CanFlowStatus.Overflow) + self.mock_can_transport_interface.async_receive_packet = AsyncMock( + return_value=mock_flow_control_record_overflow) + with pytest.raises(OverflowError): + await PyCanTransportInterface.async_send_message(self.mock_can_transport_interface, message) + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.async_receive_packet.assert_called_once_with( + timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.asyncio + async def test_async_send_message__multiple_packets__unknown_flow_status(self, message): + mock_segmented_message = [Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME)] + mock_segmented_message.extend([Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME) + for _ in range(randint(1, 20))]) + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + mock_flow_control_record_overflow = Mock(spec=CanPacketRecord, + packet_type=CanPacketType.FLOW_CONTROL, + flow_status=Mock()) + self.mock_can_transport_interface.async_receive_packet = AsyncMock( + return_value=mock_flow_control_record_overflow) + with pytest.raises(NotImplementedError): + await PyCanTransportInterface.async_send_message(self.mock_can_transport_interface, message) self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) - # TODO: more checks + self.mock_can_transport_interface.async_receive_packet.assert_called_once_with( + timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) # receive_message diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 673dbfe0..7635f7d3 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -196,7 +196,7 @@ def n_bs_timeout(self, value: TimeMillisecondsAlias): @property # noqa @abstractmethod - def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: + def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias, ...]]: """ Get the last measured values (during the last message transmission) of N_Bs time parameter. @@ -310,7 +310,7 @@ def n_cr_timeout(self, value: TimeMillisecondsAlias): @property # noqa @abstractmethod - def n_cr_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: + def n_cr_measured(self) -> Optional[Tuple[TimeMillisecondsAlias, ...]]: """ Get the last measured values (during the last message reception) of N_Cr time parameter. @@ -431,8 +431,8 @@ def __init__(self, """ self.__n_as_measured: Optional[TimeMillisecondsAlias] = None self.__n_ar_measured: Optional[TimeMillisecondsAlias] = None - self.__n_bs_measured: Optional[Tuple[TimeMillisecondsAlias]] = None - self.__n_cr_measured: Optional[Tuple[TimeMillisecondsAlias]] = None + self.__n_bs_measured: Optional[Tuple[TimeMillisecondsAlias, ...]] = None + self.__n_cr_measured: Optional[Tuple[TimeMillisecondsAlias, ...]] = None super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, **kwargs) @@ -464,8 +464,8 @@ def n_ar_measured(self) -> Optional[TimeMillisecondsAlias]: """ return self.__n_ar_measured - @property - def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: + @property # noqa + def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias, ...]]: """ Get the last measured value of N_Bs time parameter. @@ -473,8 +473,8 @@ def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: """ return self.__n_bs_measured - @property - def n_cr_measured(self) -> Optional[Tuple[TimeMillisecondsAlias]]: + @property # noqa + def n_cr_measured(self) -> Optional[Tuple[TimeMillisecondsAlias, ...]]: """ Get the last measured value of N_Cr time parameter. @@ -561,7 +561,7 @@ def _send_packets_block(self, transmission_time=datetime.fromtimestamp( received_frame.timestamp)) if CanPacketType.is_initial_packet_type(received_packet.packet_type): - raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") + raise TransmissionInterruptionError("UDS message transmission interrupted by a new message.") warn(message="CAN message transmission interrupted by an unrelated CAN packet.", category=TransmissionInterruptionWarning) packet_records.append(self.send_packet(packet)) @@ -594,7 +594,7 @@ async def _async_send_packets_block(self, packets: List[CanPacket], transmission_time=datetime.fromtimestamp( received_frame.timestamp)) if CanPacketType.is_initial_packet_type(received_packet.packet_type): - raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") + raise TransmissionInterruptionError("UDS message transmission interrupted by a new message.") warn(message="CAN message transmission interrupted by an unrelated CAN packet.", category=TransmissionInterruptionWarning) packet_records.append(await self.async_send_packet(packet, loop=loop)) @@ -805,27 +805,29 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: packet_records = [self.send_packet(packets_to_send.pop(0))] time_n_bs_measurement_start = time() while packets_to_send: - fc_record = self.receive_packet(timeout=self.N_BS_TIMEOUT) - n_bs_measurements.append((time()-time_n_bs_measurement_start)*1000.) - packet_records.append(fc_record) - if fc_record.packet_type != CanPacketType.FLOW_CONTROL: - if CanPacketType.is_initial_packet_type(fc_record.packet_type): - raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") + record = self.receive_packet(timeout=self.N_BS_TIMEOUT) + n_bs_measurements.append((time() - time_n_bs_measurement_start) * 1000.) + if record.packet_type == CanPacketType.FLOW_CONTROL: + packet_records.append(record) + if record.flow_status == CanFlowStatus.ContinueToSend: + number_of_packets = len(packets_to_send) if record.block_size == 0 else record.block_size + delay_between_cf = self.n_cs if self.n_cs is not None else \ + CanSTminTranslator.decode(record.st_min) # type: ignore + packet_records.extend(self._send_packets_block(packets=packets_to_send[:number_of_packets], + delay=delay_between_cf)) + time_n_bs_measurement_start = time() + packets_to_send = packets_to_send[number_of_packets:] + elif record.flow_status == CanFlowStatus.Wait: + time_n_bs_measurement_start = time() + elif record.flow_status == CanFlowStatus.Overflow: + raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") + else: + raise NotImplementedError(f"Unknown Flow Status received: {record.flow_status}") + elif CanPacketType.is_initial_packet_type(record.packet_type): + raise TransmissionInterruptionError("UDS message transmission interrupted by a new message.") + else: warn(message="CAN message transmission interrupted by an unrelated CAN packet.", category=TransmissionInterruptionWarning) - elif fc_record.flow_status == CanFlowStatus.ContinueToSend: - number_of_packets = len(packets_to_send) if fc_record.block_size == 0 else fc_record.block_size - delay_between_cf = CanSTminTranslator.decode(fc_record.flow_status) if self.n_cs is None else self.n_cs - packet_records.extend(self._send_packets_block(packets=packets_to_send[:number_of_packets], - delay=delay_between_cf)) - time_n_bs_measurement_start = time() - packets_to_send = packets_to_send[number_of_packets:] - elif fc_record.flow_status == CanFlowStatus.Wait: - time_n_bs_measurement_start = time() - elif fc_record.flow_status == CanFlowStatus.Overflow: - raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") - else: - raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") self.__n_bs_measured = tuple(n_bs_measurements) if n_bs_measurements else None return UdsMessageRecord(packet_records) @@ -845,28 +847,31 @@ async def async_send_message(self, packet_records = [await self.async_send_packet(packets_to_send.pop(0), loop=loop)] time_n_bs_measurement_start = time() while packets_to_send: - fc_record = await self.async_receive_packet(timeout=self.N_BS_TIMEOUT, loop=loop) + record = await self.async_receive_packet(timeout=self.N_BS_TIMEOUT, loop=loop) n_bs_measurements.append((time() - time_n_bs_measurement_start) * 1000.) - packet_records.append(fc_record) - if fc_record.packet_type != CanPacketType.FLOW_CONTROL: - if CanPacketType.is_initial_packet_type(fc_record.packet_type): - raise TransmissionInterruptionError(f"UDS message transmission interrupted by a new message.") + if record.packet_type == CanPacketType.FLOW_CONTROL: + packet_records.append(record) + if record.flow_status == CanFlowStatus.ContinueToSend: + number_of_packets = len(packets_to_send) if record.block_size == 0 else record.block_size + delay_between_cf = self.n_cs if self.n_cs is not None else \ + CanSTminTranslator.decode(record.st_min) # type: ignore + packet_records.extend( + await self._async_send_packets_block(packets=packets_to_send[:number_of_packets], + delay=delay_between_cf, + loop=loop)) + time_n_bs_measurement_start = time() + packets_to_send = packets_to_send[number_of_packets:] + elif record.flow_status == CanFlowStatus.Wait: + time_n_bs_measurement_start = time() + elif record.flow_status == CanFlowStatus.Overflow: + raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") + else: + raise NotImplementedError(f"Unknown Flow Status received: {record.flow_status}") + elif CanPacketType.is_initial_packet_type(record.packet_type): + raise TransmissionInterruptionError("UDS message transmission interrupted by a new message.") + else: warn(message="CAN message transmission interrupted by an unrelated CAN packet.", category=TransmissionInterruptionWarning) - if fc_record.flow_status == CanFlowStatus.ContinueToSend: - number_of_packets = len(packets_to_send) if fc_record.block_size == 0 else fc_record.block_size - delay_between_cf = CanSTminTranslator.decode(fc_record.flow_status) if self.n_cs is None else self.n_cs - packet_records.extend(await self._async_send_packets_block(packets=packets_to_send[:number_of_packets], - delay=delay_between_cf, - loop=loop)) - time_n_bs_measurement_start = time() - packets_to_send = packets_to_send[number_of_packets:] - elif fc_record.flow_status == CanFlowStatus.Wait: - time_n_bs_measurement_start = time() - elif fc_record.flow_status == CanFlowStatus.Overflow: - raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") - else: - raise NotImplementedError(f"Unknown Flow Status received: {fc_record.flow_status}") self.__n_bs_measured = tuple(n_bs_measurements) if n_bs_measurements else None return UdsMessageRecord(packet_records) From 4c222a920cb03f63f0d6bd5fe7818dfd130c0829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sat, 21 Sep 2024 12:33:54 +0200 Subject: [PATCH 08/20] Update .codecov.yml Add target to intergration tests branch coverage as 80% is used. --- .codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codecov.yml b/.codecov.yml index 5fbba5e1..abc20908 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -23,6 +23,7 @@ coverage: flags: - integration-tests integration-tests-branch: + target: 50% flags: - integration-tests-branch performance-tests: From f8e958d74148bb7749bda327f0a0daa3974dc8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sat, 21 Sep 2024 12:42:08 +0200 Subject: [PATCH 09/20] Update .codecov.yml add flag so comments from bot contain coverage changes --- .codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codecov.yml b/.codecov.yml index abc20908..45167db6 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -34,3 +34,4 @@ comment: layout: "reach, diff, flags, files" behavior: default require_changes: false + show_carryforward_flags: true From ca676fa35d46af27a8213f90e0ea47db492a5e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sat, 21 Sep 2024 12:42:44 +0200 Subject: [PATCH 10/20] Update .codecov.yml --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 45167db6..b8ee47eb 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -3,7 +3,7 @@ codecov: uploads: false coverage: - range: 80...100 + range: 50...100 round: down precision: 2 status: From a0dfd69a64b09eea0e886d0504ad403f2915fe16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sat, 21 Sep 2024 12:42:57 +0200 Subject: [PATCH 11/20] Update .codecov.yml test --- .codecov.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index b8ee47eb..f68ebabd 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -23,7 +23,6 @@ coverage: flags: - integration-tests integration-tests-branch: - target: 50% flags: - integration-tests-branch performance-tests: From 7a0e7779390e5a421531e71404b087c16a493358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sat, 21 Sep 2024 12:49:22 +0200 Subject: [PATCH 12/20] Update .codecov.yml must be set :( --- .codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codecov.yml b/.codecov.yml index f68ebabd..b8ee47eb 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -23,6 +23,7 @@ coverage: flags: - integration-tests integration-tests-branch: + target: 50% flags: - integration-tests-branch performance-tests: From 25036cd5e92c7ca34bfd84ad2f6ee5a472d5c410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sun, 22 Sep 2024 15:35:15 +0200 Subject: [PATCH 13/20] Fix remarks found during review - update docstrings - move and improve n_bs_measured and n_cr_measured to AbstractCanTransportInterface - add more references to knowledge base - improve async systemn tests (task handling) - add missing assertions to unit tests --- .../test_can_transport_interface.py | 246 +++++++++++++----- .../test_python_can.py | 143 +++++----- uds/packet/can_packet.py | 2 +- .../can_transport_interface.py | 237 ++++++++++------- uds/utilities/custom_warnings.py | 2 +- 5 files changed, 381 insertions(+), 249 deletions(-) diff --git a/tests/software_tests/transport_interface/test_can_transport_interface.py b/tests/software_tests/transport_interface/test_can_transport_interface.py index 6ac6354b..6aafbe60 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -21,6 +21,7 @@ TransmissionInterruptionError, UdsMessage, UdsMessageRecord, + datetime, ) SCRIPT_LOCATION = "uds.transport_interface.can_transport_interface" @@ -79,6 +80,8 @@ def test_init__valid_mandatory_args(self, mock_isinstance, == addressing_information assert self.mock_can_transport_interface._AbstractCanTransportInterface__segmenter \ == self.mock_can_segmenter_class.return_value + assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_bs_measured is None + assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_cr_measured is None assert self.mock_can_transport_interface.n_as_timeout == self.mock_can_transport_interface.N_AS_TIMEOUT assert self.mock_can_transport_interface.n_ar_timeout == self.mock_can_transport_interface.N_AR_TIMEOUT assert self.mock_can_transport_interface.n_bs_timeout == self.mock_can_transport_interface.N_BS_TIMEOUT @@ -128,6 +131,8 @@ def test_init__valid_all_args(self, mock_isinstance, == addressing_information assert self.mock_can_transport_interface._AbstractCanTransportInterface__segmenter \ == self.mock_can_segmenter_class.return_value + assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_bs_measured is None + assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_cr_measured is None assert self.mock_can_transport_interface.n_as_timeout == n_as_timeout assert self.mock_can_transport_interface.n_ar_timeout == n_ar_timeout assert self.mock_can_transport_interface.n_bs_timeout == n_bs_timeout @@ -136,6 +141,86 @@ def test_init__valid_all_args(self, mock_isinstance, assert self.mock_can_transport_interface.n_cr_timeout == n_cr_timeout assert self.mock_can_transport_interface.flow_control_parameters_generator == flow_control_parameters_generator + # _update_n_bs_measured + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessageRecord, packets_records=(Mock(spec=CanPacketRecord), )) + ]) + def test_update_n_bs_measured__1_record(self, message): + assert AbstractCanTransportInterface._update_n_bs_measured(self.mock_can_transport_interface, message=message) is None + assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_bs_measured is None + + @pytest.mark.parametrize("message, expected_n_bs_measurements", [ + (Mock(spec=UdsMessageRecord, packets_records=( + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FIRST_FRAME, + transmission_time=datetime(year=1234, month=1, day=2)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, + transmission_time=datetime(year=1234, month=1, day=2, microsecond=1000)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME, + transmission_time=datetime(year=1234, month=1, day=2, microsecond=3000)))), + (1, )), + (Mock(spec=UdsMessageRecord, packets_records=( + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FIRST_FRAME, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=12, microsecond=987654)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=154)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=57000)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=58954)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=58955)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=868955)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=14, microsecond=955)), + )), + (12.5, 56.846, 0.001)), + ]) + def test_update_n_bs_measured__multiple_records(self, message, expected_n_bs_measurements): + assert AbstractCanTransportInterface._update_n_bs_measured(self.mock_can_transport_interface, message=message) is None + assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_bs_measured == expected_n_bs_measurements + + # _update_n_cr_measured + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessageRecord, packets_records=(Mock(spec=CanPacketRecord), )) + ]) + def test_update_n_cr_measured__1_record(self, message): + assert AbstractCanTransportInterface._update_n_cr_measured(self.mock_can_transport_interface, message=message) is None + assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_cr_measured is None + + @pytest.mark.parametrize("message, expected_n_cr_measurements", [ + (Mock(spec=UdsMessageRecord, packets_records=( + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FIRST_FRAME, + transmission_time=datetime(year=1234, month=1, day=2)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, + transmission_time=datetime(year=1234, month=1, day=2, microsecond=1000)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME, + transmission_time=datetime(year=1234, month=1, day=2, microsecond=3000)))), + (2, )), + (Mock(spec=UdsMessageRecord, packets_records=( + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FIRST_FRAME, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=12, microsecond=987654)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=154)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=57000)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=58954)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.FLOW_CONTROL, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=58955)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=13, microsecond=868955)), + Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME, + transmission_time=datetime(year=2024, month=9, day=22, hour=14, minute=43, second=14, microsecond=955)), + )), + (1.954, 810.0, 132)), + ]) + def test_update_n_cr_measured__multiple_records(self, message, expected_n_cr_measurements): + assert AbstractCanTransportInterface._update_n_cr_measured(self.mock_can_transport_interface, message=message) is None + assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_cr_measured == expected_n_cr_measurements + # segmenter @pytest.mark.parametrize("value", ["something", Mock()]) @@ -297,6 +382,11 @@ def test_n_bs_timeout__set__valid_without_warn(self, mock_isinstance): self.mock_warn.assert_not_called() assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_bs_timeout == mock_value + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_n_bs_measured(self, value): + self.mock_can_transport_interface._AbstractCanTransportInterface__n_bs_measured = value + assert AbstractCanTransportInterface.n_bs_measured.fget(self.mock_can_transport_interface) == value + # n_br @pytest.mark.parametrize("value", ["something", Mock()]) @@ -472,6 +562,11 @@ def test_n_cr_timeout__set__valid_without_warn(self, mock_isinstance): self.mock_warn.assert_not_called() assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_cr_timeout == mock_value + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_n_cr_measured(self, value): + self.mock_can_transport_interface._AbstractCanTransportInterface__n_cr_measured = value + assert AbstractCanTransportInterface.n_cr_measured.fget(self.mock_can_transport_interface) == value + # addressing_information @pytest.mark.parametrize("value", ["something", Mock()]) @@ -604,8 +699,6 @@ def test_init__default_args(self, can_bus_manager, addressing_information): addressing_information=addressing_information) assert self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured is None assert self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured is None - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is None - assert self.mock_can_transport_interface._PyCanTransportInterface__n_cr_measured is None @pytest.mark.parametrize("can_bus_manager, addressing_information", [ ("can_bus_manager", "addressing_information"), @@ -625,8 +718,6 @@ def test_init__all_args(self, can_bus_manager, addressing_information, kwargs): **kwargs) assert self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured is None assert self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured is None - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is None - assert self.mock_can_transport_interface._PyCanTransportInterface__n_cr_measured is None # __del__ @@ -649,20 +740,6 @@ def test_n_ar_measured(self, value): self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured = value assert PyCanTransportInterface.n_ar_measured.fget(self.mock_can_transport_interface) == value - # n_bs_measured - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_n_bs_measured(self, value): - self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured = value - assert PyCanTransportInterface.n_bs_measured.fget(self.mock_can_transport_interface) == value - - # n_cr_measured - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_n_cr_measured(self, value): - self.mock_can_transport_interface._PyCanTransportInterface__n_cr_measured = value - assert PyCanTransportInterface.n_cr_measured.fget(self.mock_can_transport_interface) == value - # _teardown_notifier def test_teardown_notifier__no_notifier(self): @@ -756,18 +833,18 @@ def test_setup_async_notifier__notifier_exists(self, loop): self.mock_notifier.assert_not_called() self.mock_can_transport_interface._teardown_notifier.assert_called_once_with() - # _send_packets_block + # _send_cf_packets_block @pytest.mark.parametrize("packets", [ (Mock(spec=CanPacket), Mock(spec=CanPacket)), (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), ]) @pytest.mark.parametrize("delay", [0, 1234]) - def test_send_packets_block(self, packets, delay): + def test_send_cf_packets_block(self, packets, delay): mock_qsize = Mock(return_value=0) self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock(qsize=mock_qsize)) - packet_records = PyCanTransportInterface._send_packets_block(self=self.mock_can_transport_interface, - packets=packets, delay=delay) + packet_records = PyCanTransportInterface._send_cf_packets_block(self=self.mock_can_transport_interface, + cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value for packet_record in packet_records) @@ -781,7 +858,7 @@ def test_send_packets_block(self, packets, delay): (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), ]) @pytest.mark.parametrize("delay", [0, 1234]) - def test_send_packets_block__other_traffic(self, packets, delay): + def test_send_cf_packets_block__other_traffic(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[3, 2, 1, 0] * len(packets)) self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( @@ -789,8 +866,8 @@ def test_send_packets_block__other_traffic(self, packets, delay): qsize=mock_qsize )) self.mock_can_transport_interface.segmenter.is_input_packet = Mock(return_value=None) - packet_records = PyCanTransportInterface._send_packets_block(self=self.mock_can_transport_interface, - packets=packets, delay=delay) + packet_records = PyCanTransportInterface._send_cf_packets_block(self=self.mock_can_transport_interface, + cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value for packet_record in packet_records) @@ -804,7 +881,7 @@ def test_send_packets_block__other_traffic(self, packets, delay): (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), ]) @pytest.mark.parametrize("delay", [0, 1234]) - def test_send_packets_block__packets_traffic(self, packets, delay): + def test_send_cf_packets_block__packets_traffic(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[1, 0] * len(packets)) self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( @@ -814,8 +891,8 @@ def test_send_packets_block__packets_traffic(self, packets, delay): self.mock_can_packet_type_is_initial_packet_type.return_value = False self.mock_can_transport_interface.segmenter.is_input_packet = Mock( return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) - packet_records = PyCanTransportInterface._send_packets_block(self=self.mock_can_transport_interface, - packets=packets, delay=delay) + packet_records = PyCanTransportInterface._send_cf_packets_block(self=self.mock_can_transport_interface, + cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value for packet_record in packet_records) @@ -829,7 +906,7 @@ def test_send_packets_block__packets_traffic(self, packets, delay): (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), ]) @pytest.mark.parametrize("delay", [0, 1234]) - def test_send_packets_block__transmission_interruption(self, packets, delay): + def test_send_cf_packets_block__transmission_interruption(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[1, 0] * len(packets)) self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( @@ -840,11 +917,11 @@ def test_send_packets_block__transmission_interruption(self, packets, delay): self.mock_can_transport_interface.segmenter.is_input_packet = Mock( return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) with pytest.raises(TransmissionInterruptionError): - PyCanTransportInterface._send_packets_block(self=self.mock_can_transport_interface, - packets=packets, delay=delay) + PyCanTransportInterface._send_cf_packets_block(self=self.mock_can_transport_interface, + cf_packets_block=packets, delay=delay) self.mock_warn.assert_not_called() - # _async_send_packets_block + # _async_send_cf_packets_block @pytest.mark.parametrize("packets", [ (Mock(spec=CanPacket), Mock(spec=CanPacket)), @@ -852,11 +929,11 @@ def test_send_packets_block__transmission_interruption(self, packets, delay): ]) @pytest.mark.parametrize("delay", [0, 1234]) @pytest.mark.asyncio - async def test_async_send_packets_block(self, packets, delay): + async def test_async_send_cf_packets_block(self, packets, delay): mock_qsize = Mock(return_value=0) self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock(qsize=mock_qsize)) - packet_records = await PyCanTransportInterface._async_send_packets_block(self=self.mock_can_transport_interface, - packets=packets, delay=delay) + packet_records = await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, + cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value for packet_record in packet_records) @@ -872,7 +949,7 @@ async def test_async_send_packets_block(self, packets, delay): ]) @pytest.mark.parametrize("delay", [0, 1234]) @pytest.mark.asyncio - async def test_async_send_packets_block__other_traffic(self, packets, delay): + async def test_async_send_cf_packets_block__other_traffic(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[3, 2, 1, 0] * len(packets)) self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( @@ -880,8 +957,8 @@ async def test_async_send_packets_block__other_traffic(self, packets, delay): qsize=mock_qsize )) self.mock_can_transport_interface.segmenter.is_input_packet = Mock(return_value=None) - packet_records = await PyCanTransportInterface._async_send_packets_block(self=self.mock_can_transport_interface, - packets=packets, delay=delay) + packet_records = await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, + cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value for packet_record in packet_records) @@ -897,7 +974,7 @@ async def test_async_send_packets_block__other_traffic(self, packets, delay): ]) @pytest.mark.parametrize("delay", [0, 1234]) @pytest.mark.asyncio - async def test_async_send_packets_block__packets_traffic(self, packets, delay): + async def test_async_send_cf_packets_block__packets_traffic(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[1, 0] * len(packets)) self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( @@ -907,8 +984,8 @@ async def test_async_send_packets_block__packets_traffic(self, packets, delay): self.mock_can_packet_type_is_initial_packet_type.return_value = False self.mock_can_transport_interface.segmenter.is_input_packet = Mock( return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) - packet_records = await PyCanTransportInterface._async_send_packets_block(self=self.mock_can_transport_interface, - packets=packets, delay=delay) + packet_records = await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, + cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value for packet_record in packet_records) @@ -924,7 +1001,7 @@ async def test_async_send_packets_block__packets_traffic(self, packets, delay): ]) @pytest.mark.parametrize("delay", [0, 1234]) @pytest.mark.asyncio - async def test_async_send_packets_block__transmission_interruption(self, packets, delay): + async def test_async_send_cf_packets_block__transmission_interruption(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[1, 0] * len(packets)) self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( @@ -935,8 +1012,8 @@ async def test_async_send_packets_block__transmission_interruption(self, packets self.mock_can_transport_interface.segmenter.is_input_packet = Mock( return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) with pytest.raises(TransmissionInterruptionError): - await PyCanTransportInterface._async_send_packets_block(self=self.mock_can_transport_interface, - packets=packets, delay=delay) + await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, + cf_packets_block=packets, delay=delay) self.mock_warn.assert_not_called() # clear_frames_buffers @@ -1214,7 +1291,9 @@ def test_send_message__single_frame(self, message): self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.send_packet.assert_called_once_with(mock_segmented_message[0]) self.mock_uds_message_record.assert_called_once_with([self.mock_can_transport_interface.send_packet.return_value]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is None + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1234,14 +1313,14 @@ def test_send_message__multiple_packets__st_min__block_size_0(self, message, st_ st_min=st_min) self.mock_can_transport_interface.receive_packet = Mock(return_value=mock_flow_control_record) mock_sent_packet_record = Mock(spec=CanPacketRecord) - self.mock_can_transport_interface._send_packets_block.return_value = [mock_sent_packet_record] + self.mock_can_transport_interface._send_cf_packets_block.return_value = [mock_sent_packet_record] assert PyCanTransportInterface.send_message( self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.receive_packet.assert_called_once_with( timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) - self.mock_can_transport_interface._send_packets_block.assert_called_once_with( - packets=mock_segmented_message[1:], + self.mock_can_transport_interface._send_cf_packets_block.assert_called_once_with( + cf_packets_block=mock_segmented_message[1:], delay=self.mock_can_st_min_handler.decode.return_value) self.mock_can_st_min_handler.decode.assert_called_once_with(st_min) self.mock_uds_message_record.assert_called_once_with([ @@ -1249,7 +1328,9 @@ def test_send_message__multiple_packets__st_min__block_size_0(self, message, st_ mock_flow_control_record, mock_sent_packet_record ]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1272,22 +1353,24 @@ def test_send_message__multiple_packets__n_cs__block_size_1(self, message, n_cs, st_min=st_min) self.mock_can_transport_interface.receive_packet = Mock(return_value=mock_flow_control_record) mock_sent_packet_record = Mock(spec=CanPacketRecord) - self.mock_can_transport_interface._send_packets_block.return_value = [mock_sent_packet_record] + self.mock_can_transport_interface._send_cf_packets_block.return_value = [mock_sent_packet_record] assert PyCanTransportInterface.send_message( self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.receive_packet.assert_has_calls([ call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) for _ in mock_segmented_message[1:] ]) - self.mock_can_transport_interface._send_packets_block.assert_has_calls([ - call(packets=[packet], delay=n_cs) for packet in mock_segmented_message[1:] + self.mock_can_transport_interface._send_cf_packets_block.assert_has_calls([ + call(cf_packets_block=[packet], delay=n_cs) for packet in mock_segmented_message[1:] ]) self.mock_can_st_min_handler.decode.assert_not_called() self.mock_uds_message_record.assert_called_once_with([ self.mock_can_transport_interface.send_packet.return_value, *([mock_flow_control_record, mock_sent_packet_record]*len(mock_segmented_message[1:])) ]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1308,7 +1391,7 @@ def test_send_message__multiple_packets__wait(self, message): self.mock_can_transport_interface.receive_packet = Mock( side_effect=[mock_flow_control_record_wait, mock_flow_control_record_continue]) mock_sent_packet_record = Mock(spec=CanPacketRecord) - self.mock_can_transport_interface._send_packets_block.return_value = [mock_sent_packet_record] + self.mock_can_transport_interface._send_cf_packets_block.return_value = [mock_sent_packet_record] assert PyCanTransportInterface.send_message( self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) @@ -1322,7 +1405,9 @@ def test_send_message__multiple_packets__wait(self, message): mock_flow_control_record_continue, mock_sent_packet_record ]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1343,7 +1428,7 @@ def test_send_message__multiple_packets__unexpected_packet(self, message): side_effect=[mock_interrupting_record, mock_flow_control_record]) self.mock_can_packet_type_is_initial_packet_type.return_value = False mock_sent_packet_record = Mock(spec=CanPacketRecord) - self.mock_can_transport_interface._send_packets_block.return_value = [mock_sent_packet_record] + self.mock_can_transport_interface._send_cf_packets_block.return_value = [mock_sent_packet_record] assert PyCanTransportInterface.send_message( self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) @@ -1356,7 +1441,9 @@ def test_send_message__multiple_packets__unexpected_packet(self, message): mock_flow_control_record, mock_sent_packet_record ]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_warn.assert_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1376,6 +1463,7 @@ def test_send_message__multiple_packets__transmission_interruption(self, message self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with(mock_interrupting_record.packet_type) self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_not_called() @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1395,6 +1483,8 @@ def test_send_message__multiple_packets__overflow(self, message): self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.receive_packet.assert_called_once_with( timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_not_called() @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1414,6 +1504,8 @@ def test_send_message__multiple_packets__unknown_flow_status(self, message): self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.receive_packet.assert_called_once_with( timeout=self.mock_can_transport_interface.N_BS_TIMEOUT) + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_not_called() # async_send_message @@ -1430,6 +1522,9 @@ async def test_async_send_message__single_frame(self, message): self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.async_send_packet.assert_called_once_with(mock_segmented_message[0], loop=None) self.mock_uds_message_record.assert_called_once_with([self.mock_can_transport_interface.async_send_packet.return_value]) + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1450,14 +1545,14 @@ async def test_async_send_message__multiple_packets__st_min__block_size_0(self, st_min=st_min) self.mock_can_transport_interface.async_receive_packet = AsyncMock(return_value=mock_flow_control_record) mock_sent_packet_record = Mock(spec=CanPacketRecord) - self.mock_can_transport_interface._async_send_packets_block.return_value = [mock_sent_packet_record] + self.mock_can_transport_interface._async_send_cf_packets_block.return_value = [mock_sent_packet_record] assert await PyCanTransportInterface.async_send_message( self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.async_receive_packet.assert_called_once_with( timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) - self.mock_can_transport_interface._async_send_packets_block.assert_called_once_with( - packets=mock_segmented_message[1:], + self.mock_can_transport_interface._async_send_cf_packets_block.assert_called_once_with( + cf_packets_block=mock_segmented_message[1:], delay=self.mock_can_st_min_handler.decode.return_value, loop=None) self.mock_can_st_min_handler.decode.assert_called_once_with(st_min) @@ -1466,7 +1561,9 @@ async def test_async_send_message__multiple_packets__st_min__block_size_0(self, mock_flow_control_record, mock_sent_packet_record ]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1490,22 +1587,24 @@ async def test_async_send_message__multiple_packets__n_cs__block_size_1(self, me st_min=st_min) self.mock_can_transport_interface.async_receive_packet = AsyncMock(return_value=mock_flow_control_record) mock_sent_packet_record = Mock(spec=CanPacketRecord) - self.mock_can_transport_interface._async_send_packets_block.return_value = [mock_sent_packet_record] + self.mock_can_transport_interface._async_send_cf_packets_block.return_value = [mock_sent_packet_record] assert await PyCanTransportInterface.async_send_message( self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.async_receive_packet.assert_has_calls([ call(timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) for _ in mock_segmented_message[1:] ]) - self.mock_can_transport_interface._async_send_packets_block.assert_has_calls([ - call(packets=[packet], delay=n_cs, loop=None) for packet in mock_segmented_message[1:] + self.mock_can_transport_interface._async_send_cf_packets_block.assert_has_calls([ + call(cf_packets_block=[packet], delay=n_cs, loop=None) for packet in mock_segmented_message[1:] ]) self.mock_can_st_min_handler.decode.assert_not_called() self.mock_uds_message_record.assert_called_once_with([ self.mock_can_transport_interface.async_send_packet.return_value, *([mock_flow_control_record, mock_sent_packet_record]*len(mock_segmented_message[1:])) ]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1527,7 +1626,7 @@ async def test_async_send_message__multiple_packets__wait(self, message): self.mock_can_transport_interface.async_receive_packet = AsyncMock( side_effect=[mock_flow_control_record_wait, mock_flow_control_record_continue]) mock_sent_packet_record = Mock(spec=CanPacketRecord) - self.mock_can_transport_interface._async_send_packets_block.return_value = [mock_sent_packet_record] + self.mock_can_transport_interface._async_send_cf_packets_block.return_value = [mock_sent_packet_record] assert await PyCanTransportInterface.async_send_message( self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) @@ -1541,7 +1640,9 @@ async def test_async_send_message__multiple_packets__wait(self, message): mock_flow_control_record_continue, mock_sent_packet_record ]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1563,7 +1664,7 @@ async def test_async_send_message__multiple_packets__unexpected_packet(self, mes side_effect=[mock_interrupting_record, mock_flow_control_record]) self.mock_can_packet_type_is_initial_packet_type.return_value = False mock_sent_packet_record = Mock(spec=CanPacketRecord) - self.mock_can_transport_interface._async_send_packets_block.return_value = [mock_sent_packet_record] + self.mock_can_transport_interface._async_send_cf_packets_block.return_value = [mock_sent_packet_record] assert await PyCanTransportInterface.async_send_message( self.mock_can_transport_interface, message) == self.mock_uds_message_record.return_value self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) @@ -1576,7 +1677,9 @@ async def test_async_send_message__multiple_packets__unexpected_packet(self, mes mock_flow_control_record, mock_sent_packet_record ]) - assert self.mock_can_transport_interface._PyCanTransportInterface__n_bs_measured is not None + self.mock_warn.assert_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_called_once_with( + self.mock_uds_message_record.return_value) @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1597,6 +1700,7 @@ async def test_async_send_message__multiple_packets__transmission_interruption(s self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with(mock_interrupting_record.packet_type) self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_not_called() @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1618,6 +1722,8 @@ async def test_async_send_message__multiple_packets__overflow(self, message): self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.async_receive_packet.assert_called_once_with( timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_not_called() @pytest.mark.parametrize("message", [ Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), @@ -1639,6 +1745,8 @@ async def test_async_send_message__multiple_packets__unknown_flow_status(self, m self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) self.mock_can_transport_interface.async_receive_packet.assert_called_once_with( timeout=self.mock_can_transport_interface.N_BS_TIMEOUT, loop=None) + self.mock_warn.assert_not_called() + self.mock_can_transport_interface._update_n_bs_measured.assert_not_called() # receive_message diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index b1ba0aac..7cbbbcd8 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -24,7 +24,7 @@ class TestPythonCanKvaser: """ TASK_TIMING_TOLERANCE = 30. # ms - DELAY_AFTER_RECEIVING_FRAME = 1. # ms + DELAY_AFTER_RECEIVING_FRAME = 10. # ms DELAY_BETWEEN_CONSECUTIVE_FRAMES = 50. # ms def setup_class(self): @@ -298,9 +298,6 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, assert packet_record.target_address == addressing_information.rx_packets_physical_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] - # timing parameters - assert can_transport_interface.n_as_measured is None - assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout @@ -376,9 +373,6 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout assert packet_record.target_address == addressing_information.rx_packets_functional_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] - # timing parameters - assert can_transport_interface.n_as_measured is None - assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout @@ -552,9 +546,6 @@ async def _send_frame(): await future_record time_after_receive = time() assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE - # timing parameters - assert can_transport_interface.n_as_measured is None - assert can_transport_interface.n_ar_measured is None # wait till frame arrives await frame_sending_task sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.) @@ -623,15 +614,12 @@ async def _send_frame(): # and `addressing_information` can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) - future_record = can_transport_interface.async_receive_packet(timeout=timeout) - tasks = [asyncio.create_task(_send_frame()), asyncio.create_task(future_record)] + + send_frame_task = asyncio.create_task(_send_frame()) datetime_before_receive = datetime.now() - done_tasks, _ = await asyncio.wait(tasks) + packet_record = await can_transport_interface.async_receive_packet(timeout=timeout) datetime_after_receive = datetime.now() - received_records = tuple(filter(lambda result: isinstance(result, CanPacketRecord), - (done_task.result() for done_task in done_tasks))) - assert len(received_records) == 1, "CAN packet was received" - packet_record = received_records[0] + await send_frame_task assert isinstance(packet_record, CanPacketRecord) assert packet_record.direction == TransmissionDirection.RECEIVED assert packet_record.raw_frame_data == tuple(frame.data) @@ -641,9 +629,6 @@ async def _send_frame(): assert packet_record.target_address == addressing_information.rx_packets_physical_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] - # timing parameters - assert can_transport_interface.n_as_measured is None - assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout @@ -713,15 +698,11 @@ async def _send_frame(): # and `addressing_information` can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) - future_record = can_transport_interface.async_receive_packet(timeout=timeout) - tasks = [asyncio.create_task(_send_frame()), asyncio.create_task(future_record)] + send_frame_task = asyncio.create_task(_send_frame()) datetime_before_receive = datetime.now() - done_tasks, _ = await asyncio.wait(tasks) + packet_record = await can_transport_interface.async_receive_packet(timeout=timeout) datetime_after_receive = datetime.now() - received_records = tuple(filter(lambda result: isinstance(result, CanPacketRecord), - (done_task.result() for done_task in done_tasks))) - assert len(received_records) == 1, "CAN packet was received" - packet_record = received_records[0] + await send_frame_task assert isinstance(packet_record, CanPacketRecord) assert packet_record.direction == TransmissionDirection.RECEIVED assert packet_record.raw_frame_data == tuple(frame.data) @@ -731,9 +712,6 @@ async def _send_frame(): assert packet_record.target_address == addressing_information.rx_packets_functional_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] - # timing parameters - assert can_transport_interface.n_as_measured is None - assert can_transport_interface.n_ar_measured is None # performance checks # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout @@ -781,7 +759,7 @@ def test_send_message__sf(self, example_addressing_information, message): UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), ]) - @pytest.mark.parametrize("send_after", [5, 970]) + @pytest.mark.parametrize("send_after", [5, 950]) def test_send_message__multi_packets(self, example_addressing_information, example_addressing_information_2nd_node, message, send_after): @@ -829,7 +807,9 @@ def test_send_message__multi_packets(self, example_addressing_information, # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send - # assert send_after - 5 <= can_transport_interface.n_bs_measured[0] <= send_after + 5 + # assert (send_after - self.TASK_TIMING_TOLERANCE + # <= can_transport_interface.n_bs_measured[0] + # <= send_after + self.TASK_TIMING_TOLERANCE) @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), @@ -911,11 +891,11 @@ async def test_async_send_message__sf(self, example_addressing_information, mess UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), ]) - @pytest.mark.parametrize("send_after", [5, 970]) + @pytest.mark.parametrize("send_after", [5, 950]) @pytest.mark.asyncio async def test_async_send_message__multi_packets(self, example_addressing_information, - example_addressing_information_2nd_node, - message, send_after): + example_addressing_information_2nd_node, + message, send_after): """ Check for an asynchronous multi packet (FF + CF) UDS message sending. @@ -943,15 +923,11 @@ async def _send_frame(): await asyncio.sleep(send_after / 1000.) self.can_interface_2.send(flow_control_frame) - future_record = can_transport_interface.async_send_message(message) - tasks = [asyncio.create_task(future_record), asyncio.create_task(_send_frame())] + send_frame_task = asyncio.create_task(_send_frame()) datetime_before_send = datetime.now() - done_tasks, _ = await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) + message_record = await can_transport_interface.async_send_message(message) datetime_after_send = datetime.now() - sent_records = tuple(filter(lambda result: isinstance(result, UdsMessageRecord), - (done_task.result() for done_task in done_tasks))) - assert len(sent_records) == 1, "UDS message was sent" - message_record = sent_records[0] + await send_frame_task assert isinstance(message_record, UdsMessageRecord) assert message_record.direction == TransmissionDirection.TRANSMITTED assert message_record.payload == message.payload @@ -969,7 +945,9 @@ async def _send_frame(): # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < message_record.transmission_start # assert message_record.transmission_end < datetime_after_send - # assert send_after - 5 <= can_transport_interface.n_bs_measured[0] <= send_after + 5 + # assert (send_after - self.TASK_TIMING_TOLERANCE + # <= can_transport_interface.n_bs_measured[0] + # <= send_after + self.TASK_TIMING_TOLERANCE) @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), @@ -1003,16 +981,15 @@ async def _send_frame(): await asyncio.sleep((can_transport_interface.n_bs_timeout + 1) / 1000.) self.can_interface_2.send(flow_control_frame) - future_record = can_transport_interface.async_send_message(message) - frame_sending_task = asyncio.create_task(_send_frame()) + send_frame_task = asyncio.create_task(_send_frame()) time_before_receive = time() with pytest.raises(TimeoutError): - await future_record + await can_transport_interface.async_send_message(message) time_after_receive = time() assert (can_transport_interface.n_bs_timeout < (time_after_receive - time_before_receive) * 1000. < can_transport_interface.n_bs_timeout + self.TASK_TIMING_TOLERANCE) - await frame_sending_task + await send_frame_task sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.) # receive_message @@ -1142,14 +1119,13 @@ async def _send_frame(): await asyncio.sleep(send_after / 1000.) self.can_interface_2.send(frame) - future_record = can_transport_interface.async_receive_message(timeout=timeout) - frame_sending_task = asyncio.create_task(_send_frame()) + send_frame_task = asyncio.create_task(_send_frame()) time_before_receive = time() with pytest.raises(TimeoutError): - await future_record + await can_transport_interface.async_receive_message(timeout=timeout) time_after_receive = time() + await send_frame_task assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE - await frame_sending_task sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.) @pytest.mark.parametrize("message", [ @@ -1192,15 +1168,11 @@ async def _send_frame(): await asyncio.sleep(send_after / 1000.) self.can_interface_2.send(frame) - future_record = can_transport_interface.async_receive_message(timeout=timeout) - tasks = [asyncio.create_task(_send_frame()), asyncio.create_task(future_record)] + send_frame_task = asyncio.create_task(_send_frame()) datetime_before_receive = datetime.now() - done_tasks, _ = await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) + message_record = await can_transport_interface.async_receive_message(timeout=timeout) datetime_after_receive = datetime.now() - received_records = tuple(filter(lambda result: isinstance(result, UdsMessageRecord), - (done_task.result() for done_task in done_tasks))) - assert len(received_records) == 1, "UDS message was received" - message_record = received_records[0] + await send_frame_task assert message_record.direction == TransmissionDirection.RECEIVED assert message_record.payload == message.payload assert message_record.addressing_type == message.addressing_type @@ -1289,17 +1261,17 @@ async def test_async_send_packet_on_one_receive_on_other_bus(self, example_addre addressing_information=example_addressing_information_2nd_node) uds_message = UdsMessage(payload=payload, addressing_type=addressing_type) packet = can_transport_interface_2.segmenter.segmentation(uds_message)[0] - tasks = [asyncio.create_task(can_transport_interface_2.async_send_packet(packet)), - asyncio.create_task(can_transport_interface_1.async_receive_packet(timeout=100))] - done_tasks, _ = await asyncio.wait(tasks) - packet_record_1, packet_record_2 = [done_task.result() for done_task in done_tasks] - assert isinstance(packet_record_1, CanPacketRecord) and isinstance(packet_record_2, CanPacketRecord) - assert {packet_record_1.direction, packet_record_2.direction} \ - == {TransmissionDirection.TRANSMITTED, TransmissionDirection.RECEIVED} - assert packet_record_1.addressing_format == packet_record_2.addressing_format - assert packet_record_1.can_id == packet_record_2.can_id - assert packet_record_1.raw_frame_data == packet_record_2.raw_frame_data - assert packet_record_1.addressing_type == packet_record_2.addressing_type + receive_packet_task = asyncio.create_task(can_transport_interface_1.async_receive_packet(timeout=100)) + sent_packet_record = await can_transport_interface_2.async_send_packet(packet) + received_packet_record = await receive_packet_task + assert isinstance(sent_packet_record, CanPacketRecord) + assert isinstance(received_packet_record, CanPacketRecord) + assert sent_packet_record.direction == TransmissionDirection.TRANSMITTED + assert received_packet_record.direction == TransmissionDirection.RECEIVED + assert sent_packet_record.addressing_format == received_packet_record.addressing_format + assert sent_packet_record.can_id == received_packet_record.can_id + assert sent_packet_record.raw_frame_data == received_packet_record.raw_frame_data + assert sent_packet_record.addressing_type == received_packet_record.addressing_type @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), @@ -1369,15 +1341,16 @@ async def test_async_send_message_on_one_receive_on_other_bus(self, example_addr addressing_information=example_addressing_information) can_transport_interface_2 = PyCanTransportInterface(can_bus_manager=self.can_interface_2, addressing_information=example_addressing_information_2nd_node) - tasks = [asyncio.create_task(can_transport_interface_2.async_send_message(message)), - asyncio.create_task(can_transport_interface_1.async_receive_message(timeout=100))] - done_tasks, _ = await asyncio.wait(tasks) - message_record_1, message_record_2 = [done_task.result() for done_task in done_tasks] - assert isinstance(message_record_1, UdsMessageRecord) and isinstance(message_record_2, UdsMessageRecord) - assert {message_record_1.direction, message_record_2.direction} \ - == {TransmissionDirection.TRANSMITTED, TransmissionDirection.RECEIVED} - assert message_record_1.addressing_type == message_record_2.addressing_type == message.addressing_type - assert message_record_1.payload == message_record_2.payload == message.payload + + receive_message_task = asyncio.create_task(can_transport_interface_1.async_receive_message(timeout=100)) + sent_message_record = await can_transport_interface_2.async_send_message(message) + received_message_record = await receive_message_task + assert isinstance(sent_message_record, UdsMessageRecord) + assert isinstance(received_message_record, UdsMessageRecord) + assert sent_message_record.direction == TransmissionDirection.TRANSMITTED + assert received_message_record.direction == TransmissionDirection.RECEIVED + assert sent_message_record.addressing_type == received_message_record.addressing_type == message.addressing_type + assert sent_message_record.payload == received_message_record.payload == message.payload # TODO: after https://github.com/mdabrowski1990/uds/issues/266: Flow Control CONTINUE TO SEND with changing block size and STmin (including max value) @@ -1584,7 +1557,7 @@ async def test_async_observe_tx_packet(self, example_addressing_information, exa UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), ]) - @pytest.mark.parametrize("send_after", [5, 970]) + @pytest.mark.parametrize("send_after", [5, 950]) def test_overflow_during_message_sending(self, example_addressing_information, example_addressing_information_2nd_node, message, send_after): @@ -1614,7 +1587,7 @@ def test_overflow_during_message_sending(self, example_addressing_information, UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), ]) - @pytest.mark.parametrize("send_after", [5, 970]) + @pytest.mark.parametrize("send_after", [5, 950]) @pytest.mark.asyncio async def test_overflow_during_async_message_sending(self, example_addressing_information, example_addressing_information_2nd_node, @@ -1635,13 +1608,15 @@ async def test_overflow_during_async_message_sending(self, example_addressing_in can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) - flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.ContinueToSend, - block_size=0, - st_min=0) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.Overflow) flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) async def _send_frame(): await asyncio.sleep(send_after / 1000.) self.can_interface_2.send(flow_control_frame) - # TODO: schedule tasks and catch exception + future_record = can_transport_interface.async_send_message(message) + frame_sending_task = asyncio.create_task(_send_frame()) + with pytest.raises(OverflowError): + await future_record + await frame_sending_task diff --git a/uds/packet/can_packet.py b/uds/packet/can_packet.py index f5e3d26c..1ef74666 100644 --- a/uds/packet/can_packet.py +++ b/uds/packet/can_packet.py @@ -85,7 +85,7 @@ def __init__(self, *, Flow status information carried by this Flow Control frame. - :parameter block_size: (required for: FC with ContinueToSend Flow Status) Block size information carried by this Flow Control frame. - - :parameter **: (required for: FC with ContinueToSend Flow Status) + - :parameter st_min: (required for: FC with ContinueToSend Flow Status) Separation Time minimum information carried by this Flow Control frame. """ # initialize the variables diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 7635f7d3..cc9d488e 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -80,6 +80,7 @@ def __init__(self, - :parameter use_data_optimization: Information whether to use CAN Frame Data Optimization. - :parameter filler_byte: Filler byte value to use for CAN Frame Data Padding. - :parameter flow_control_parameters_generator: Generator with Flow Control parameters to use. + # TODO: Add N_WFTmax :raise TypeError: Provided Addressing Information value has unexpected type. """ @@ -87,6 +88,8 @@ def __init__(self, raise TypeError("Unsupported type of Addressing Information was provided.") self.__addressing_information: AbstractCanAddressingInformation = addressing_information super().__init__(bus_manager=can_bus_manager) + self.__n_bs_measured: Optional[Tuple[TimeMillisecondsAlias, ...]] = None + self.__n_cr_measured: Optional[Tuple[TimeMillisecondsAlias, ...]] = None self.n_as_timeout = kwargs.pop("n_as_timeout", self.N_AS_TIMEOUT) self.n_ar_timeout = kwargs.pop("n_ar_timeout", self.N_AR_TIMEOUT) self.n_bs_timeout = kwargs.pop("n_bs_timeout", self.N_BS_TIMEOUT) @@ -97,6 +100,40 @@ def __init__(self, self.DEFAULT_FLOW_CONTROL_PARAMETERS) self.__segmenter = CanSegmenter(addressing_information=addressing_information, **kwargs) + # Common + + def _update_n_bs_measured(self, message: UdsMessageRecord) -> None: + """ + Update measured values of N_Bs according to timestamps of CAN packet records. + + :param message: Record of UDS message transmitted over CAN. + """ + if len(message.packets_records) == 1: + self.__n_bs_measured = None + else: + n_bs_measured = [] + for i, packet_record in enumerate(message.packets_records[1:]): + if packet_record.packet_type == CanPacketType.FLOW_CONTROL: + n_bs = packet_record.transmission_time - message.packets_records[i].transmission_time + n_bs_measured.append(round(n_bs.total_seconds()*1000, 3)) + self.__n_bs_measured = tuple(n_bs_measured) + + def _update_n_cr_measured(self, message: UdsMessageRecord) -> None: + """ + Update measured values of N_Cr according to timestamps of CAN packet records. + + :param message: Record of UDS message transmitted over CAN. + """ + if len(message.packets_records) == 1: + self.__n_cr_measured = None + else: + n_cr_measured = [] + for i, packet_record in enumerate(message.packets_records[1:]): + if packet_record.packet_type == CanPacketType.CONSECUTIVE_FRAME: + n_cr = packet_record.transmission_time - message.packets_records[i].transmission_time + n_cr_measured.append(round(n_cr.total_seconds()*1000, 3)) + self.__n_cr_measured = tuple(n_cr_measured) + @property def segmenter(self) -> CanSegmenter: """Value of the segmenter used by this CAN Transport Interface.""" @@ -106,13 +143,13 @@ def segmenter(self) -> CanSegmenter: @property def n_as_timeout(self) -> TimeMillisecondsAlias: - """Timeout value for N_As time parameter.""" + """Timeout value for :ref:`N_As ` time parameter.""" return self.__n_as_timeout @n_as_timeout.setter def n_as_timeout(self, value: TimeMillisecondsAlias): """ - Set timeout value for N_As time parameter. + Set timeout value for :ref:`N_As ` time parameter. :param value: Value of timeout to set. @@ -132,20 +169,20 @@ def n_as_timeout(self, value: TimeMillisecondsAlias): @abstractmethod def n_as_measured(self) -> Optional[TimeMillisecondsAlias]: """ - Get the last measured value of N_As time parameter. + Get the last measured value of :ref:`N_As ` time parameter. :return: Time in milliseconds or None if the value was never measured. """ @property def n_ar_timeout(self) -> TimeMillisecondsAlias: - """Timeout value for N_Ar time parameter.""" + """Timeout value for :ref:`N_Ar ` time parameter.""" return self.__n_ar_timeout @n_ar_timeout.setter def n_ar_timeout(self, value: TimeMillisecondsAlias): """ - Set timeout value for N_Ar time parameter. + Set timeout value for :ref:`N_Ar ` time parameter. :param value: Value of timeout to set. @@ -165,20 +202,20 @@ def n_ar_timeout(self, value: TimeMillisecondsAlias): @abstractmethod def n_ar_measured(self) -> Optional[TimeMillisecondsAlias]: """ - Get the last measured value of N_Ar time parameter. + Get the last measured value of :ref:`N_Ar ` time parameter. :return: Time in milliseconds or None if the value was never measured. """ @property def n_bs_timeout(self) -> TimeMillisecondsAlias: - """Timeout value for N_Bs time parameter.""" + """Timeout value for :ref:`N_Bs ` time parameter.""" return self.__n_bs_timeout @n_bs_timeout.setter def n_bs_timeout(self, value: TimeMillisecondsAlias): """ - Set timeout value for N_Bs time parameter. + Set timeout value for :ref:`N_Bs ` time parameter. :param value: Value of timeout to set. @@ -195,18 +232,23 @@ def n_bs_timeout(self, value: TimeMillisecondsAlias): self.__n_bs_timeout = value @property # noqa - @abstractmethod def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias, ...]]: """ - Get the last measured values (during the last message transmission) of N_Bs time parameter. + Get the last measured values of :ref:`N_Bs ` time parameter. + + .. note:: The last measurement comes from the last transmission of UDS message using either + :meth:`~uds.transport_interface.can_transport_interface.AbstractCanTransportInterface.send_message` + :meth:`~uds.transport_interface.can_transport_interface.AbstractCanTransportInterface.async_send_message` + method. - :return: Tuple with times in milliseconds or None if the values were never measured. + :return: Tuple with times in milliseconds or None if the values could not be measured. """ + return self.__n_bs_measured @property def n_br(self) -> TimeMillisecondsAlias: """ - Get the value of N_Br time parameter which is currently set. + Get the value of :ref:`N_Br ` time parameter which is currently set. .. note:: The actual (observed on the bus) value will be slightly longer as it also includes computation and CAN Interface delays. @@ -216,7 +258,7 @@ def n_br(self) -> TimeMillisecondsAlias: @n_br.setter def n_br(self, value: TimeMillisecondsAlias): """ - Set the value of N_Br time parameter to use. + Set the value of :ref:`N_Br ` time parameter to use. :param value: The value to set. @@ -232,12 +274,12 @@ def n_br(self, value: TimeMillisecondsAlias): @property def n_br_max(self) -> TimeMillisecondsAlias: """ - Get the maximum valid value of N_Br time parameter. + Get the maximum valid value of :ref:`N_Br ` time parameter. .. warning:: To assess maximal value of :ref:`N_Br `, the actual value of :ref:`N_Ar ` time parameter is required. - Either the latest measured value of N_Ar would be used, or 0ms would be assumed (if there are - no measurement result). + Either the latest measured value of :ref:`N_Ar ` would be used, + or 0ms would be assumed (if there are no measurement result). """ n_ar_measured = 0 if self.n_ar_measured is None else self.n_ar_measured return 0.9 * self.n_bs_timeout - n_ar_measured @@ -245,7 +287,7 @@ def n_br_max(self) -> TimeMillisecondsAlias: @property def n_cs(self) -> Optional[TimeMillisecondsAlias]: """ - Get the value of N_Cs time parameter which is currently set. + Get the value of :ref:`N_Cs ` time parameter which is currently set. .. note:: The actual (observed on the bus) value will be slightly longer as it also includes computation and CAN Interface delays. @@ -255,7 +297,7 @@ def n_cs(self) -> Optional[TimeMillisecondsAlias]: @n_cs.setter def n_cs(self, value: Optional[TimeMillisecondsAlias]): """ - Set the value of N_Cs time parameter to use. + Set the value of :ref:`N_Cs ` time parameter to use. :param value: The value to set. - None - use timing compatible with STmin value received in a preceding Flow Control packet @@ -274,25 +316,25 @@ def n_cs(self, value: Optional[TimeMillisecondsAlias]): @property def n_cs_max(self) -> TimeMillisecondsAlias: """ - Get the maximum valid value of N_Cs time parameter. + Get the maximum valid value of :ref:`N_Cs ` time parameter. .. warning:: To assess maximal value of :ref:`N_Cs `, the actual value of :ref:`N_As ` time parameter is required. - Either the latest measured value of N_Ar would be used, or 0ms would be assumed (if there are - no measurement result). + Either the latest measured value of :ref:`N_As ` would be used, + or 0ms would be assumed (if there are no measurement result). """ n_as_measured = 0 if self.n_as_measured is None else self.n_as_measured return 0.9 * self.n_cr_timeout - n_as_measured @property def n_cr_timeout(self) -> TimeMillisecondsAlias: - """Timeout value for N_Cr time parameter.""" + """Timeout value for :ref:`N_Cr ` time parameter.""" return self.__n_cr_timeout @n_cr_timeout.setter def n_cr_timeout(self, value: TimeMillisecondsAlias): """ - Set timeout value for N_Cr time parameter. + Set timeout value for :ref:`N_Cr ` time parameter. :param value: Value of timeout to set. @@ -309,13 +351,18 @@ def n_cr_timeout(self, value: TimeMillisecondsAlias): self.__n_cr_timeout = value @property # noqa - @abstractmethod def n_cr_measured(self) -> Optional[Tuple[TimeMillisecondsAlias, ...]]: """ - Get the last measured values (during the last message reception) of N_Cr time parameter. + Get the last measured values of :ref:`N_Cr ` time parameter. + + .. note:: The last measurement comes from the last reception of UDS message using either + :meth:`~uds.transport_interface.can_transport_interface.AbstractCanTransportInterface.receive_message` + :meth:`~uds.transport_interface.can_transport_interface.AbstractCanTransportInterface.async_receive_message` + method. - :return: Tuple with times in milliseconds or None if the values were never measured. + :return: Tuple with times in milliseconds or None if the values could not be measured. """ + return self.__n_cr_measured # Communication parameters @@ -431,8 +478,6 @@ def __init__(self, """ self.__n_as_measured: Optional[TimeMillisecondsAlias] = None self.__n_ar_measured: Optional[TimeMillisecondsAlias] = None - self.__n_bs_measured: Optional[Tuple[TimeMillisecondsAlias, ...]] = None - self.__n_cr_measured: Optional[Tuple[TimeMillisecondsAlias, ...]] = None super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, **kwargs) @@ -449,7 +494,7 @@ def __del__(self): @property def n_as_measured(self) -> Optional[TimeMillisecondsAlias]: """ - Get the last measured value of N_As time parameter. + Get the last measured value of :ref:`N_As ` time parameter. :return: Time in milliseconds or None if the value was never measured. """ @@ -458,30 +503,12 @@ def n_as_measured(self) -> Optional[TimeMillisecondsAlias]: @property def n_ar_measured(self) -> Optional[TimeMillisecondsAlias]: """ - Get the last measured value of N_Ar time parameter. + Get the last measured value of :ref:`N_Ar ` time parameter. :return: Time in milliseconds or None if the value was never measured. """ return self.__n_ar_measured - @property # noqa - def n_bs_measured(self) -> Optional[Tuple[TimeMillisecondsAlias, ...]]: - """ - Get the last measured value of N_Bs time parameter. - - :return: Time in milliseconds or None if the value was never measured. - """ - return self.__n_bs_measured - - @property # noqa - def n_cr_measured(self) -> Optional[Tuple[TimeMillisecondsAlias, ...]]: - """ - Get the last measured value of N_Cr time parameter. - - :return: Time in milliseconds or None if the value was never measured. - """ - return self.__n_cr_measured - def _teardown_notifier(self, suppress_warning: bool = False) -> None: """ Stop and remove CAN frame notifier for synchronous communication. @@ -535,20 +562,23 @@ def _setup_async_notifier(self, loop: AbstractEventLoop) -> None: timeout=self._MIN_NOTIFIER_TIMEOUT, loop=loop) - def _send_packets_block(self, - packets: List[CanPacket], - delay: TimeMillisecondsAlias) -> Tuple[CanPacketRecord, ...]: + def _send_cf_packets_block(self, + cf_packets_block: List[CanPacket], + delay: TimeMillisecondsAlias) -> Tuple[CanPacketRecord, ...]: """ - Send block of CAN packets. + Send block of Consecutive Frame CAN packets. - :param packets: CAN packets to send. - :param delay: Minimal delay between sending following packets [ms]. + :param cf_packets_block: Consecutive Frame CAN packets to send. + :param delay: Minimal delay between sending following Consecutive Frames [ms]. - :return: Records with historic information about transmitted CAN packets. + :raise TransmissionInterruptionError: A new UDS message transmission was started while sending this message. + + :return: Records with historic information about transmitted Consecutive Frame CAN packets. """ packet_records = [] - for packet in packets: + for cf_packet in cf_packets_block: sleep(delay / 1000.) + # handle errors - check whether another UDS message transmission was started while waiting while self.__frames_buffer.buffer.qsize() > 0: received_frame = self.__frames_buffer.buffer.get_nowait() packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, @@ -561,27 +591,32 @@ def _send_packets_block(self, transmission_time=datetime.fromtimestamp( received_frame.timestamp)) if CanPacketType.is_initial_packet_type(received_packet.packet_type): - raise TransmissionInterruptionError("UDS message transmission interrupted by a new message.") - warn(message="CAN message transmission interrupted by an unrelated CAN packet.", + raise TransmissionInterruptionError("A new UDS message transmission was started while sending " + "this message.") + warn(message="An unrelated CAN packet was received during UDS message transmission.", category=TransmissionInterruptionWarning) - packet_records.append(self.send_packet(packet)) + packet_records.append(self.send_packet(cf_packet)) return tuple(packet_records) - async def _async_send_packets_block(self, packets: List[CanPacket], - delay: TimeMillisecondsAlias, - loop: Optional[AbstractEventLoop] = None) -> Tuple[CanPacketRecord, ...]: + async def _async_send_cf_packets_block(self, + cf_packets_block: List[CanPacket], + delay: TimeMillisecondsAlias, + loop: Optional[AbstractEventLoop] = None) -> Tuple[CanPacketRecord, ...]: """ - Send block of CAN packets asynchronously. + Send block of Consecutive Frame CAN packets asynchronously. - :param packets: CAN packets to send. - :param delay: Minimal delay between sending following packets [ms]. + :param cf_packets_block: Consecutive Frame CAN packets to send. + :param delay: Minimal delay between sending following Consecutive Frames [ms]. :param loop: An asyncio event loop to use for scheduling this task. - :return: Records with historic information about transmitted CAN packets. + :raise TransmissionInterruptionError: A new UDS message transmission was started while sending this message. + + :return: Records with historic information about transmitted Consecutive Frame CAN packets. """ packet_records = [] - for packet in packets: + for cf_packet in cf_packets_block: await asyncio.sleep(delay / 1000.) + # handle errors - check whether another UDS message transmission was started while waiting while self.__frames_buffer.buffer.qsize() > 0: received_frame = self.__frames_buffer.buffer.get_nowait() packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, @@ -594,10 +629,11 @@ async def _async_send_packets_block(self, packets: List[CanPacket], transmission_time=datetime.fromtimestamp( received_frame.timestamp)) if CanPacketType.is_initial_packet_type(received_packet.packet_type): - raise TransmissionInterruptionError("UDS message transmission interrupted by a new message.") - warn(message="CAN message transmission interrupted by an unrelated CAN packet.", + raise TransmissionInterruptionError("A new UDS message transmission was started while sending " + "this message.") + warn(message="An unrelated CAN packet was received during UDS message transmission.", category=TransmissionInterruptionWarning) - packet_records.append(await self.async_send_packet(packet, loop=loop)) + packet_records.append(await self.async_send_packet(cf_packet, loop=loop)) return tuple(packet_records) def clear_frames_buffers(self) -> None: @@ -654,9 +690,11 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore raise TimeoutError("Timeout was reached before observing a CAN Packet being transmitted.") observed_frame = self.__frames_buffer.get_message(timeout=timeout_left) if is_flow_control_packet: + # Temporary solution due to https://github.com/mdabrowski1990/uds/issues/228 # self.__n_ar_measured = observed_frame.timestamp - time_start self.__n_ar_measured = time() - time_start else: + # Temporary solution due to https://github.com/mdabrowski1990/uds/issues/228 # self.__n_as_measured = observed_frame.timestamp - time_start self.__n_as_measured = time() - time_start return CanPacketRecord(frame=observed_frame, @@ -699,9 +737,11 @@ async def async_send_packet(self, timeout_left = timeout / 1000. - (time() - time_start) observed_frame = await wait_for(self.__async_frames_buffer.get_message(), timeout=timeout_left) if is_flow_control_packet: + # Temporary solution due to https://github.com/mdabrowski1990/uds/issues/228 # self.__n_ar_measured = observed_frame.timestamp - time_start self.__n_ar_measured = time() - time_start else: + # Temporary solution due to https://github.com/mdabrowski1990/uds/issues/228 # self.__n_as_measured = observed_frame.timestamp - time_start self.__n_as_measured = time() - time_start return CanPacketRecord(frame=observed_frame, @@ -793,43 +833,47 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: """ Transmit UDS message over CAN. + .. warning:: Must not be called within an asynchronous function. + :param message: A message to send. - :raise OverflowError: Flow Control packet Flow Status equal to OVERFLOW was received. - :raise TransmissionInterruptionError: A new UDS message transmission interrupted this message sending. + :raise OverflowError: Flow Control packet with Flow Status equal to OVERFLOW was received. + :raise TransmissionInterruptionError: A new UDS message transmission was started while sending this message. + :raise NotImplementedError: Flow Control CAN packet with unknown Flow Status was received. :return: Record with historic information about transmitted UDS message. """ - n_bs_measurements = [] packets_to_send = list(self.segmenter.segmentation(message)) packet_records = [self.send_packet(packets_to_send.pop(0))] time_n_bs_measurement_start = time() while packets_to_send: record = self.receive_packet(timeout=self.N_BS_TIMEOUT) - n_bs_measurements.append((time() - time_n_bs_measurement_start) * 1000.) if record.packet_type == CanPacketType.FLOW_CONTROL: packet_records.append(record) if record.flow_status == CanFlowStatus.ContinueToSend: number_of_packets = len(packets_to_send) if record.block_size == 0 else record.block_size delay_between_cf = self.n_cs if self.n_cs is not None else \ CanSTminTranslator.decode(record.st_min) # type: ignore - packet_records.extend(self._send_packets_block(packets=packets_to_send[:number_of_packets], - delay=delay_between_cf)) - time_n_bs_measurement_start = time() + packet_records.extend(self._send_cf_packets_block( + cf_packets_block=packets_to_send[:number_of_packets], + delay=delay_between_cf)) packets_to_send = packets_to_send[number_of_packets:] elif record.flow_status == CanFlowStatus.Wait: - time_n_bs_measurement_start = time() + # TODO: Handle N_WFTmax + continue elif record.flow_status == CanFlowStatus.Overflow: raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") else: raise NotImplementedError(f"Unknown Flow Status received: {record.flow_status}") elif CanPacketType.is_initial_packet_type(record.packet_type): - raise TransmissionInterruptionError("UDS message transmission interrupted by a new message.") + raise TransmissionInterruptionError("A new UDS message transmission was started while sending " + "this message.") else: - warn(message="CAN message transmission interrupted by an unrelated CAN packet.", + warn(message="An unrelated CAN packet was received during UDS message transmission.", category=TransmissionInterruptionWarning) - self.__n_bs_measured = tuple(n_bs_measurements) if n_bs_measurements else None - return UdsMessageRecord(packet_records) + message_records = UdsMessageRecord(packet_records) + self._update_n_bs_measured(message_records) + return message_records async def async_send_message(self, message: UdsMessage, @@ -840,15 +884,16 @@ async def async_send_message(self, :param message: A message to send. :param loop: An asyncio event loop to use for scheduling this task. + :raise OverflowError: Flow Control packet with Flow Status equal to OVERFLOW was received. + :raise TransmissionInterruptionError: A new UDS message transmission was started while sending this message. + :raise NotImplementedError: Flow Control CAN packet with unknown Flow Status was received. + :return: Record with historic information about transmitted UDS message. """ - n_bs_measurements = [] packets_to_send = list(self.segmenter.segmentation(message)) packet_records = [await self.async_send_packet(packets_to_send.pop(0), loop=loop)] - time_n_bs_measurement_start = time() while packets_to_send: record = await self.async_receive_packet(timeout=self.N_BS_TIMEOUT, loop=loop) - n_bs_measurements.append((time() - time_n_bs_measurement_start) * 1000.) if record.packet_type == CanPacketType.FLOW_CONTROL: packet_records.append(record) if record.flow_status == CanFlowStatus.ContinueToSend: @@ -856,29 +901,33 @@ async def async_send_message(self, delay_between_cf = self.n_cs if self.n_cs is not None else \ CanSTminTranslator.decode(record.st_min) # type: ignore packet_records.extend( - await self._async_send_packets_block(packets=packets_to_send[:number_of_packets], - delay=delay_between_cf, - loop=loop)) - time_n_bs_measurement_start = time() + await self._async_send_cf_packets_block(cf_packets_block=packets_to_send[:number_of_packets], + delay=delay_between_cf, + loop=loop)) packets_to_send = packets_to_send[number_of_packets:] elif record.flow_status == CanFlowStatus.Wait: - time_n_bs_measurement_start = time() + # TODO: Handle N_WFTmax + continue elif record.flow_status == CanFlowStatus.Overflow: raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") else: raise NotImplementedError(f"Unknown Flow Status received: {record.flow_status}") elif CanPacketType.is_initial_packet_type(record.packet_type): - raise TransmissionInterruptionError("UDS message transmission interrupted by a new message.") + raise TransmissionInterruptionError("A new UDS message transmission was started while sending " + "this message.") else: - warn(message="CAN message transmission interrupted by an unrelated CAN packet.", + warn(message="An unrelated CAN packet was received during UDS message transmission.", category=TransmissionInterruptionWarning) - self.__n_bs_measured = tuple(n_bs_measurements) if n_bs_measurements else None - return UdsMessageRecord(packet_records) + message_records = UdsMessageRecord(packet_records) + self._update_n_bs_measured(message_records) + return message_records def receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> UdsMessageRecord: """ Receive UDS message over CAN. + .. warning:: Must not be called within an asynchronous function. + :param timeout: Maximal time (in milliseconds) to wait for UDS message transmission to start. This means that receiving might last longer if First Frame was received within provided time. diff --git a/uds/utilities/custom_warnings.py b/uds/utilities/custom_warnings.py index 593ba806..dc479bd5 100644 --- a/uds/utilities/custom_warnings.py +++ b/uds/utilities/custom_warnings.py @@ -24,5 +24,5 @@ class TransmissionInterruptionWarning(RuntimeWarning): """ An unexpected packet was received during UDS message transmission. - According to UDS ISO Standards it shall be ignored. + According to UDS ISO Standards a received packet shall be ignored. """ From f24faa1ca107534129de7c79c83cdcf872b1683c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sun, 22 Sep 2024 20:15:15 +0200 Subject: [PATCH 14/20] Update can_transport_interface.py remove needless lines --- uds/transport_interface/can_transport_interface.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index cc9d488e..72dcc545 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -80,7 +80,6 @@ def __init__(self, - :parameter use_data_optimization: Information whether to use CAN Frame Data Optimization. - :parameter filler_byte: Filler byte value to use for CAN Frame Data Padding. - :parameter flow_control_parameters_generator: Generator with Flow Control parameters to use. - # TODO: Add N_WFTmax :raise TypeError: Provided Addressing Information value has unexpected type. """ @@ -845,7 +844,6 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: """ packets_to_send = list(self.segmenter.segmentation(message)) packet_records = [self.send_packet(packets_to_send.pop(0))] - time_n_bs_measurement_start = time() while packets_to_send: record = self.receive_packet(timeout=self.N_BS_TIMEOUT) if record.packet_type == CanPacketType.FLOW_CONTROL: @@ -859,7 +857,6 @@ def send_message(self, message: UdsMessage) -> UdsMessageRecord: delay=delay_between_cf)) packets_to_send = packets_to_send[number_of_packets:] elif record.flow_status == CanFlowStatus.Wait: - # TODO: Handle N_WFTmax continue elif record.flow_status == CanFlowStatus.Overflow: raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") @@ -906,7 +903,6 @@ async def async_send_message(self, loop=loop)) packets_to_send = packets_to_send[number_of_packets:] elif record.flow_status == CanFlowStatus.Wait: - # TODO: Handle N_WFTmax continue elif record.flow_status == CanFlowStatus.Overflow: raise OverflowError("Flow Control with Flow Status `OVERFLOW` was received.") From 3ce415471813e87f2a8d3547ab66eeb14dfc394e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sun, 22 Sep 2024 21:07:17 +0200 Subject: [PATCH 15/20] add message interruption system tests - add system tests with message interrupting another message - fix async_frames_bugger issue --- .../test_can_transport_interface.py | 8 +- .../test_python_can.py | 111 ++++++++++++++++++ .../can_transport_interface.py | 12 +- 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/tests/software_tests/transport_interface/test_can_transport_interface.py b/tests/software_tests/transport_interface/test_can_transport_interface.py index 6aafbe60..45b53f65 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -931,7 +931,7 @@ def test_send_cf_packets_block__transmission_interruption(self, packets, delay): @pytest.mark.asyncio async def test_async_send_cf_packets_block(self, packets, delay): mock_qsize = Mock(return_value=0) - self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock(qsize=mock_qsize)) + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=Mock(qsize=mock_qsize)) packet_records = await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) @@ -952,7 +952,7 @@ async def test_async_send_cf_packets_block(self, packets, delay): async def test_async_send_cf_packets_block__other_traffic(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[3, 2, 1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=Mock( get_nowait=mock_get_nowait, qsize=mock_qsize )) @@ -977,7 +977,7 @@ async def test_async_send_cf_packets_block__other_traffic(self, packets, delay): async def test_async_send_cf_packets_block__packets_traffic(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=Mock( get_nowait=mock_get_nowait, qsize=mock_qsize )) @@ -1004,7 +1004,7 @@ async def test_async_send_cf_packets_block__packets_traffic(self, packets, delay async def test_async_send_cf_packets_block__transmission_interruption(self, packets, delay): mock_get_nowait = Mock(return_value=Mock(spec=Message)) mock_qsize = Mock(side_effect=[1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=Mock( get_nowait=mock_get_nowait, qsize=mock_qsize )) diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index 7cbbbcd8..79901c40 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -12,6 +12,7 @@ from uds.segmentation import CanSegmenter from uds.transmission_attributes import AddressingType, TransmissionDirection from uds.transport_interface import PyCanTransportInterface +from uds.utilities import TransmissionInterruptionError class TestPythonCanKvaser: @@ -1620,3 +1621,113 @@ async def _send_frame(): with pytest.raises(OverflowError): await future_record await frame_sending_task + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("new_message", [ + UdsMessage([0x10, 0x81], AddressingType.FUNCTIONAL), + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("fc_after, new_message_after, st_min", [ + (5, 100, 50), + (50, 1, 100), + (1, 10, 1), + ]) + def test_new_message_started_when_multi_packet_message_sending(self, example_addressing_information, + example_addressing_information_2nd_node, + message, new_message, fc_after, new_message_after, + st_min): + """ + Check for a synchronous multi packet (FF + CF) UDS message sending being interrupted by a new message. + + Procedure: + 1. Schedule Flow Control CAN packet with information to continue sending all consecutive frame packets at once. + 2. Schedule Single Frame/First Frame CAN packet starting a transmission of a new message. + 3. Send a UDS message using Transport Interface (via CAN Interface). + Expected: UDS message transmission stopped and an exception raised. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information_2nd_node: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + :param new_message: UDS message to interrupt. + :param fc_after: Delay to use for sending CAN flow control. + :param new_message_after: Delay to use for sending interrupting message. + :param st_min: STmin value to transmit in Flow Control. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.ContinueToSend, + block_size=0, + st_min=st_min) + flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) + interrupting_packet = other_node_segmenter.segmentation(new_message)[0] + interrupting_frame = Message(arbitration_id=interrupting_packet.can_id, data=interrupting_packet.raw_frame_data) + Timer(interval=fc_after / 1000., function=self.can_interface_2.send, args=(flow_control_frame,)).start() + Timer(interval=new_message_after / 1000., function=self.can_interface_2.send, + args=(interrupting_frame,)).start() + with pytest.raises(TransmissionInterruptionError): + can_transport_interface.send_message(message) + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x22, *range(62)], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("new_message", [ + UdsMessage([0x10, 0x81], AddressingType.FUNCTIONAL), + UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.parametrize("fc_after, new_message_after, st_min", [ + (5, 100, 50), + (50, 1, 100), + (1, 10, 1), + ]) + @pytest.mark.asyncio + async def test_new_message_started_when_multi_packet_async_message_sending(self, example_addressing_information, + example_addressing_information_2nd_node, + message, new_message, fc_after, + new_message_after, st_min): + """ + Check for a synchronous multi packet (FF + CF) UDS message sending being interrupted by a new message. + + Procedure: + 1. Schedule Flow Control CAN packet with information to continue sending all consecutive frame packets at once. + 2. Schedule Single Frame/First Frame CAN packet starting a transmission of a new message. + 3. Send a UDS message using Transport Interface (via CAN Interface). + Expected: UDS message transmission stopped and an exception raised. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information_2nd_node: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + :param new_message: UDS message to interrupt. + :param fc_after: Delay to use for sending CAN flow control. + :param new_message_after: Delay to use for sending interrupting message after Flow Control. + :param st_min: STmin value to transmit in Flow Control. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + flow_control_packet = other_node_segmenter.get_flow_control_packet(flow_status=CanFlowStatus.ContinueToSend, + block_size=0, + st_min=st_min) + flow_control_frame = Message(arbitration_id=flow_control_packet.can_id, data=flow_control_packet.raw_frame_data) + interrupting_packet = other_node_segmenter.segmentation(new_message)[0] + interrupting_frame = Message(arbitration_id=interrupting_packet.can_id, data=interrupting_packet.raw_frame_data) + + async def _send_fc(): + await asyncio.sleep(fc_after / 1000.) + self.can_interface_2.send(flow_control_frame) + + async def _send_message(): + await asyncio.sleep(new_message_after / 1000.) + self.can_interface_2.send(interrupting_frame) + + send_fc_task = asyncio.create_task(_send_fc()) + send_message_task = asyncio.create_task(_send_message()) + with pytest.raises(TransmissionInterruptionError): + await can_transport_interface.async_send_message(message) + + await send_fc_task + await send_message_task diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 72dcc545..8f8a42ad 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -114,7 +114,7 @@ def _update_n_bs_measured(self, message: UdsMessageRecord) -> None: for i, packet_record in enumerate(message.packets_records[1:]): if packet_record.packet_type == CanPacketType.FLOW_CONTROL: n_bs = packet_record.transmission_time - message.packets_records[i].transmission_time - n_bs_measured.append(round(n_bs.total_seconds()*1000, 3)) + n_bs_measured.append(round(n_bs.total_seconds() * 1000, 3)) self.__n_bs_measured = tuple(n_bs_measured) def _update_n_cr_measured(self, message: UdsMessageRecord) -> None: @@ -130,7 +130,7 @@ def _update_n_cr_measured(self, message: UdsMessageRecord) -> None: for i, packet_record in enumerate(message.packets_records[1:]): if packet_record.packet_type == CanPacketType.CONSECUTIVE_FRAME: n_cr = packet_record.transmission_time - message.packets_records[i].transmission_time - n_cr_measured.append(round(n_cr.total_seconds()*1000, 3)) + n_cr_measured.append(round(n_cr.total_seconds() * 1000, 3)) self.__n_cr_measured = tuple(n_cr_measured) @property @@ -480,10 +480,10 @@ def __init__(self, super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, **kwargs) - self.__notifier: Optional[Notifier] = None self.__frames_buffer = BufferedReader() - self.__async_notifier: Optional[Notifier] = None + self.__notifier: Optional[Notifier] = None self.__async_frames_buffer = AsyncBufferedReader() + self.__async_notifier: Optional[Notifier] = None def __del__(self): """Safely close all threads open by this object.""" @@ -616,8 +616,8 @@ async def _async_send_cf_packets_block(self, for cf_packet in cf_packets_block: await asyncio.sleep(delay / 1000.) # handle errors - check whether another UDS message transmission was started while waiting - while self.__frames_buffer.buffer.qsize() > 0: - received_frame = self.__frames_buffer.buffer.get_nowait() + while self.__async_frames_buffer.buffer.qsize() > 0: + received_frame = self.__async_frames_buffer.buffer.get_nowait() packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) if packet_addressing_type is not None: From fcda2ac952599b32870854d91e85cd89995d0200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sun, 22 Sep 2024 21:41:43 +0200 Subject: [PATCH 16/20] Update test_python_can.py Why? --- .../test_python_can.py | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index 79901c40..877ed14e 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -1633,7 +1633,7 @@ async def _send_frame(): @pytest.mark.parametrize("fc_after, new_message_after, st_min", [ (5, 100, 50), (50, 1, 100), - (1, 10, 1), + (8, 10, 10), ]) def test_new_message_started_when_multi_packet_message_sending(self, example_addressing_information, example_addressing_information_2nd_node, @@ -1670,6 +1670,7 @@ def test_new_message_started_when_multi_packet_message_sending(self, example_add args=(interrupting_frame,)).start() with pytest.raises(TransmissionInterruptionError): can_transport_interface.send_message(message) + sleep(self.DELAY_AFTER_RECEIVING_FRAME / 100.) @pytest.mark.parametrize("message", [ UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), @@ -1680,9 +1681,9 @@ def test_new_message_started_when_multi_packet_message_sending(self, example_add UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), ]) @pytest.mark.parametrize("fc_after, new_message_after, st_min", [ - (5, 100, 50), + # (5, 100, 50), # TODO: figure out why this one keep failing (50, 1, 100), - (1, 10, 1), + (8, 10, 10), ]) @pytest.mark.asyncio async def test_new_message_started_when_multi_packet_async_message_sending(self, example_addressing_information, @@ -1716,18 +1717,21 @@ async def test_new_message_started_when_multi_packet_async_message_sending(self, interrupting_packet = other_node_segmenter.segmentation(new_message)[0] interrupting_frame = Message(arbitration_id=interrupting_packet.can_id, data=interrupting_packet.raw_frame_data) - async def _send_fc(): - await asyncio.sleep(fc_after / 1000.) - self.can_interface_2.send(flow_control_frame) - - async def _send_message(): - await asyncio.sleep(new_message_after / 1000.) - self.can_interface_2.send(interrupting_frame) - - send_fc_task = asyncio.create_task(_send_fc()) - send_message_task = asyncio.create_task(_send_message()) + async def _send_fc_and_message(): + if fc_after > new_message_after: + await asyncio.sleep(new_message_after / 1000.) + self.can_interface_2.send(interrupting_frame) + await asyncio.sleep((fc_after - new_message_after) / 1000.) + self.can_interface_2.send(flow_control_frame) + else: + await asyncio.sleep(fc_after / 1000.) + self.can_interface_2.send(flow_control_frame) + await asyncio.sleep((new_message_after - fc_after) / 1000.) + self.can_interface_2.send(interrupting_frame) + + send_fc_and_message_task = asyncio.create_task(_send_fc_and_message()) with pytest.raises(TransmissionInterruptionError): await can_transport_interface.async_send_message(message) - await send_fc_task - await send_message_task + await send_fc_and_message_task + await asyncio.sleep(self.DELAY_AFTER_RECEIVING_FRAME / 100.) From dfb2ccd558e17f8ebb95866a5c705e5ab4d2373c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Mon, 23 Sep 2024 16:51:31 +0200 Subject: [PATCH 17/20] Update config.wcc --- examples/can/python-can/kvaser/config.wcc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/can/python-can/kvaser/config.wcc b/examples/can/python-can/kvaser/config.wcc index 0f58375a..12670ced 100644 --- a/examples/can/python-can/kvaser/config.wcc +++ b/examples/can/python-can/kvaser/config.wcc @@ -86,7 +86,7 @@ StandardText 0 - 0 + 1 0 0 0 @@ -162,7 +162,7 @@ 1 - 1,0,2 + 0,1,2 0,1,0,0 @@ -215,7 +215,7 @@ 0 - 0 + 1 From a1762ab29555b36b082052bde6a1464d3944da59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Mon, 23 Sep 2024 18:25:32 +0200 Subject: [PATCH 18/20] fix defect Fix defect with asynchronous implementation where in some cases received packets were ignored during message transmission. --- .../test_can_transport_interface.py | 221 +++++++++--------- .../test_python_can.py | 16 +- .../can_transport_interface.py | 42 ++-- 3 files changed, 134 insertions(+), 145 deletions(-) diff --git a/tests/software_tests/transport_interface/test_can_transport_interface.py b/tests/software_tests/transport_interface/test_can_transport_interface.py index 45b53f65..5ada29f8 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -640,10 +640,6 @@ def setup_method(self): self.mock_wait_for = self._patcher_wait_for.start() self._patcher_time = patch(f"{SCRIPT_LOCATION}.time") self.mock_time = self._patcher_time.start() - self._patcher_sleep = patch(f"{SCRIPT_LOCATION}.sleep") - self.mock_sleep = self._patcher_sleep.start() - self._patcher_asyncio_sleep = patch(f"{SCRIPT_LOCATION}.asyncio.sleep") - self.mock_asyncio_sleep = self._patcher_asyncio_sleep.start() self._patcher_datetime = patch(f"{SCRIPT_LOCATION}.datetime") self.mock_datetime = self._patcher_datetime.start() self._patcher_abstract_can_ti_init = patch(f"{SCRIPT_LOCATION}.AbstractCanTransportInterface.__init__") @@ -671,7 +667,6 @@ def teardown_method(self): self._patcher_warn.stop() self._patcher_wait_for.stop() self._patcher_time.stop() - self._patcher_sleep.stop() self._patcher_datetime.stop() self._patcher_abstract_can_ti_init.stop() self._patcher_uds_message.stop() @@ -839,86 +834,89 @@ def test_setup_async_notifier__notifier_exists(self, loop): (Mock(spec=CanPacket), Mock(spec=CanPacket)), (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), ]) - @pytest.mark.parametrize("delay", [0, 1234]) + @pytest.mark.parametrize("delay", [0, 12.34]) def test_send_cf_packets_block(self, packets, delay): - mock_qsize = Mock(return_value=0) - self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock(qsize=mock_qsize)) - packet_records = PyCanTransportInterface._send_cf_packets_block(self=self.mock_can_transport_interface, - cf_packets_block=packets, delay=delay) - assert isinstance(packet_records, tuple) - assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value - for packet_record in packet_records) - self.mock_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) - assert self.mock_sleep.call_count == len(packets) - self.mock_can_transport_interface.send_packet.assert_has_calls(calls=[call(packet) for packet in packets]) - self.mock_warn.assert_not_called() - - @pytest.mark.parametrize("packets", [ - (Mock(spec=CanPacket), Mock(spec=CanPacket)), - (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), - ]) - @pytest.mark.parametrize("delay", [0, 1234]) - def test_send_cf_packets_block__other_traffic(self, packets, delay): - mock_get_nowait = Mock(return_value=Mock(spec=Message)) - mock_qsize = Mock(side_effect=[3, 2, 1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( - get_nowait=mock_get_nowait, - qsize=mock_qsize - )) - self.mock_can_transport_interface.segmenter.is_input_packet = Mock(return_value=None) + called = 0 + + def once_true_once_false(*args): + nonlocal called + called += 1 + return called % 2 + + mock_sub = MagicMock() + mock_add = MagicMock(__sub__=Mock(return_value=mock_sub)) + self.mock_time.return_value.__add__.return_value = mock_add + mock_lt = Mock(side_effect=once_true_once_false) + self.mock_time.return_value.__lt__ = mock_lt + self.mock_can_transport_interface.receive_packet.side_effect = TimeoutError packet_records = PyCanTransportInterface._send_cf_packets_block(self=self.mock_can_transport_interface, cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value for packet_record in packet_records) - self.mock_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) - assert self.mock_sleep.call_count == len(packets) self.mock_can_transport_interface.send_packet.assert_has_calls(calls=[call(packet) for packet in packets]) + self.mock_can_transport_interface.receive_packet.assert_called() self.mock_warn.assert_not_called() @pytest.mark.parametrize("packets", [ (Mock(spec=CanPacket), Mock(spec=CanPacket)), (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), ]) - @pytest.mark.parametrize("delay", [0, 1234]) + @pytest.mark.parametrize("delay", [0, 12.34]) def test_send_cf_packets_block__packets_traffic(self, packets, delay): - mock_get_nowait = Mock(return_value=Mock(spec=Message)) - mock_qsize = Mock(side_effect=[1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( - get_nowait=mock_get_nowait, - qsize=mock_qsize - )) + called = 0 + + def once_true_once_false(*args): + nonlocal called + called += 1 + return called % 2 + + mock_sub = MagicMock() + mock_add = MagicMock(__sub__=Mock(return_value=mock_sub)) + self.mock_time.return_value.__add__.return_value = mock_add + mock_lt = Mock(side_effect=once_true_once_false) + self.mock_time.return_value.__lt__ = mock_lt + mock_received_packet_records = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface.receive_packet.return_value = mock_received_packet_records self.mock_can_packet_type_is_initial_packet_type.return_value = False - self.mock_can_transport_interface.segmenter.is_input_packet = Mock( - return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) packet_records = PyCanTransportInterface._send_cf_packets_block(self=self.mock_can_transport_interface, cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.send_packet.return_value for packet_record in packet_records) - self.mock_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) - assert self.mock_sleep.call_count == len(packets) self.mock_can_transport_interface.send_packet.assert_has_calls(calls=[call(packet) for packet in packets]) + self.mock_can_transport_interface.receive_packet.assert_called() + self.mock_can_packet_type_is_initial_packet_type.assert_has_calls( + calls=[call(mock_received_packet_records.packet_type) for _ in packets]) self.mock_warn.assert_called() @pytest.mark.parametrize("packets", [ (Mock(spec=CanPacket), Mock(spec=CanPacket)), (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), ]) - @pytest.mark.parametrize("delay", [0, 1234]) + @pytest.mark.parametrize("delay", [0, 12.34]) def test_send_cf_packets_block__transmission_interruption(self, packets, delay): - mock_get_nowait = Mock(return_value=Mock(spec=Message)) - mock_qsize = Mock(side_effect=[1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=Mock( - get_nowait=mock_get_nowait, - qsize=mock_qsize - )) + called = 0 + + def once_true_once_false(*args): + nonlocal called + called += 1 + return called % 2 + + mock_sub = MagicMock() + mock_add = MagicMock(__sub__=Mock(return_value=mock_sub)) + self.mock_time.return_value.__add__.return_value = mock_add + mock_lt = Mock(side_effect=once_true_once_false) + self.mock_time.return_value.__lt__ = mock_lt + mock_received_packet_records = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface.receive_packet.return_value = mock_received_packet_records self.mock_can_packet_type_is_initial_packet_type.return_value = True - self.mock_can_transport_interface.segmenter.is_input_packet = Mock( - return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) with pytest.raises(TransmissionInterruptionError): PyCanTransportInterface._send_cf_packets_block(self=self.mock_can_transport_interface, cf_packets_block=packets, delay=delay) + self.mock_can_transport_interface.receive_packet.assert_called() + self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with( + mock_received_packet_records.packet_type) self.mock_warn.assert_not_called() # _async_send_cf_packets_block @@ -927,45 +925,30 @@ def test_send_cf_packets_block__transmission_interruption(self, packets, delay): (Mock(spec=CanPacket), Mock(spec=CanPacket)), (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), ]) - @pytest.mark.parametrize("delay", [0, 1234]) + @pytest.mark.parametrize("delay", [0, 12.34]) @pytest.mark.asyncio async def test_async_send_cf_packets_block(self, packets, delay): - mock_qsize = Mock(return_value=0) - self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=Mock(qsize=mock_qsize)) - packet_records = await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, - cf_packets_block=packets, delay=delay) - assert isinstance(packet_records, tuple) - assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value - for packet_record in packet_records) - self.mock_asyncio_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) - assert self.mock_asyncio_sleep.call_count == len(packets) - self.mock_can_transport_interface.async_send_packet.assert_has_calls(calls=[call(packet, loop=None) - for packet in packets]) - self.mock_warn.assert_not_called() - - @pytest.mark.parametrize("packets", [ - (Mock(spec=CanPacket), Mock(spec=CanPacket)), - (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)), - ]) - @pytest.mark.parametrize("delay", [0, 1234]) - @pytest.mark.asyncio - async def test_async_send_cf_packets_block__other_traffic(self, packets, delay): - mock_get_nowait = Mock(return_value=Mock(spec=Message)) - mock_qsize = Mock(side_effect=[3, 2, 1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=Mock( - get_nowait=mock_get_nowait, - qsize=mock_qsize - )) - self.mock_can_transport_interface.segmenter.is_input_packet = Mock(return_value=None) - packet_records = await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, - cf_packets_block=packets, delay=delay) + called = 0 + + def once_true_once_false(*args): + nonlocal called + called += 1 + return called % 2 + + mock_sub = MagicMock() + mock_add = MagicMock(__sub__=Mock(return_value=mock_sub)) + self.mock_time.return_value.__add__.return_value = mock_add + mock_lt = Mock(side_effect=once_true_once_false) + self.mock_time.return_value.__lt__ = mock_lt + self.mock_can_transport_interface.async_receive_packet.side_effect = TimeoutError + packet_records = await PyCanTransportInterface._async_send_cf_packets_block( + self=self.mock_can_transport_interface, cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value for packet_record in packet_records) - self.mock_asyncio_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) - assert self.mock_asyncio_sleep.call_count == len(packets) - self.mock_can_transport_interface.async_send_packet.assert_has_calls(calls=[call(packet, loop=None) - for packet in packets]) + self.mock_can_transport_interface.async_send_packet.assert_has_calls( + calls=[call(packet, loop=None) for packet in packets]) + self.mock_can_transport_interface.async_receive_packet.assert_called() self.mock_warn.assert_not_called() @pytest.mark.parametrize("packets", [ @@ -975,24 +958,31 @@ async def test_async_send_cf_packets_block__other_traffic(self, packets, delay): @pytest.mark.parametrize("delay", [0, 1234]) @pytest.mark.asyncio async def test_async_send_cf_packets_block__packets_traffic(self, packets, delay): - mock_get_nowait = Mock(return_value=Mock(spec=Message)) - mock_qsize = Mock(side_effect=[1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=Mock( - get_nowait=mock_get_nowait, - qsize=mock_qsize - )) + called = 0 + + def once_true_once_false(*args): + nonlocal called + called += 1 + return called % 2 + + mock_sub = MagicMock() + mock_add = MagicMock(__sub__=Mock(return_value=mock_sub)) + self.mock_time.return_value.__add__.return_value = mock_add + mock_lt = Mock(side_effect=once_true_once_false) + self.mock_time.return_value.__lt__ = mock_lt + mock_received_packet_records = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface.async_receive_packet.return_value = mock_received_packet_records self.mock_can_packet_type_is_initial_packet_type.return_value = False - self.mock_can_transport_interface.segmenter.is_input_packet = Mock( - return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) - packet_records = await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, - cf_packets_block=packets, delay=delay) + packet_records = await PyCanTransportInterface._async_send_cf_packets_block( + self=self.mock_can_transport_interface, cf_packets_block=packets, delay=delay) assert isinstance(packet_records, tuple) assert all(packet_record == self.mock_can_transport_interface.async_send_packet.return_value for packet_record in packet_records) - self.mock_asyncio_sleep.assert_has_calls(calls=[call(delay / 1000.)] * len(packets)) - assert self.mock_asyncio_sleep.call_count == len(packets) - self.mock_can_transport_interface.async_send_packet.assert_has_calls(calls=[call(packet, loop=None) - for packet in packets]) + self.mock_can_transport_interface.async_send_packet.assert_has_calls( + calls=[call(packet, loop=None) for packet in packets]) + self.mock_can_transport_interface.async_receive_packet.assert_called() + self.mock_can_packet_type_is_initial_packet_type.assert_has_calls( + calls=[call(mock_received_packet_records.packet_type) for _ in packets]) self.mock_warn.assert_called() @pytest.mark.parametrize("packets", [ @@ -1002,18 +992,27 @@ async def test_async_send_cf_packets_block__packets_traffic(self, packets, delay @pytest.mark.parametrize("delay", [0, 1234]) @pytest.mark.asyncio async def test_async_send_cf_packets_block__transmission_interruption(self, packets, delay): - mock_get_nowait = Mock(return_value=Mock(spec=Message)) - mock_qsize = Mock(side_effect=[1, 0] * len(packets)) - self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=Mock( - get_nowait=mock_get_nowait, - qsize=mock_qsize - )) + called = 0 + + def once_true_once_false(*args): + nonlocal called + called += 1 + return called % 2 + + mock_sub = MagicMock() + mock_add = MagicMock(__sub__=Mock(return_value=mock_sub)) + self.mock_time.return_value.__add__.return_value = mock_add + mock_lt = Mock(side_effect=once_true_once_false) + self.mock_time.return_value.__lt__ = mock_lt + mock_received_packet_records = Mock(spec=CanPacketRecord) + self.mock_can_transport_interface.async_receive_packet.return_value = mock_received_packet_records self.mock_can_packet_type_is_initial_packet_type.return_value = True - self.mock_can_transport_interface.segmenter.is_input_packet = Mock( - return_value=lambda: choice({AddressingType.PHYSICAL, AddressingType.FUNCTIONAL})) with pytest.raises(TransmissionInterruptionError): - await PyCanTransportInterface._async_send_cf_packets_block(self=self.mock_can_transport_interface, - cf_packets_block=packets, delay=delay) + await PyCanTransportInterface._async_send_cf_packets_block( + self=self.mock_can_transport_interface, cf_packets_block=packets, delay=delay) + self.mock_can_transport_interface.async_receive_packet.assert_called() + self.mock_can_packet_type_is_initial_packet_type.assert_called_once_with( + mock_received_packet_records.packet_type) self.mock_warn.assert_not_called() # clear_frames_buffers diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index 877ed14e..fa2b2e19 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -190,7 +190,7 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, Message(data=[0xFF, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), ]) @pytest.mark.parametrize("timeout, send_after", [ - (1000, 1001), # ms + (1000, 1005), # ms (50, 55), ]) def test_receive_packet__timeout(self, addressing_information, addressing_type, frame, timeout, send_after): @@ -506,7 +506,7 @@ async def test_async_send_packet(self, packet_type, addressing_type, addressing_ Message(data=[0xFF, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), ]) @pytest.mark.parametrize("timeout, send_after", [ - (1000, 1001), # ms + (1000, 1005), # ms (50, 55), ]) @pytest.mark.asyncio @@ -1000,7 +1000,7 @@ async def _send_frame(): UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), ]) @pytest.mark.parametrize("timeout, send_after", [ - (1000, 1001), # ms + (1000, 1005), # ms (50, 55), ]) def test_receive_message__sf__timeout(self, example_addressing_information, example_addressing_information_2nd_node, @@ -1089,7 +1089,7 @@ def test_receive_message__sf(self, example_addressing_information, example_addre UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), ]) @pytest.mark.parametrize("timeout, send_after", [ - (1000, 1001), # ms + (1000, 1005), # ms (50, 55), ]) @pytest.mark.asyncio @@ -1631,9 +1631,9 @@ async def _send_frame(): UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), ]) @pytest.mark.parametrize("fc_after, new_message_after, st_min", [ - (5, 100, 50), (50, 1, 100), (8, 10, 10), + (5, 100, 50), ]) def test_new_message_started_when_multi_packet_message_sending(self, example_addressing_information, example_addressing_information_2nd_node, @@ -1681,9 +1681,9 @@ def test_new_message_started_when_multi_packet_message_sending(self, example_add UdsMessage(payload=[0x62, 0x12, 0x34, *range(100, 200)], addressing_type=AddressingType.PHYSICAL), ]) @pytest.mark.parametrize("fc_after, new_message_after, st_min", [ - # (5, 100, 50), # TODO: figure out why this one keep failing (50, 1, 100), (8, 10, 10), + (5, 100, 51), ]) @pytest.mark.asyncio async def test_new_message_started_when_multi_packet_async_message_sending(self, example_addressing_information, @@ -1691,12 +1691,12 @@ async def test_new_message_started_when_multi_packet_async_message_sending(self, message, new_message, fc_after, new_message_after, st_min): """ - Check for a synchronous multi packet (FF + CF) UDS message sending being interrupted by a new message. + Check for a asynchronous multi packet (FF + CF) UDS message sending being interrupted by a new message. Procedure: 1. Schedule Flow Control CAN packet with information to continue sending all consecutive frame packets at once. 2. Schedule Single Frame/First Frame CAN packet starting a transmission of a new message. - 3. Send a UDS message using Transport Interface (via CAN Interface). + 3. Send (using async method) a UDS message using Transport Interface (via CAN Interface). Expected: UDS message transmission stopped and an exception raised. :param example_addressing_information: Example Addressing Information of a CAN Node. diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 8f8a42ad..9b27b190 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -576,19 +576,13 @@ def _send_cf_packets_block(self, """ packet_records = [] for cf_packet in cf_packets_block: - sleep(delay / 1000.) - # handle errors - check whether another UDS message transmission was started while waiting - while self.__frames_buffer.buffer.qsize() > 0: - received_frame = self.__frames_buffer.buffer.get_nowait() - packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, - data=received_frame.data) - if packet_addressing_type is not None: - received_packet = CanPacketRecord(frame=received_frame, - direction=TransmissionDirection.RECEIVED, - addressing_type=packet_addressing_type, - addressing_format=self.segmenter.addressing_format, - transmission_time=datetime.fromtimestamp( - received_frame.timestamp)) + time_end = time() + (delay / 1000.) + while time() < time_end: + try: + received_packet = self.receive_packet(timeout=(time_end - time())) + except TimeoutError: + pass + else: if CanPacketType.is_initial_packet_type(received_packet.packet_type): raise TransmissionInterruptionError("A new UDS message transmission was started while sending " "this message.") @@ -614,19 +608,13 @@ async def _async_send_cf_packets_block(self, """ packet_records = [] for cf_packet in cf_packets_block: - await asyncio.sleep(delay / 1000.) - # handle errors - check whether another UDS message transmission was started while waiting - while self.__async_frames_buffer.buffer.qsize() > 0: - received_frame = self.__async_frames_buffer.buffer.get_nowait() - packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, - data=received_frame.data) - if packet_addressing_type is not None: - received_packet = CanPacketRecord(frame=received_frame, - direction=TransmissionDirection.RECEIVED, - addressing_type=packet_addressing_type, - addressing_format=self.segmenter.addressing_format, - transmission_time=datetime.fromtimestamp( - received_frame.timestamp)) + time_end = time() + (delay / 1000.) + while time() < time_end: + try: + received_packet = await self.async_receive_packet(timeout=(time_end - time())) + except TimeoutError: + pass + else: if CanPacketType.is_initial_packet_type(received_packet.packet_type): raise TransmissionInterruptionError("A new UDS message transmission was started while sending " "this message.") @@ -644,6 +632,8 @@ def clear_frames_buffers(self) -> None: for _ in range(self.__frames_buffer.buffer.qsize()): self.__frames_buffer.buffer.get_nowait() for _ in range(self.__async_frames_buffer.buffer.qsize()): + print("ERROR - clearing frames") + print(_) self.__async_frames_buffer.buffer.get_nowait() @staticmethod From b635ad3dadbfc307d048c696c9582e8b4f09e08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Mon, 23 Sep 2024 19:09:24 +0200 Subject: [PATCH 19/20] Update can_transport_interface.py --- uds/transport_interface/can_transport_interface.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 9b27b190..8ba41bc1 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -2,11 +2,10 @@ __all__ = ["AbstractCanTransportInterface", "PyCanTransportInterface"] -import asyncio from abc import abstractmethod from asyncio import AbstractEventLoop, get_running_loop, wait_for from datetime import datetime -from time import sleep, time +from time import time from typing import Any, List, Optional, Tuple from warnings import warn @@ -579,7 +578,7 @@ def _send_cf_packets_block(self, time_end = time() + (delay / 1000.) while time() < time_end: try: - received_packet = self.receive_packet(timeout=(time_end - time())) + received_packet = self.receive_packet(timeout=time_end - time()) except TimeoutError: pass else: @@ -611,7 +610,7 @@ async def _async_send_cf_packets_block(self, time_end = time() + (delay / 1000.) while time() < time_end: try: - received_packet = await self.async_receive_packet(timeout=(time_end - time())) + received_packet = await self.async_receive_packet(timeout=time_end - time()) except TimeoutError: pass else: From f23ef76a224a97bd8ca2b71f49905ace2d46c48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Mon, 23 Sep 2024 19:13:12 +0200 Subject: [PATCH 20/20] improve readability fixes before PR merging --- .../transport_interface/test_can_transport_interface.py | 3 +-- uds/transport_interface/can_transport_interface.py | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/software_tests/transport_interface/test_can_transport_interface.py b/tests/software_tests/transport_interface/test_can_transport_interface.py index 5ada29f8..16ef7859 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -1,4 +1,4 @@ -from random import choice, randint +from random import randint import pytest from mock import AsyncMock, MagicMock, Mock, call, patch @@ -15,7 +15,6 @@ CanPacketRecord, CanPacketType, DefaultFlowControlParametersGenerator, - Message, PyCanTransportInterface, TransmissionDirection, TransmissionInterruptionError, diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 8ba41bc1..df09517a 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -631,8 +631,6 @@ def clear_frames_buffers(self) -> None: for _ in range(self.__frames_buffer.buffer.qsize()): self.__frames_buffer.buffer.get_nowait() for _ in range(self.__async_frames_buffer.buffer.qsize()): - print("ERROR - clearing frames") - print(_) self.__async_frames_buffer.buffer.get_nowait() @staticmethod