diff --git a/.codecov.yml b/.codecov.yml
index 5fbba5e1..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:
@@ -23,6 +23,7 @@ coverage:
flags:
- integration-tests
integration-tests-branch:
+ target: 50%
flags:
- integration-tests-branch
performance-tests:
@@ -33,3 +34,4 @@ comment:
layout: "reach, diff, flags, files"
behavior: default
require_changes: false
+ show_carryforward_flags: true
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
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/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/software_tests/transport_interface/test_can_transport_interface.py b/tests/software_tests/transport_interface/test_can_transport_interface.py
index b6c4d029..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,7 +1,7 @@
from random import 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,14 +10,17 @@
AbstractCanTransportInterface,
AbstractFlowControlParametersGenerator,
BusABC,
+ CanFlowStatus,
CanPacket,
CanPacketRecord,
CanPacketType,
DefaultFlowControlParametersGenerator,
PyCanTransportInterface,
TransmissionDirection,
+ TransmissionInterruptionError,
UdsMessage,
UdsMessageRecord,
+ datetime,
)
SCRIPT_LOCATION = "uds.transport_interface.can_transport_interface"
@@ -76,6 +79,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
@@ -125,6 +130,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
@@ -133,6 +140,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()])
@@ -294,6 +381,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()])
@@ -469,6 +561,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()])
@@ -554,8 +651,12 @@ 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")
+ 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")
@@ -571,7 +672,9 @@ 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()
self._patcher_message.stop()
@@ -590,8 +693,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"),
@@ -611,8 +712,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__
@@ -635,20 +734,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):
@@ -742,6 +827,193 @@ 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_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, 12.34])
+ def test_send_cf_packets_block(self, packets, 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.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_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, 12.34])
+ def test_send_cf_packets_block__packets_traffic(self, packets, 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
+ 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
+ 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_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, 12.34])
+ def test_send_cf_packets_block__transmission_interruption(self, packets, 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
+ 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
+ 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
+
+ @pytest.mark.parametrize("packets", [
+ (Mock(spec=CanPacket), Mock(spec=CanPacket)),
+ (Mock(spec=CanPacket), Mock(spec=CanPacket), Mock(spec=CanPacket)),
+ ])
+ @pytest.mark.parametrize("delay", [0, 12.34])
+ @pytest.mark.asyncio
+ async def test_async_send_cf_packets_block(self, packets, 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_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", [
+ (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__packets_traffic(self, packets, 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
+ 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
+ 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_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", [
+ (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__transmission_interruption(self, packets, 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
+ 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
+ with pytest.raises(TransmissionInterruptionError):
+ 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
@pytest.mark.parametrize("sync_queue_size", [0, 1, 7])
@@ -838,14 +1110,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 +1218,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
@@ -1020,22 +1284,226 @@ 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, ))
+ self.mock_uds_message_record.assert_called_once_with([self.mock_can_transport_interface.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),
+ Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL),
+ ])
+ @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,
+ 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_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_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([
+ self.mock_can_transport_interface.send_packet.return_value,
+ mock_flow_control_record,
+ mock_sent_packet_record
+ ])
+ 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),
+ 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)
+ mock_sent_packet_record = Mock(spec=CanPacketRecord)
+ 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_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:]))
+ ])
+ 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),
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))]
+ 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_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),
+ 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
+ ])
+ 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),
+ 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
+ mock_sent_packet_record = Mock(spec=CanPacketRecord)
+ 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),
+ 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
+ ])
+ 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),
+ 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()
+ 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),
+ 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)
+ 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),
+ 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)
+ self.mock_warn.assert_not_called()
+ self.mock_can_transport_interface._update_n_bs_measured.assert_not_called()
# async_send_message
@@ -1051,19 +1519,232 @@ 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])
+ 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),
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):
- mock_segmented_message = [Mock(spec=CanPacket) for _ in range(randint(2, 20))]
+ 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,
+ 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_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_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)
+ 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
+ ])
+ 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),
+ 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_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_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:]))
+ ])
+ 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),
+ 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_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),
+ 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
+ ])
+ 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),
+ 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_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),
+ 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
+ ])
+ 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),
+ 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()
+ 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),
+ 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)
+ 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),
+ 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)
+ 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 d581f8ef..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
@@ -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:
@@ -24,7 +25,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):
@@ -132,6 +133,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
@@ -180,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):
@@ -212,7 +222,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,
@@ -459,10 +473,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", [
@@ -482,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
@@ -523,6 +547,7 @@ 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
+ # wait till frame arrives
await frame_sending_task
sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.)
@@ -590,15 +615,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)
@@ -677,15 +699,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)
@@ -705,9 +723,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 +746,117 @@ 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
+ # 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
# 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
+
+ @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, 950])
+ def test_send_message__multi_packets(self, example_addressing_information,
+ example_addressing_information_2nd_node,
+ message, send_after):
+ """
+ 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.
+ 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:])
+ # 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 - 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),
+ 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
@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 +878,120 @@ 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
+ # 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
# 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
+
+ @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, 950])
+ @pytest.mark.asyncio
+ async def test_async_send_message__multi_packets(self, example_addressing_information,
+ example_addressing_information_2nd_node,
+ message, send_after):
+ """
+ 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.
+ 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)
+
+ send_frame_task = asyncio.create_task(_send_frame())
+ datetime_before_send = datetime.now()
+ message_record = await can_transport_interface.async_send_message(message)
+ datetime_after_send = datetime.now()
+ await send_frame_task
+ 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:])
+ # 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 - 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),
+ 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)
+
+ send_frame_task = asyncio.create_task(_send_frame())
+ time_before_receive = time()
+ with pytest.raises(TimeoutError):
+ 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 send_frame_task
+ sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.)
# receive_message
@@ -784,7 +1000,7 @@ async def test_async_send_message(self, example_addressing_information, message)
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,
@@ -858,14 +1074,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
@@ -876,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
@@ -907,14 +1120,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", [
@@ -957,26 +1169,19 @@ 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
+ 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
@@ -1057,26 +1262,26 @@ 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),
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, message):
+ example_addressing_information_2nd_node,
+ message):
"""
Check for sending and receiving UDS message using two Transport Interfaces.
@@ -1110,12 +1315,12 @@ 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,
- example_addressing_information_2nd_node, message):
+ example_addressing_information_2nd_node,
+ message):
"""
Check for asynchronous sending and receiving UDS message using two Transport Interfaces.
@@ -1137,15 +1342,18 @@ 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)
# error guessing
@@ -1342,3 +1550,188 @@ 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: 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, 950])
+ 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(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, 950])
+ @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.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)
+
+ 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
+
+ @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", [
+ (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,
+ 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)
+ sleep(self.DELAY_AFTER_RECEIVING_FRAME / 100.)
+
+ @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", [
+ (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,
+ example_addressing_information_2nd_node,
+ message, new_message, fc_after,
+ new_message_after, st_min):
+ """
+ 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 (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 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_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_and_message_task
+ await asyncio.sleep(self.DELAY_AFTER_RECEIVING_FRAME / 100.)
diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py
index 65be7de8..208fbca8 100644
--- a/uds/segmentation/can_segmenter.py
+++ b/uds/segmentation/can_segmenter.py
@@ -12,6 +12,7 @@
CanConsecutiveFrameHandler,
CanDlcHandler,
CanFirstFrameHandler,
+ CanFlowStatus,
CanSingleFrameHandler,
PacketAIParamsAlias,
)
@@ -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,29 @@ 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.
+ """
+ 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]:
"""
Perform desegmentation of CAN packets.
@@ -276,75 +372,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..df09517a 100644
--- a/uds/transport_interface/can_transport_interface.py
+++ b/uds/transport_interface/can_transport_interface.py
@@ -6,7 +6,7 @@
from asyncio import AbstractEventLoop, get_running_loop, wait_for
from datetime import datetime
from time import time
-from typing import Any, Optional
+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
@@ -79,6 +86,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)
@@ -89,6 +98,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."""
@@ -98,13 +141,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.
@@ -124,20 +167,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.
@@ -157,20 +200,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.
@@ -187,18 +230,23 @@ def n_bs_timeout(self, value: TimeMillisecondsAlias):
self.__n_bs_timeout = value
@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 of :ref:`N_Bs ` time parameter.
- :return: Time in milliseconds or None if the value was never measured.
+ .. 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 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.
@@ -208,7 +256,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.
@@ -224,12 +272,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
@@ -237,7 +285,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.
@@ -247,7 +295,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
@@ -266,25 +314,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.
@@ -301,13 +349,18 @@ def n_cr_timeout(self, value: TimeMillisecondsAlias):
self.__n_cr_timeout = value
@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 of :ref:`N_Cr ` time parameter.
- :return: Time in milliseconds or None if the value was never measured.
+ .. 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 could not be measured.
"""
+ return self.__n_cr_measured
# Communication parameters
@@ -423,15 +476,13 @@ 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
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."""
@@ -441,7 +492,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.
"""
@@ -450,30 +501,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[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[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.
@@ -527,6 +560,68 @@ def _setup_async_notifier(self, loop: AbstractEventLoop) -> None:
timeout=self._MIN_NOTIFIER_TIMEOUT,
loop=loop)
+ def _send_cf_packets_block(self,
+ cf_packets_block: List[CanPacket],
+ delay: TimeMillisecondsAlias) -> Tuple[CanPacketRecord, ...]:
+ """
+ Send block of Consecutive Frame CAN packets.
+
+ :param cf_packets_block: Consecutive Frame CAN packets to send.
+ :param delay: Minimal delay between sending following Consecutive Frames [ms].
+
+ :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 cf_packet in cf_packets_block:
+ 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.")
+ warn(message="An unrelated CAN packet was received during UDS message transmission.",
+ category=TransmissionInterruptionWarning)
+ packet_records.append(self.send_packet(cf_packet))
+ return tuple(packet_records)
+
+ async def _async_send_cf_packets_block(self,
+ cf_packets_block: List[CanPacket],
+ delay: TimeMillisecondsAlias,
+ loop: Optional[AbstractEventLoop] = None) -> Tuple[CanPacketRecord, ...]:
+ """
+ Send block of Consecutive Frame CAN packets asynchronously.
+
+ :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.
+
+ :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 cf_packet in cf_packets_block:
+ 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.")
+ warn(message="An unrelated CAN packet was received during UDS message transmission.",
+ category=TransmissionInterruptionWarning)
+ packet_records.append(await self.async_send_packet(cf_packet, loop=loop))
+ return tuple(packet_records)
+
def clear_frames_buffers(self) -> None:
"""
Clear buffers with transmitted and received frames.
@@ -559,7 +654,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
@@ -570,6 +664,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 \
@@ -581,53 +676,19 @@ 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
+ # 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:
- self.__n_as_measured = observed_frame.timestamp - time_start
+ # 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,
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:
@@ -641,7 +702,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
@@ -653,6 +713,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 \
@@ -662,15 +723,57 @@ 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
+ # 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:
- self.__n_as_measured = observed_frame.timestamp - time_start
+ # 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,
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:
@@ -716,20 +819,98 @@ 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 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.
+ """
+ packets_to_send = list(self.segmenter.segmentation(message))
+ packet_records = [self.send_packet(packets_to_send.pop(0))]
+ while packets_to_send:
+ record = self.receive_packet(timeout=self.N_BS_TIMEOUT)
+ 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_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:
+ 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("A new UDS message transmission was started while sending "
+ "this message.")
+ else:
+ warn(message="An unrelated CAN packet was received during UDS message transmission.",
+ category=TransmissionInterruptionWarning)
+ message_records = UdsMessageRecord(packet_records)
+ self._update_n_bs_measured(message_records)
+ return message_records
+
+ async def async_send_message(self,
+ message: UdsMessage,
+ loop: Optional[AbstractEventLoop] = None) -> UdsMessageRecord:
+ """
+ Transmit asynchronously UDS message over CAN.
+
: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.
"""
- 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,))
- raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/267")
+ packets_to_send = list(self.segmenter.segmentation(message))
+ packet_records = [await self.async_send_packet(packets_to_send.pop(0), loop=loop)]
+ while packets_to_send:
+ record = await self.async_receive_packet(timeout=self.N_BS_TIMEOUT, loop=loop)
+ 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_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:
+ 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("A new UDS message transmission was started while sending "
+ "this message.")
+ else:
+ warn(message="An unrelated CAN packet was received during UDS message transmission.",
+ category=TransmissionInterruptionWarning)
+ 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.
@@ -751,23 +932,6 @@ def receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> Ud
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:
- """
- Transmit asynchronously UDS message over CAN.
-
- :param message: A message to send.
- :param loop: An asyncio event loop to use for scheduling this task.
-
- :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")
-
async def async_receive_message(self,
timeout: Optional[TimeMillisecondsAlias] = None,
loop: Optional[AbstractEventLoop] = 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..dc479bd5 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 a received packet shall be ignored.
+ """