From 5e31dfc4bc0e6ab9c61ab3b8c14e164c08dd6046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sun, 6 Feb 2022 10:03:24 +0100 Subject: [PATCH 01/40] change_ti_api Adjust Transport Interface API to asynchronous/synchronous implementation. --- .../test_abstract_transport_interface.py | 12 ++-- .../test_python_can_transport_interface.py | 24 ++++++++ uds/can/addressing_information.py | 3 +- .../abstract_transport_interface.py | 56 +++++++++++++++++-- .../python_can_transport_interface.py | 46 +++++++++++++++ 5 files changed, 127 insertions(+), 14 deletions(-) diff --git a/tests/software_tests/transport_interface/test_abstract_transport_interface.py b/tests/software_tests/transport_interface/test_abstract_transport_interface.py index e187fbcb..bf3aa444 100644 --- a/tests/software_tests/transport_interface/test_abstract_transport_interface.py +++ b/tests/software_tests/transport_interface/test_abstract_transport_interface.py @@ -112,14 +112,14 @@ async def test_await_message_transmitted(self): with pytest.raises(NotImplementedError): await AbstractTransportInterface.await_message_transmitted(self=self.mock_transport_interface) - # send_packet + # schedule_packet - def test_send_packet(self): + def test_schedule_packet(self): with pytest.raises(NotImplementedError): - AbstractTransportInterface.send_packet(self=self.mock_transport_interface, packet=Mock()) + AbstractTransportInterface.schedule_packet(self=self.mock_transport_interface, packet=Mock()) - # send_message + # schedule_message - def test_send_message(self): + def test_schedule_message(self): with pytest.raises(NotImplementedError): - AbstractTransportInterface.send_message(self=self.mock_transport_interface, message=Mock()) + AbstractTransportInterface.schedule_message(self=self.mock_transport_interface, message=Mock()) diff --git a/tests/software_tests/transport_interface/test_python_can_transport_interface.py b/tests/software_tests/transport_interface/test_python_can_transport_interface.py index 3abf02d7..a895a775 100644 --- a/tests/software_tests/transport_interface/test_python_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_python_can_transport_interface.py @@ -103,6 +103,30 @@ def test_is_supported_bus_manager(self, mock_isinstance, value): assert PyCanTransportInterface.is_supported_bus_manager(value) == mock_isinstance.return_value mock_isinstance.assert_called_once_with(value, BusABC) + # send_packet + + def test_send_packet(self): + with pytest.raises(NotImplementedError): + PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=Mock()) + + # send_message + + def test_send_message(self): + with pytest.raises(NotImplementedError): + PyCanTransportInterface.send_message(self=self.mock_can_transport_interface, message=Mock()) + + # receive_packet + + def test_receive_packet(self): + with pytest.raises(NotImplementedError): + PyCanTransportInterface.receive_packet(self=self.mock_can_transport_interface, timeout=Mock()) + + # receive_message + + def test_receive_message(self): + with pytest.raises(NotImplementedError): + PyCanTransportInterface.receive_message(self=self.mock_can_transport_interface, timeout=Mock()) + class TestPyCanTransportInterfaceIntegration: """Integration tests for `PyCanTransportInterface` class.""" diff --git a/uds/can/addressing_information.py b/uds/can/addressing_information.py index 394f24d3..0d330afe 100644 --- a/uds/can/addressing_information.py +++ b/uds/can/addressing_information.py @@ -8,8 +8,7 @@ from typing import Optional, Dict, TypedDict, Type -from uds.utilities import RawBytes, RawBytesList, validate_raw_byte, validate_raw_bytes, \ - InconsistentArgumentsError +from uds.utilities import RawBytes, RawBytesList, validate_raw_byte, validate_raw_bytes, InconsistentArgumentsError from uds.transmission_attributes import AddressingTypeAlias from .addressing_format import CanAddressingFormat, CanAddressingFormatAlias from .frame_fields import CanIdHandler diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index 2ef0b0cc..95f9be33 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -181,28 +181,72 @@ async def await_message_transmitted(self, """ raise NotImplementedError - def send_packet(self, packet: AbstractUdsPacket, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 + def schedule_packet(self, packet: AbstractUdsPacket, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 """ - Transmit UDS packet on the configured bus. + Schedule UDS packet transmission. :param packet: A packet to send. :param delay: Value of a delay (in milliseconds) if the transmission to be scheduled in the future. - None if the transmission to be executed immediately. + None if the transmission to be scheduled immediately. :raise TypeError: Delay value is not int or float type. :raise ValueError: Delay value is less or equal 0. """ raise NotImplementedError - def send_message(self, message: UdsMessage, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 + def schedule_message(self, message: UdsMessage, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 """ - Transmit UDS message on the configured bus. + Schedule UDS message transmissions. :param message: A message to send. :param delay: Value of a delay (in milliseconds) if the transmission to be scheduled in the future. - None if the transmission to be executed immediately. + None if the transmission to be scheduled immediately. :raise TypeError: Delay value is not int or float type. :raise ValueError: Delay value is less or equal 0. """ raise NotImplementedError + + @abstractmethod + def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: + """ + Transmit UDS packet. + + :param packet: A packet to send. + + :return: Record with historic information about transmitted UDS packet. + """ + + @abstractmethod + def send_message(self, message: UdsMessage) -> UdsMessageRecord: + """ + Transmit UDS message. + + :param message: A message to send. + + :return: Record with historic information about transmitted UDS message. + """ + + @abstractmethod + def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> AbstractUdsPacketRecord: + """ + Receive UDS packet. + + :param timeout: Maximal time (in milliseconds) to wait. + + :raise TimeoutError: Timeout was reached. + + :return: Record with historic information about received UDS packet. + """ + + @abstractmethod + def receive_message(self, timeout: Optional[TimeMilliseconds]) -> UdsMessageRecord: + """ + Receive UDS message. + + :param timeout: Maximal time (in milliseconds) to wait. + + :raise TimeoutError: Timeout was reached. + + :return: Record with historic information about received UDS message. + """ diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index dd475268..2a0eb8ec 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -12,6 +12,8 @@ from uds.utilities import TimeMilliseconds from uds.can import AbstractCanAddressingInformation +from uds.packet import CanPacket, CanPacketRecord +from uds.message import UdsMessage, UdsMessageRecord from .abstract_transport_interface import AbstractTransportInterface from .abstract_can_transport_interface import AbstractCanTransportInterface @@ -101,3 +103,47 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: :return: True if provided bus object is compatible with this Transport Interface, False otherwise. """ return isinstance(bus_manager, BusABC) + + def send_packet(self, packet: CanPacket) -> CanPacketRecord: + """ + Transmit UDS packet. + + :param packet: A packet to send. + + :return: Record with historic information about transmitted UDS packet. + """ + raise NotImplementedError + + def send_message(self, message: UdsMessage) -> UdsMessageRecord: + """ + Transmit UDS message. + + :param message: A message to send. + + :return: Record with historic information about transmitted UDS message. + """ + raise NotImplementedError + + def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> CanPacketRecord: + """ + Receive UDS packet. + + :param timeout: Maximal time (in milliseconds) to wait. + + :raise TimeoutError: Timeout was reached. + + :return: Record with historic information about received UDS packet. + """ + raise NotImplementedError + + def receive_message(self, timeout: Optional[TimeMilliseconds]) -> UdsMessageRecord: + """ + Receive UDS message. + + :param timeout: Maximal time (in milliseconds) to wait. + + :raise TimeoutError: Timeout was reached. + + :return: Record with historic information about received UDS message. + """ + raise NotImplementedError From 633cad773a6443a5679f61e75e0d9f750f7582b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= <51504507+mdabrowski1990@users.noreply.github.com> Date: Mon, 7 Feb 2022 16:15:44 +0100 Subject: [PATCH 02/40] notes Add notes what has to be done by each method. --- .../test_python_can_transport_interface.py | 12 ++++++------ .../python_can_transport_interface.py | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/software_tests/transport_interface/test_python_can_transport_interface.py b/tests/software_tests/transport_interface/test_python_can_transport_interface.py index a895a775..08c13a01 100644 --- a/tests/software_tests/transport_interface/test_python_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_python_can_transport_interface.py @@ -105,9 +105,9 @@ def test_is_supported_bus_manager(self, mock_isinstance, value): # send_packet - def test_send_packet(self): - with pytest.raises(NotImplementedError): - PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=Mock()) + # def test_send_packet(self): + # with pytest.raises(NotImplementedError): + # PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=Mock()) # send_message @@ -117,9 +117,9 @@ def test_send_message(self): # receive_packet - def test_receive_packet(self): - with pytest.raises(NotImplementedError): - PyCanTransportInterface.receive_packet(self=self.mock_can_transport_interface, timeout=Mock()) + # def test_receive_packet(self): + # with pytest.raises(NotImplementedError): + # PyCanTransportInterface.receive_packet(self=self.mock_can_transport_interface, timeout=Mock()) # receive_message diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index 2a0eb8ec..27d9506d 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -112,7 +112,14 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: :return: Record with historic information about transmitted UDS packet. """ - raise NotImplementedError + # TODO: + # - check packet type - TypeError + # - make sure packet uses proper AddressingInformation - Warning + # - measure N_AS / N_AR + # - use CAN Bus to transmit packet + # - get confirmation from Bus Listener that the packet was received + # - make sure that `_packet_records_queue` (and `_message_records_queue' if needed) is updated + # - return record of transmitted packet def send_message(self, message: UdsMessage) -> UdsMessageRecord: """ @@ -134,7 +141,12 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> CanPacketRecord :return: Record with historic information about received UDS packet. """ - raise NotImplementedError + # TODO: + # - update `_packet_records_queue` + # - measure N_As / N_Ar + # - use Bus Listener to filter out UDS Packets that targets this entity + # - make sure that `_packet_records_queue` (and `_message_records_queue' if needed) is updated + # - return record of received packet when received def receive_message(self, timeout: Optional[TimeMilliseconds]) -> UdsMessageRecord: """ From b52bcfd89e1719d0a4f95071a63922a2a65b760d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= <51504507+mdabrowski1990@users.noreply.github.com> Date: Thu, 10 Feb 2022 16:13:57 +0100 Subject: [PATCH 03/40] save --- .../test_python_can_transport_interface.py | 57 ++++++++++++++++--- .../python_can_transport_interface.py | 18 +++++- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/tests/software_tests/transport_interface/test_python_can_transport_interface.py b/tests/software_tests/transport_interface/test_python_can_transport_interface.py index 08c13a01..c22c2d0f 100644 --- a/tests/software_tests/transport_interface/test_python_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_python_can_transport_interface.py @@ -1,8 +1,8 @@ import pytest -from mock import Mock, patch +from mock import MagicMock, Mock, patch from uds.transport_interface.python_can_transport_interface import PyCanTransportInterface, \ - BusABC, AbstractTransportInterface + BusABC, AbstractTransportInterface, CanPacket from uds.can import CanAddressingInformation, CanAddressingFormat @@ -16,9 +16,12 @@ def setup(self): # patching self._patcher_abstract_can_ti_init = patch(f"{self.SCRIPT_LOCATION}.AbstractCanTransportInterface.__init__") self.mock_abstract_can_ti_init = self._patcher_abstract_can_ti_init.start() + self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") + self.mock_warn = self._patcher_warn.start() def teardown(self): self._patcher_abstract_can_ti_init.stop() + self._patcher_warn.stop() # __init__ @@ -105,9 +108,31 @@ def test_is_supported_bus_manager(self, mock_isinstance, value): # send_packet - # def test_send_packet(self): - # with pytest.raises(NotImplementedError): - # PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=Mock()) + @pytest.mark.parametrize("packet", ["some packet", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + def test_send_packet__type_error(self, mock_isinstance, packet): + mock_isinstance.return_value = False + with pytest.raises(TypeError): + PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=packet) + mock_isinstance.assert_called_once_with(packet, CanPacket) + + # TODO: finish more cases + + # @pytest.mark.parametrize("packet", [Mock()]) + # @patch(f"{SCRIPT_LOCATION}.isinstance") + # def test_send_packet__with_warning(self, mock_isinstance, packet): + # mock_isinstance.return_value = True + # PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=packet) + # mock_isinstance.assert_called_once_with(packet, CanPacket) + # self.mock_warn.assert_called_once() + # + # @pytest.mark.parametrize("packet", [Mock()]) + # @patch(f"{SCRIPT_LOCATION}.isinstance") + # def test_send_packet__without_warning(self, mock_isinstance, packet): + # mock_isinstance.return_value = True + # PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=packet) + # mock_isinstance.assert_called_once_with(packet, CanPacket) + # self.mock_warn.assert_not_called() # send_message @@ -117,9 +142,25 @@ def test_send_message(self): # receive_packet - # def test_receive_packet(self): - # with pytest.raises(NotImplementedError): - # PyCanTransportInterface.receive_packet(self=self.mock_can_transport_interface, timeout=Mock()) + @pytest.mark.parametrize("timeout", ["some timeout", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + def test_receive_packet__type_error(self, mock_isinstance, timeout): + mock_isinstance.return_value = False + with pytest.raises(TypeError): + PyCanTransportInterface.receive_packet(self=self.mock_can_transport_interface, timeout=timeout) + mock_isinstance.assert_called_once_with(timeout, (int, float)) + + @patch(f"{SCRIPT_LOCATION}.isinstance") + def test_receive_packet__value_error(self, mock_isinstance): + mock_le = Mock(return_value=True) + mock_timeout = MagicMock(__le__=mock_le) + mock_isinstance.return_value = True + with pytest.raises(ValueError): + PyCanTransportInterface.receive_packet(self=self.mock_can_transport_interface, timeout=mock_timeout) + mock_isinstance.assert_called_once_with(mock_timeout, (int, float)) + mock_le.assert_called_once_with(0) + + # TODO: more cases # receive_message diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index 27d9506d..290346bc 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -7,10 +7,11 @@ __all__ = ["PyCanTransportInterface"] from typing import Optional, Any +from warnings import warn -from can import BusABC +from can import BusABC, AsyncBufferedReader -from uds.utilities import TimeMilliseconds +from uds.utilities import TimeMilliseconds, ValueWarning from uds.can import AbstractCanAddressingInformation from uds.packet import CanPacket, CanPacketRecord from uds.message import UdsMessage, UdsMessageRecord @@ -110,10 +111,14 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: :param packet: A packet to send. + :raise TypeError: Provided packet value does not contain CAN Packet. + :return: Record with historic information about transmitted UDS packet. """ + if not isinstance(packet, CanPacket): + raise TypeError("Provided packet value does not contain CAN Packet.") + # TODO: - # - check packet type - TypeError # - make sure packet uses proper AddressingInformation - Warning # - measure N_AS / N_AR # - use CAN Bus to transmit packet @@ -137,10 +142,17 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> CanPacketRecord :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 UDS packet. """ + 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.") # TODO: # - update `_packet_records_queue` # - measure N_As / N_Ar From 2179927654bdd307bf154abff3f1f65742c2fd6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= <51504507+mdabrowski1990@users.noreply.github.com> Date: Fri, 18 Feb 2022 15:42:14 +0100 Subject: [PATCH 04/40] save just a few minor changes --- .../test_python_can_transport_interface.py | 11 +- .../abstract_transport_interface.py | 225 +++++++++--------- .../python_can_transport_interface.py | 3 +- 3 files changed, 121 insertions(+), 118 deletions(-) diff --git a/tests/software_tests/transport_interface/test_python_can_transport_interface.py b/tests/software_tests/transport_interface/test_python_can_transport_interface.py index c22c2d0f..44bb8151 100644 --- a/tests/software_tests/transport_interface/test_python_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_python_can_transport_interface.py @@ -59,12 +59,11 @@ def test_init__all_args(self, can_bus_manager, addressing_information, packet_records_number=packet_records_number, message_records_number=message_records_number, **kwargs) - self.mock_abstract_can_ti_init.assert_called_once_with( - can_bus_manager=can_bus_manager, - addressing_information=addressing_information, - packet_records_number=packet_records_number, - message_records_number=message_records_number, - **kwargs) + self.mock_abstract_can_ti_init.assert_called_once_with(an_bus_manager=can_bus_manager, + addressing_information=addressing_information, + packet_records_number=packet_records_number, + message_records_number=message_records_number, + **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 diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index 95f9be33..2d16c8aa 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -84,7 +84,12 @@ def packet_records_history(self) -> Tuple[AbstractUdsPacketRecord]: @property @abstractmethod def segmenter(self) -> AbstractSegmenter: - """Value of the segmenter used by this Transport Interface.""" + """ + Value of the segmenter used by this Transport Interface. + + .. warning:: Do not change any segmenter attributes as it might cause malfunction of the entire + Transport Interface. + """ @staticmethod @abstractmethod @@ -97,115 +102,115 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: :return: True if provided bus object is compatible with this Transport Interface, False otherwise. """ - async def await_packet_received(self, - timeout: Optional[TimeMilliseconds] = None, # noqa: F841 - ignore_interruptions: bool = False) -> AbstractUdsPacketRecord: # noqa: F841 - """ - Wait until the next UDS packet is received. - - :param timeout: Maximal time (in milliseconds) to wait. - :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was transmitted. - - - True - ignore transmitted UDS packets and do not raise InterruptedError - - False - raise InterruptedError if UDS packet is transmitted when awaiting - - :raise TypeError: Timeout value is not int or float type. - :raise ValueError: Timeout value is less or equal 0. - :raise TimeoutError: Timeout was reached. - :raise InterruptedError: UDS packet was transmitted during awaiting. - - :return: Record with historic information of a packet that was just received. - """ - raise NotImplementedError - - async def await_packet_transmitted(self, - timeout: Optional[TimeMilliseconds] = None, # noqa: F841 - ignore_interruptions: bool = False) -> AbstractUdsPacketRecord: # noqa: F841 - """ - Wait until the next UDS packet is transmitted. - - :param timeout: Maximal time (in milliseconds) to wait. - :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was received. - - - True - ignore received UDS packets and do not raise InterruptedError - - False - raise InterruptedError if UDS packet is received when awaiting - - :raise TypeError: Timeout value is not int or float type. - :raise ValueError: Timeout value is less or equal 0. - :raise TimeoutError: Timeout was reached. - :raise InterruptedError: UDS packet was received during awaiting. - - :return: Record with historic information of a packet that was just transmitted. - """ - raise NotImplementedError - - async def await_message_received(self, - timeout: Optional[TimeMilliseconds] = None, # noqa: F841 - ignore_interruptions: bool = False) -> UdsMessageRecord: # noqa: F841 - """ - Wait until the next UDS message is received. - - :param timeout: Maximal time (in milliseconds) to wait. - :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was transmitted. - - - True - ignore transmitted UDS packets and do not raise InterruptedError - - False - raise InterruptedError if UDS packet is transmitted when awaiting - - :raise TypeError: Timeout value is not int or float type. - :raise ValueError: Timeout value is less or equal 0. - :raise TimeoutError: Timeout was reached. - :raise InterruptedError: UDS packet was transmitted during awaiting. - - :return: Record with historic information of a message that was just received. - """ - raise NotImplementedError - - async def await_message_transmitted(self, - timeout: Optional[TimeMilliseconds] = None, # noqa: F841 - ignore_interruptions: bool = False) -> UdsMessageRecord: # noqa: F841 - """ - Wait until the next UDS message is transmitted. - - :param timeout: Maximal time (in milliseconds) to wait. - :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was received. - - - True - ignore received UDS packets and do not raise InterruptedError - - False - raise InterruptedError if UDS packet is received when awaiting - - :raise TypeError: Timeout value is not int or float type. - :raise ValueError: Timeout value is less or equal 0. - :raise TimeoutError: Timeout was reached. - :raise InterruptedError: UDS packet was received during awaiting. - - :return: Record with historic information of a message that was just transmitted. - """ - raise NotImplementedError - - def schedule_packet(self, packet: AbstractUdsPacket, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 - """ - Schedule UDS packet transmission. - - :param packet: A packet to send. - :param delay: Value of a delay (in milliseconds) if the transmission to be scheduled in the future. - None if the transmission to be scheduled immediately. - - :raise TypeError: Delay value is not int or float type. - :raise ValueError: Delay value is less or equal 0. - """ - raise NotImplementedError - - def schedule_message(self, message: UdsMessage, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 - """ - Schedule UDS message transmissions. - - :param message: A message to send. - :param delay: Value of a delay (in milliseconds) if the transmission to be scheduled in the future. - None if the transmission to be scheduled immediately. - - :raise TypeError: Delay value is not int or float type. - :raise ValueError: Delay value is less or equal 0. - """ - raise NotImplementedError + # async def await_packet_received(self, + # timeout: Optional[TimeMilliseconds] = None, # noqa: F841 + # ignore_interruptions: bool = False) -> AbstractUdsPacketRecord: # noqa: F841 + # """ + # Wait until the next UDS packet is received. + # + # :param timeout: Maximal time (in milliseconds) to wait. + # :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was transmitted. + # + # - True - ignore transmitted UDS packets and do not raise InterruptedError + # - False - raise InterruptedError if UDS packet is transmitted when awaiting + # + # :raise TypeError: Timeout value is not int or float type. + # :raise ValueError: Timeout value is less or equal 0. + # :raise TimeoutError: Timeout was reached. + # :raise InterruptedError: UDS packet was transmitted during awaiting. + # + # :return: Record with historic information of a packet that was just received. + # """ + # raise NotImplementedError + # + # async def await_packet_transmitted(self, + # timeout: Optional[TimeMilliseconds] = None, # noqa: F841 + # ignore_interruptions: bool = False) -> AbstractUdsPacketRecord: # noqa: F841 + # """ + # Wait until the next UDS packet is transmitted. + # + # :param timeout: Maximal time (in milliseconds) to wait. + # :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was received. + # + # - True - ignore received UDS packets and do not raise InterruptedError + # - False - raise InterruptedError if UDS packet is received when awaiting + # + # :raise TypeError: Timeout value is not int or float type. + # :raise ValueError: Timeout value is less or equal 0. + # :raise TimeoutError: Timeout was reached. + # :raise InterruptedError: UDS packet was received during awaiting. + # + # :return: Record with historic information of a packet that was just transmitted. + # """ + # raise NotImplementedError + # + # async def await_message_received(self, + # timeout: Optional[TimeMilliseconds] = None, # noqa: F841 + # ignore_interruptions: bool = False) -> UdsMessageRecord: # noqa: F841 + # """ + # Wait until the next UDS message is received. + # + # :param timeout: Maximal time (in milliseconds) to wait. + # :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was transmitted. + # + # - True - ignore transmitted UDS packets and do not raise InterruptedError + # - False - raise InterruptedError if UDS packet is transmitted when awaiting + # + # :raise TypeError: Timeout value is not int or float type. + # :raise ValueError: Timeout value is less or equal 0. + # :raise TimeoutError: Timeout was reached. + # :raise InterruptedError: UDS packet was transmitted during awaiting. + # + # :return: Record with historic information of a message that was just received. + # """ + # raise NotImplementedError + # + # async def await_message_transmitted(self, + # timeout: Optional[TimeMilliseconds] = None, # noqa: F841 + # ignore_interruptions: bool = False) -> UdsMessageRecord: # noqa: F841 + # """ + # Wait until the next UDS message is transmitted. + # + # :param timeout: Maximal time (in milliseconds) to wait. + # :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was received. + # + # - True - ignore received UDS packets and do not raise InterruptedError + # - False - raise InterruptedError if UDS packet is received when awaiting + # + # :raise TypeError: Timeout value is not int or float type. + # :raise ValueError: Timeout value is less or equal 0. + # :raise TimeoutError: Timeout was reached. + # :raise InterruptedError: UDS packet was received during awaiting. + # + # :return: Record with historic information of a message that was just transmitted. + # """ + # raise NotImplementedError + # + # def schedule_packet(self, packet: AbstractUdsPacket, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 + # """ + # Schedule UDS packet transmission. + # + # :param packet: A packet to send. + # :param delay: Value of a delay (in milliseconds) if the transmission to be scheduled in the future. + # None if the transmission to be scheduled immediately. + # + # :raise TypeError: Delay value is not int or float type. + # :raise ValueError: Delay value is less or equal 0. + # """ + # raise NotImplementedError + # + # def schedule_message(self, message: UdsMessage, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 + # """ + # Schedule UDS message transmissions. + # + # :param message: A message to send. + # :param delay: Value of a delay (in milliseconds) if the transmission to be scheduled in the future. + # None if the transmission to be scheduled immediately. + # + # :raise TypeError: Delay value is not int or float type. + # :raise ValueError: Delay value is less or equal 0. + # """ + # raise NotImplementedError @abstractmethod def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index 290346bc..c5feadb0 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -9,7 +9,7 @@ from typing import Optional, Any from warnings import warn -from can import BusABC, AsyncBufferedReader +from can import BusABC, AsyncBufferedReader, Notifier from uds.utilities import TimeMilliseconds, ValueWarning from uds.can import AbstractCanAddressingInformation @@ -117,7 +117,6 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: """ if not isinstance(packet, CanPacket): raise TypeError("Provided packet value does not contain CAN Packet.") - # TODO: # - make sure packet uses proper AddressingInformation - Warning # - measure N_AS / N_AR From ee4adc2864f2067498b30b9c30b02981e13dde23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Sat, 4 Feb 2023 12:31:11 +0100 Subject: [PATCH 05/40] queue performance Add performance tests for transmission queue. --- .../test_transmission_queue.py | 25 +++++++++++++++++++ uds/transport_interface/transmission_queue.py | 1 + 2 files changed, 26 insertions(+) diff --git a/tests/software_tests/transport_interface/test_transmission_queue.py b/tests/software_tests/transport_interface/test_transmission_queue.py index 3f17bb39..1c630c19 100644 --- a/tests/software_tests/transport_interface/test_transmission_queue.py +++ b/tests/software_tests/transport_interface/test_transmission_queue.py @@ -1,6 +1,7 @@ import pytest from mock import AsyncMock, MagicMock, Mock, patch, call from copy import deepcopy +from time import perf_counter from uds.transport_interface.transmission_queue import TransmissionQueue, \ UdsMessage, AbstractUdsPacket, QueueEmpty, Event, AsyncioTimeoutError, PriorityQueue @@ -243,3 +244,27 @@ def test_put_pdu__with_timestamp(self, mock_isinstance, pdu, timestamp): self.mock_transmission_queue._TransmissionQueue__async_queue.put_nowait.assert_called_once_with((timestamp, pdu)) self.mock_transmission_queue._TransmissionQueue__timestamps.add.assert_called_once_with(timestamp) self.mock_transmission_queue._TransmissionQueue__event_pdu_added.set.assert_called_once_with() + + +@pytest.mark.performance +class TestTransmissionQueuePerformance: + + def setup(self): + self.transmission_queue = TransmissionQueue(AbstractUdsPacket) + + @pytest.mark.asyncio + @pytest.mark.skip("Sometimes fails due to issues with async timing - TO BE IMPROVED") + @pytest.mark.parametrize("delays", [ + (0.1, 0.2, 0.3, 0.4), + (0.19, 0.01, 0.35, 0.3), + (0.01, 0.02, 0.8, 0.05, 0.03, 0.07, 0.065, 0.015, 0.7, 0.123456)]) + async def test_put_and_get_pdu(self, delays): + pdu_list = [Mock(spec=AbstractUdsPacket) for _ in delays] + pdu_with_delays = list(zip(pdu_list, [perf_counter() + delay for delay in delays])) + for pdu, timestamp in pdu_with_delays: + self.transmission_queue.put_pdu(pdu=pdu, timestamp=timestamp) + for expected_pdu, expected_timestamp in sorted(pdu_with_delays, key=lambda pair: pair[1]): + assert await self.transmission_queue.get_pdu() == expected_pdu + out_time = perf_counter() + assert out_time >= expected_timestamp + print(expected_timestamp, out_time, out_time - expected_timestamp) diff --git a/uds/transport_interface/transmission_queue.py b/uds/transport_interface/transmission_queue.py index 8839e944..b4e1110c 100644 --- a/uds/transport_interface/transmission_queue.py +++ b/uds/transport_interface/transmission_queue.py @@ -88,6 +88,7 @@ def clear(self) -> None: else: self.mark_pdu_sent() + # TODO: improve to have better performance (currently up to 15 ms) async def get_pdu(self) -> PDUAlias: # pylint: disable=undefined-variable """ Get the next PDU from the queue. From bd6383aca12bf7da1bdb190294f6f2f43a56f319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Mon, 10 Apr 2023 12:43:30 +0200 Subject: [PATCH 06/40] Create kvaser.py Notifier + Listener example :D --- examples/kvaser.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 examples/kvaser.py diff --git a/examples/kvaser.py b/examples/kvaser.py new file mode 100644 index 00000000..240ac101 --- /dev/null +++ b/examples/kvaser.py @@ -0,0 +1,22 @@ +import can + +bus1 = can.Bus(interface="kvaser", channel=0, fd=True) +bus2 = can.Bus(interface="kvaser", channel=1, fd=True) +listener = can.BufferedReader() +notifier = can.Notifier(bus=bus1, listeners=[listener]) + + +msg1 = can.Message(arbitration_id=0x123456, + dlc=64, + is_fd=True, + data=8*[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]) +msg2 = can.Message(arbitration_id=0x7FF, + dlc=8, + data=[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF][::-1]) + +bus1.send(msg=msg1) +bus1.send(msg=msg1) +bus2.send(msg=msg2) + +print(listener.get_message()) +print(listener.get_message()) From f9fee3c95bd2b6d23d8bb40819e4978b9be8d0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Mon, 10 Apr 2023 13:06:00 +0200 Subject: [PATCH 07/40] make it simple Simplify the task and get rid of unnecessary implementation. --- .../test_abstract_can_transport_interface.py | 139 +-------- .../test_abstract_transport_interface.py | 92 +----- .../test_python_can_transport_interface.py | 86 +----- .../transport_interface/test_records_queue.py | 184 ------------ .../test_transmission_queue.py | 273 ------------------ .../abstract_can_transport_interface.py | 93 +----- .../abstract_transport_interface.py | 184 +----------- .../python_can_transport_interface.py | 49 +--- uds/transport_interface/records_queue.py | 99 ------- uds/transport_interface/transmission_queue.py | 127 -------- 10 files changed, 46 insertions(+), 1280 deletions(-) delete mode 100644 tests/software_tests/transport_interface/test_records_queue.py delete mode 100644 tests/software_tests/transport_interface/test_transmission_queue.py delete mode 100644 uds/transport_interface/records_queue.py delete mode 100644 uds/transport_interface/transmission_queue.py diff --git a/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py b/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py index 6a100b34..dea31b9c 100644 --- a/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py @@ -2,7 +2,7 @@ from mock import MagicMock, Mock, patch from uds.transport_interface.abstract_can_transport_interface import AbstractCanTransportInterface, \ - AbstractTransportInterface, AbstractCanAddressingInformation, CanPacket, CanPacketRecord, Iterable + AbstractCanAddressingInformation class TestAbstractCanTransportInterface: @@ -11,16 +11,11 @@ class TestAbstractCanTransportInterface: SCRIPT_LOCATION = "uds.transport_interface.abstract_can_transport_interface" def setup(self): - self.mock_can_transport_interface = Mock(spec=AbstractCanTransportInterface, - DEFAULT_FLOW_CONTROL_ARGS=AbstractCanTransportInterface.DEFAULT_FLOW_CONTROL_ARGS) + self.mock_can_transport_interface = Mock(spec=AbstractCanTransportInterface) # patching self._patcher_abstract_transport_interface_init \ = patch(f"{self.SCRIPT_LOCATION}.AbstractTransportInterface.__init__") self.mock_abstract_transport_interface_init = self._patcher_abstract_transport_interface_init.start() - self._patcher_records_queue_class = patch(f"{self.SCRIPT_LOCATION}.RecordsQueue") - self.mock_records_queue_class = self._patcher_records_queue_class.start() - self._patcher_transmission_queue_class = patch(f"{self.SCRIPT_LOCATION}.TransmissionQueue") - self.mock_transmission_queue_class = self._patcher_transmission_queue_class.start() self._patcher_can_segmenter_class = patch(f"{self.SCRIPT_LOCATION}.CanSegmenter") self.mock_can_segmenter_class = self._patcher_can_segmenter_class.start() self._patcher_can_packet_class = patch(f"{self.SCRIPT_LOCATION}.CanPacket") @@ -30,8 +25,6 @@ def setup(self): def teardown(self): self._patcher_abstract_transport_interface_init.stop() - self._patcher_records_queue_class.stop() - self._patcher_transmission_queue_class.stop() self._patcher_can_segmenter_class.stop() self._patcher_can_packet_class.stop() self._patcher_warn.stop() @@ -65,31 +58,15 @@ def test_init__valid_mandatory_args(self, mock_isinstance, can_bus_manager=can_bus_manager, addressing_information=addressing_information) mock_isinstance.assert_called_once_with(addressing_information, AbstractCanAddressingInformation) - self.mock_abstract_transport_interface_init.assert_called_once_with( - bus_manager=can_bus_manager, - message_records_number=AbstractTransportInterface.DEFAULT_MESSAGE_RECORDS_NUMBER) + self.mock_abstract_transport_interface_init.assert_called_once_with(bus_manager=can_bus_manager) self.mock_can_segmenter_class.assert_called_once_with( addressing_format=addressing_information.addressing_format, physical_ai=addressing_information.tx_packets_physical_ai, functional_ai=addressing_information.tx_packets_functional_ai) - self.mock_records_queue_class.assert_called_once_with( - records_type=CanPacketRecord, - history_size=AbstractTransportInterface.DEFAULT_PACKET_RECORDS_NUMBER) - self.mock_transmission_queue_class.assert_called_once_with(pdu_type=self.mock_can_packet_class) - self.mock_can_packet_class.assert_called_once_with( - dlc=None if self.mock_can_transport_interface.use_data_optimization else self.mock_can_transport_interface.dlc, - filler_byte=self.mock_can_transport_interface.filler_byte, - **addressing_information.tx_packets_physical_ai, - **self.mock_can_transport_interface.DEFAULT_FLOW_CONTROL_ARGS) assert self.mock_can_transport_interface._AbstractCanTransportInterface__addressing_information \ == addressing_information assert self.mock_can_transport_interface._AbstractCanTransportInterface__segmenter \ == self.mock_can_segmenter_class.return_value - assert self.mock_can_transport_interface._AbstractCanTransportInterface__packet_records_queue \ - == self.mock_records_queue_class.return_value - assert self.mock_can_transport_interface._AbstractCanTransportInterface__packet_transmission_queue \ - == self.mock_transmission_queue_class.return_value - assert self.mock_can_transport_interface.flow_control_generator == self.mock_can_packet_class.return_value 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 @@ -103,23 +80,19 @@ def test_init__valid_mandatory_args(self, mock_isinstance, (Mock(), Mock(tx_packets_physical_ai={"arg1": 1, "arg2": 2})), ]) @pytest.mark.parametrize("n_as_timeout, n_ar_timeout, n_bs_timeout, n_br, n_cs, n_cr_timeout, " - "dlc, use_data_optimization, filler_byte, flow_control_generator, " - "packet_records_number, message_records_number", [ + "dlc, use_data_optimization, filler_byte", [ ("n_as_timeout", "n_ar_timeout", "n_bs_timeout", "n_br", "n_cs", "n_cr_timeout", "dlc", "use_data_optimization", - "filler_byte", "flow_control_generator", "packet_records_number", "message_records_number"), - (Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock()), + "filler_byte"), + (Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock()), ]) @patch(f"{SCRIPT_LOCATION}.isinstance") def test_init__valid_all_args(self, mock_isinstance, can_bus_manager, addressing_information, n_as_timeout, n_ar_timeout, n_bs_timeout, n_br, n_cs, n_cr_timeout, - dlc, use_data_optimization, filler_byte, flow_control_generator, - packet_records_number, message_records_number): + dlc, use_data_optimization, filler_byte): mock_isinstance.return_value = True AbstractCanTransportInterface.__init__(self=self.mock_can_transport_interface, can_bus_manager=can_bus_manager, - packet_records_number=packet_records_number, - message_records_number=message_records_number, addressing_information=addressing_information, n_as_timeout=n_as_timeout, n_ar_timeout=n_ar_timeout, @@ -129,12 +102,9 @@ def test_init__valid_all_args(self, mock_isinstance, n_cr_timeout=n_cr_timeout, dlc=dlc, use_data_optimization=use_data_optimization, - filler_byte=filler_byte, - flow_control_generator=flow_control_generator) + filler_byte=filler_byte) mock_isinstance.assert_called_once_with(addressing_information, AbstractCanAddressingInformation) - self.mock_abstract_transport_interface_init.assert_called_once_with( - bus_manager=can_bus_manager, - message_records_number=message_records_number) + self.mock_abstract_transport_interface_init.assert_called_once_with(bus_manager=can_bus_manager) self.mock_can_segmenter_class.assert_called_once_with( addressing_format=addressing_information.addressing_format, physical_ai=addressing_information.tx_packets_physical_ai, @@ -142,19 +112,10 @@ def test_init__valid_all_args(self, mock_isinstance, dlc=dlc, use_data_optimization=use_data_optimization, filler_byte=filler_byte) - self.mock_records_queue_class.assert_called_once_with(records_type=CanPacketRecord, - history_size=packet_records_number) - self.mock_transmission_queue_class.assert_called_once_with(pdu_type=self.mock_can_packet_class) - self.mock_can_packet_class.assert_not_called() assert self.mock_can_transport_interface._AbstractCanTransportInterface__addressing_information \ == addressing_information assert self.mock_can_transport_interface._AbstractCanTransportInterface__segmenter \ == self.mock_can_segmenter_class.return_value - assert self.mock_can_transport_interface._AbstractCanTransportInterface__packet_records_queue \ - == self.mock_records_queue_class.return_value - assert self.mock_can_transport_interface._AbstractCanTransportInterface__packet_transmission_queue \ - == self.mock_transmission_queue_class.return_value - assert self.mock_can_transport_interface.flow_control_generator == flow_control_generator 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 @@ -162,20 +123,6 @@ def test_init__valid_all_args(self, mock_isinstance, assert self.mock_can_transport_interface.n_cs == n_cs assert self.mock_can_transport_interface.n_cr_timeout == n_cr_timeout - # _packet_records_queue - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_packet_records_queue__get(self, value): - self.mock_can_transport_interface._AbstractCanTransportInterface__packet_records_queue = value - assert AbstractCanTransportInterface._packet_records_queue.fget(self.mock_can_transport_interface) == value - - # _packet_transmission_queue - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_packet_transmission_queue__get(self, value): - self.mock_can_transport_interface._AbstractCanTransportInterface__packet_transmission_queue = value - assert AbstractCanTransportInterface._packet_transmission_queue.fget(self.mock_can_transport_interface) == value - # segmenter @pytest.mark.parametrize("value", ["something", Mock()]) @@ -552,71 +499,3 @@ def test_filler_byte__get(self): def test_filler_byte__set(self, value): AbstractCanTransportInterface.filler_byte.fset(self.mock_can_transport_interface, value) assert self.mock_can_transport_interface.segmenter.filler_byte == value - - # flow_control_generator - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_flow_control_generator__get(self, value): - self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_generator = value - assert AbstractCanTransportInterface.flow_control_generator.fget(self.mock_can_transport_interface) \ - == self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_generator - - @pytest.mark.parametrize("value", ["something", Mock()]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_flow_control_generator__set__type_error(self, mock_isinstance, value): - mock_isinstance.return_value = False - with pytest.raises(TypeError): - AbstractCanTransportInterface.flow_control_generator.fset(self.mock_can_transport_interface, value) - mock_isinstance.assert_called_once_with(value, (self.mock_can_packet_class, Iterable)) - - @pytest.mark.parametrize("value", ["something", Mock()]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_flow_control_generator__set__valid(self, mock_isinstance, value): - mock_isinstance.return_value = True - AbstractCanTransportInterface.flow_control_generator.fset(self.mock_can_transport_interface, value) - mock_isinstance.assert_called_once_with(value, (self.mock_can_packet_class, Iterable)) - assert self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_generator == value - assert self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_iterator is None - - # _get_flow_control - - @pytest.mark.parametrize("is_first", [True, False]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_get_flow_control__packet(self, mock_isinstance, is_first): - mock_isinstance.return_value = True - assert AbstractCanTransportInterface._get_flow_control(self=self.mock_can_transport_interface, - is_first=is_first) \ - == self.mock_can_transport_interface.flow_control_generator - mock_isinstance.assert_called_once_with(self.mock_can_transport_interface.flow_control_generator, - self.mock_can_packet_class) - - @patch(f"{SCRIPT_LOCATION}.iter") - @patch(f"{SCRIPT_LOCATION}.next") - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_get_flow_control__generator__first(self, mock_isinstance, mock_next, mock_iter): - mock_isinstance.return_value = False - assert AbstractCanTransportInterface._get_flow_control(self=self.mock_can_transport_interface, - is_first=True) \ - == mock_next.return_value - mock_isinstance.assert_called_once_with(self.mock_can_transport_interface.flow_control_generator, - self.mock_can_packet_class) - mock_iter.assert_called_once_with(self.mock_can_transport_interface.flow_control_generator) - mock_next.assert_called_once_with( - self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_iterator) - assert self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_iterator \ - == mock_iter.return_value - - @patch(f"{SCRIPT_LOCATION}.iter") - @patch(f"{SCRIPT_LOCATION}.next") - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_get_flow_control__generator__following(self, mock_isinstance, mock_next, mock_iter): - mock_isinstance.return_value = False - self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_iterator = Mock() - assert AbstractCanTransportInterface._get_flow_control(self=self.mock_can_transport_interface, - is_first=False) \ - == mock_next.return_value - mock_isinstance.assert_called_once_with(self.mock_can_transport_interface.flow_control_generator, - self.mock_can_packet_class) - mock_iter.assert_not_called() - mock_next.assert_called_once_with( - self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_iterator) diff --git a/tests/software_tests/transport_interface/test_abstract_transport_interface.py b/tests/software_tests/transport_interface/test_abstract_transport_interface.py index bf3aa444..839f6ed0 100644 --- a/tests/software_tests/transport_interface/test_abstract_transport_interface.py +++ b/tests/software_tests/transport_interface/test_abstract_transport_interface.py @@ -1,8 +1,7 @@ import pytest -from mock import Mock, patch +from mock import Mock -from uds.transport_interface.abstract_transport_interface import AbstractTransportInterface, \ - UdsMessageRecord, UdsMessage +from uds.transport_interface.abstract_transport_interface import AbstractTransportInterface, AbstractUdsPacket class TestAbstractTransportInterface: @@ -12,15 +11,6 @@ class TestAbstractTransportInterface: def setup(self): self.mock_transport_interface = Mock(spec=AbstractTransportInterface) - # patching - self._patcher_records_queue_class = patch(f"{self.SCRIPT_LOCATION}.RecordsQueue") - self.mock_records_queue_class = self._patcher_records_queue_class.start() - self._patcher_transmission_queue_class = patch(f"{self.SCRIPT_LOCATION}.TransmissionQueue") - self.mock_transmission_queue_class = self._patcher_transmission_queue_class.start() - - def teardown(self): - self._patcher_records_queue_class.stop() - self._patcher_transmission_queue_class.stop() # __init__ @@ -28,8 +18,7 @@ def test_init__value_error(self): self.mock_transport_interface.is_supported_bus_manager.return_value = False with pytest.raises(ValueError): AbstractTransportInterface.__init__(self=self.mock_transport_interface, - bus_manager=Mock(), - message_records_number=Mock()) + bus_manager=Mock()) self.mock_transport_interface.is_supported_bus_manager.assert_called_once() @pytest.mark.parametrize("bus_manager, message_records_number, packet_records_number", [ @@ -39,31 +28,9 @@ def test_init__value_error(self): def test_init__valid(self, bus_manager, message_records_number, packet_records_number): self.mock_transport_interface.is_supported_bus_manager.return_value = True AbstractTransportInterface.__init__(self=self.mock_transport_interface, - bus_manager=bus_manager, - message_records_number=message_records_number) + bus_manager=bus_manager) self.mock_transport_interface.is_supported_bus_manager.assert_called_once_with(bus_manager) - self.mock_records_queue_class.assert_called_once_with(records_type=UdsMessageRecord, - history_size=message_records_number) - self.mock_transmission_queue_class.assert_called_once_with(pdu_type=UdsMessage) assert self.mock_transport_interface._AbstractTransportInterface__bus_manager == bus_manager - assert self.mock_transport_interface._AbstractTransportInterface__message_records_queue \ - == self.mock_records_queue_class.return_value - assert self.mock_transport_interface._AbstractTransportInterface__message_transmission_queue \ - == self.mock_transmission_queue_class.return_value - - # _message_records_queue - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_message_records_queue__get(self, value): - self.mock_transport_interface._AbstractTransportInterface__message_records_queue = value - assert AbstractTransportInterface._message_records_queue.fget(self.mock_transport_interface) == value - - # _message_transmission_queue - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_message_transmission_queue__get(self, value): - self.mock_transport_interface._AbstractTransportInterface__message_transmission_queue = value - assert AbstractTransportInterface._message_transmission_queue.fget(self.mock_transport_interface) == value # bus_manager @@ -72,54 +39,17 @@ def test_bus_manager(self, value): self.mock_transport_interface._AbstractTransportInterface__bus_manager = value assert AbstractTransportInterface.bus_manager.fget(self.mock_transport_interface) == value - # message_records_history - - def test_message_records_history__get(self): - assert AbstractTransportInterface.message_records_history.fget(self.mock_transport_interface) \ - == self.mock_transport_interface._message_records_queue.records_history - - # packet_records_history - - def test_packet_records_history__get(self): - assert AbstractTransportInterface.packet_records_history.fget(self.mock_transport_interface) \ - == self.mock_transport_interface._packet_records_queue.records_history - - # await_packet_received + # send_packet @pytest.mark.asyncio - async def test_await_packet_received(self): + async def test_send_packet(self): with pytest.raises(NotImplementedError): - await AbstractTransportInterface.await_packet_received(self=self.mock_transport_interface) + await AbstractTransportInterface.send_packet(self=self.mock_transport_interface, + packet=Mock(spec=AbstractUdsPacket)) - # await_packet_transmitted + # receive_packet @pytest.mark.asyncio - async def test_await_packet_transmitted(self): - with pytest.raises(NotImplementedError): - await AbstractTransportInterface.await_packet_transmitted(self=self.mock_transport_interface) - - # await_message_received - - @pytest.mark.asyncio - async def test_await_message_received(self): - with pytest.raises(NotImplementedError): - await AbstractTransportInterface.await_message_received(self=self.mock_transport_interface) - - # await_message_transmitted - - @pytest.mark.asyncio - async def test_await_message_transmitted(self): - with pytest.raises(NotImplementedError): - await AbstractTransportInterface.await_message_transmitted(self=self.mock_transport_interface) - - # schedule_packet - - def test_schedule_packet(self): - with pytest.raises(NotImplementedError): - AbstractTransportInterface.schedule_packet(self=self.mock_transport_interface, packet=Mock()) - - # schedule_message - - def test_schedule_message(self): + async def test_receive_packet(self): with pytest.raises(NotImplementedError): - AbstractTransportInterface.schedule_message(self=self.mock_transport_interface, message=Mock()) + await AbstractTransportInterface.receive_packet(self=self.mock_transport_interface) diff --git a/tests/software_tests/transport_interface/test_python_can_transport_interface.py b/tests/software_tests/transport_interface/test_python_can_transport_interface.py index 44bb8151..0409d41f 100644 --- a/tests/software_tests/transport_interface/test_python_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_python_can_transport_interface.py @@ -2,7 +2,7 @@ from mock import MagicMock, Mock, patch from uds.transport_interface.python_can_transport_interface import PyCanTransportInterface, \ - BusABC, AbstractTransportInterface, CanPacket + BusABC, CanPacket from uds.can import CanAddressingInformation, CanAddressingFormat @@ -16,12 +16,9 @@ def setup(self): # patching self._patcher_abstract_can_ti_init = patch(f"{self.SCRIPT_LOCATION}.AbstractCanTransportInterface.__init__") self.mock_abstract_can_ti_init = self._patcher_abstract_can_ti_init.start() - self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") - self.mock_warn = self._patcher_warn.start() def teardown(self): self._patcher_abstract_can_ti_init.stop() - self._patcher_warn.stop() # __init__ @@ -35,9 +32,7 @@ def test_init__default_args(self, can_bus_manager, addressing_information): addressing_information=addressing_information) self.mock_abstract_can_ti_init.assert_called_once_with( can_bus_manager=can_bus_manager, - addressing_information=addressing_information, - packet_records_number=AbstractTransportInterface.DEFAULT_PACKET_RECORDS_NUMBER, - message_records_number=AbstractTransportInterface.DEFAULT_MESSAGE_RECORDS_NUMBER) + 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 @@ -47,22 +42,17 @@ def test_init__default_args(self, can_bus_manager, addressing_information): ("can_bus_manager", "addressing_information"), (Mock(), Mock()) ]) - @pytest.mark.parametrize("packet_records_number, message_records_number, kwargs", [ - (1, 2, {"a": Mock(), "b": Mock()}), - (Mock(), Mock(), {"param1": Mock(), "param2": Mock(), "something_else": Mock()}), + @pytest.mark.parametrize("kwargs", [ + {"a": Mock(), "b": Mock()}, + {"param1": Mock(), "param2": Mock(), "something_else": Mock()}, ]) - def test_init__all_args(self, can_bus_manager, addressing_information, - packet_records_number, message_records_number, kwargs): + def test_init__all_args(self, can_bus_manager, addressing_information, kwargs): PyCanTransportInterface.__init__(self=self.mock_can_transport_interface, can_bus_manager=can_bus_manager, addressing_information=addressing_information, - packet_records_number=packet_records_number, - message_records_number=message_records_number, **kwargs) - self.mock_abstract_can_ti_init.assert_called_once_with(an_bus_manager=can_bus_manager, + self.mock_abstract_can_ti_init.assert_called_once_with(can_bus_manager=can_bus_manager, addressing_information=addressing_information, - packet_records_number=packet_records_number, - message_records_number=message_records_number, **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 @@ -105,68 +95,6 @@ def test_is_supported_bus_manager(self, mock_isinstance, value): assert PyCanTransportInterface.is_supported_bus_manager(value) == mock_isinstance.return_value mock_isinstance.assert_called_once_with(value, BusABC) - # send_packet - - @pytest.mark.parametrize("packet", ["some packet", Mock()]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_send_packet__type_error(self, mock_isinstance, packet): - mock_isinstance.return_value = False - with pytest.raises(TypeError): - PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=packet) - mock_isinstance.assert_called_once_with(packet, CanPacket) - - # TODO: finish more cases - - # @pytest.mark.parametrize("packet", [Mock()]) - # @patch(f"{SCRIPT_LOCATION}.isinstance") - # def test_send_packet__with_warning(self, mock_isinstance, packet): - # mock_isinstance.return_value = True - # PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=packet) - # mock_isinstance.assert_called_once_with(packet, CanPacket) - # self.mock_warn.assert_called_once() - # - # @pytest.mark.parametrize("packet", [Mock()]) - # @patch(f"{SCRIPT_LOCATION}.isinstance") - # def test_send_packet__without_warning(self, mock_isinstance, packet): - # mock_isinstance.return_value = True - # PyCanTransportInterface.send_packet(self=self.mock_can_transport_interface, packet=packet) - # mock_isinstance.assert_called_once_with(packet, CanPacket) - # self.mock_warn.assert_not_called() - - # send_message - - def test_send_message(self): - with pytest.raises(NotImplementedError): - PyCanTransportInterface.send_message(self=self.mock_can_transport_interface, message=Mock()) - - # receive_packet - - @pytest.mark.parametrize("timeout", ["some timeout", Mock()]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_receive_packet__type_error(self, mock_isinstance, timeout): - mock_isinstance.return_value = False - with pytest.raises(TypeError): - PyCanTransportInterface.receive_packet(self=self.mock_can_transport_interface, timeout=timeout) - mock_isinstance.assert_called_once_with(timeout, (int, float)) - - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_receive_packet__value_error(self, mock_isinstance): - mock_le = Mock(return_value=True) - mock_timeout = MagicMock(__le__=mock_le) - mock_isinstance.return_value = True - with pytest.raises(ValueError): - PyCanTransportInterface.receive_packet(self=self.mock_can_transport_interface, timeout=mock_timeout) - mock_isinstance.assert_called_once_with(mock_timeout, (int, float)) - mock_le.assert_called_once_with(0) - - # TODO: more cases - - # receive_message - - def test_receive_message(self): - with pytest.raises(NotImplementedError): - PyCanTransportInterface.receive_message(self=self.mock_can_transport_interface, timeout=Mock()) - class TestPyCanTransportInterfaceIntegration: """Integration tests for `PyCanTransportInterface` class.""" diff --git a/tests/software_tests/transport_interface/test_records_queue.py b/tests/software_tests/transport_interface/test_records_queue.py deleted file mode 100644 index fffc281d..00000000 --- a/tests/software_tests/transport_interface/test_records_queue.py +++ /dev/null @@ -1,184 +0,0 @@ -import pytest -from copy import deepcopy -from mock import AsyncMock, Mock, patch - -from uds.transport_interface.records_queue import RecordsQueue, \ - UdsMessageRecord, AbstractUdsPacketRecord - - -class TestRecordsQueue: - """Unit tests for 'RecordsQueue' class.""" - - SCRIPT_LOCATION = "uds.transport_interface.records_queue" - - def setup(self): - self.mock_records_queue = Mock(spec=RecordsQueue) - # patching - self._patcher_event_class = patch(f"{self.SCRIPT_LOCATION}.Event") - self.mock_event_class = self._patcher_event_class.start() - - def teardown(self): - self._patcher_event_class.stop() - - # __init__ - - @pytest.mark.parametrize("records_type, history_size", [ - (Mock(), Mock()), - ("some type", "some history size"), - ]) - @patch(f"{SCRIPT_LOCATION}.issubclass") - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_init__type_error__records_type(self, mock_isinstance, mock_issubclass, records_type, history_size): - mock_isinstance.return_value = True - mock_issubclass.return_value = False - with pytest.raises(TypeError): - RecordsQueue.__init__(self=self.mock_records_queue, - records_type=records_type, - history_size=history_size) - mock_issubclass.assert_called_once_with(records_type, (UdsMessageRecord, AbstractUdsPacketRecord)) - - @pytest.mark.parametrize("records_type, history_size", [ - (Mock(), Mock()), - ("some type", "some history size"), - ]) - @patch(f"{SCRIPT_LOCATION}.issubclass") - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_init__type_error__history_size(self, mock_isinstance, mock_issubclass, records_type, history_size): - mock_isinstance.return_value = False - mock_issubclass.return_value = True - with pytest.raises(TypeError): - RecordsQueue.__init__(self=self.mock_records_queue, - records_type=records_type, - history_size=history_size) - mock_isinstance.assert_called_once_with(history_size, int) - - @pytest.mark.parametrize("records_type", [Mock(), "some type"]) - @patch(f"{SCRIPT_LOCATION}.issubclass") - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_init__value_error(self, mock_isinstance, mock_issubclass, records_type): - mock_isinstance.return_value = True - mock_issubclass.return_value = True - mock_le = Mock(return_value=True) - mock_history_size = Mock(__le__=mock_le) - with pytest.raises(ValueError): - RecordsQueue.__init__(self=self.mock_records_queue, - records_type=records_type, - history_size=mock_history_size) - mock_isinstance.assert_called_once_with(mock_history_size, int) - mock_issubclass.assert_called_once_with(records_type, (UdsMessageRecord, AbstractUdsPacketRecord)) - mock_le.assert_called_once_with(0) - - @pytest.mark.parametrize("records_type", [Mock(), "some type"]) - @patch(f"{SCRIPT_LOCATION}.issubclass") - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_init__valid(self, mock_isinstance, mock_issubclass, records_type): - mock_isinstance.return_value = True - mock_issubclass.return_value = True - mock_le = Mock(return_value=False) - mock_history_size = Mock(__le__=mock_le) - assert RecordsQueue.__init__(self=self.mock_records_queue, - records_type=records_type, - history_size=mock_history_size) is None - mock_isinstance.assert_called_once_with(mock_history_size, int) - mock_issubclass.assert_called_once_with(records_type, (UdsMessageRecord, AbstractUdsPacketRecord)) - mock_le.assert_called_once_with(0) - self.mock_event_class.assert_called_once_with() - assert self.mock_records_queue._RecordsQueue__records_type == records_type - assert self.mock_records_queue._RecordsQueue__history_size == mock_history_size - assert self.mock_records_queue._RecordsQueue__total_records_number == 0 - assert self.mock_records_queue._RecordsQueue__records_history == [] - assert self.mock_records_queue._RecordsQueue__event_new_record == self.mock_event_class.return_value - - # records_type - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_records_type__get(self, value): - self.mock_records_queue._RecordsQueue__records_type = value - assert RecordsQueue.records_type.fget(self.mock_records_queue) == value - - # history_size - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_history_size__get(self, value): - self.mock_records_queue._RecordsQueue__history_size = value - assert RecordsQueue.history_size.fget(self.mock_records_queue) == value - - # total_records_number - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_total_records_number__get(self, value): - self.mock_records_queue._RecordsQueue__total_records_number = value - assert RecordsQueue.total_records_number.fget(self.mock_records_queue) == value - - # records_history - - @pytest.mark.parametrize("value", ["something", Mock()]) - @patch(f"{SCRIPT_LOCATION}.tuple") - def test_records_history__get(self, mock_tuple, value): - self.mock_records_queue._RecordsQueue__records_history = value - assert RecordsQueue.records_history.fget(self.mock_records_queue) == mock_tuple.return_value - mock_tuple.assert_called_once_with(value) - - # clear_records_history - - def test_clear_records_history(self): - assert RecordsQueue.clear_records_history(self.mock_records_queue) is None - assert self.mock_records_queue._RecordsQueue__records_history == [] - assert self.mock_records_queue._RecordsQueue__total_records_number == 0 - - # put_record - - @pytest.mark.parametrize("record", ["some record", Mock()]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_put_record__type_error(self, mock_isinstance, record): - mock_isinstance.return_value = False - with pytest.raises(TypeError): - RecordsQueue.put_record(self=self.mock_records_queue, record=record) - mock_isinstance.assert_called_once_with(record, self.mock_records_queue.records_type) - - @pytest.mark.parametrize("records_history, history_size", [ - ([], 0), - (list(range(10)), 15), - (["record 1", "record 2"], 2), - ]) - @pytest.mark.parametrize("record, total_records_number", [ - ("some record", 0), - (Mock(), 12325), - ]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_put_record__valid(self, mock_isinstance, record, total_records_number, records_history, history_size): - mock_isinstance.return_value = True - self.mock_records_queue._RecordsQueue__total_records_number = total_records_number - self.mock_records_queue._RecordsQueue__records_history = deepcopy(records_history) - self.mock_records_queue._RecordsQueue__event_new_record = Mock() - self.mock_records_queue.history_size = history_size - assert RecordsQueue.put_record(self=self.mock_records_queue, record=record) is None - mock_isinstance.assert_called_once_with(record, self.mock_records_queue.records_type) - self.mock_records_queue._RecordsQueue__event_new_record.set.assert_called_once_with() - assert self.mock_records_queue._RecordsQueue__records_history == ([record] + records_history)[:history_size] - assert self.mock_records_queue._RecordsQueue__total_records_number == total_records_number + 1 - - # get_next_record - - @pytest.mark.asyncio - @pytest.mark.parametrize("records_history", [ - (Mock(), Mock(), Mock(), Mock()), - tuple(range(100)), - ]) - @pytest.mark.parametrize("records_number_1, records_number_2", [ - (0, 1), - (5, 8), - (65, 67), - ]) - async def test_get_next_record(self, records_history, records_number_1, records_number_2): - def _update_records_number(): - self.mock_records_queue.total_records_number = records_number_2 - - self.mock_records_queue.records_history = records_history - self.mock_records_queue.total_records_number = records_number_1 - self.mock_records_queue._RecordsQueue__event_new_record \ - = Mock(wait=AsyncMock(side_effect=_update_records_number)) - assert await RecordsQueue.get_next_record(self=self.mock_records_queue) \ - == records_history[records_number_2 - records_number_1 - 1] - self.mock_records_queue._RecordsQueue__event_new_record.clear.assert_called_once_with() - self.mock_records_queue._RecordsQueue__event_new_record.wait.assert_awaited_once_with() diff --git a/tests/software_tests/transport_interface/test_transmission_queue.py b/tests/software_tests/transport_interface/test_transmission_queue.py deleted file mode 100644 index 8e49814a..00000000 --- a/tests/software_tests/transport_interface/test_transmission_queue.py +++ /dev/null @@ -1,273 +0,0 @@ -import pytest -from mock import AsyncMock, MagicMock, Mock, patch, call -from copy import deepcopy -from time import perf_counter - -from uds.transport_interface.transmission_queue import TransmissionQueue, \ - UdsMessage, AbstractUdsPacket, QueueEmpty, Event, AsyncioTimeoutError, PriorityQueue - - -class TestTransmissionQueue: - """Unit tests for 'TransmissionQueue' class.""" - - SCRIPT_LOCATION = "uds.transport_interface.transmission_queue" - - def setup(self): - self.mock_transmission_queue = Mock(spec=TransmissionQueue, - _TransmissionQueue__timestamps=MagicMock(spec=set), - _TransmissionQueue__event_pdu_added=MagicMock(spec=Event, - wait=AsyncMock()), - _TransmissionQueue__async_queue=MagicMock(spec=PriorityQueue, - get=AsyncMock(), - put=AsyncMock(), - join=AsyncMock())) - # patching - self._patcher_len = patch(f"{self.SCRIPT_LOCATION}.len") - self.mock_len = self._patcher_len.start() - self._patcher_priority_queue_class = patch(f"{self.SCRIPT_LOCATION}.PriorityQueue") - self.mock_priority_queue_class = self._patcher_priority_queue_class.start() - self._patcher_event_class = patch(f"{self.SCRIPT_LOCATION}.Event") - self.mock_event_class = self._patcher_event_class.start() - self._patcher_wait_for = patch(f"{self.SCRIPT_LOCATION}.wait_for") - self.mock_wait_for = self._patcher_wait_for.start() - self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") - self.mock_warn = self._patcher_warn.start() - self._patcher_perf_counter = patch(f"{self.SCRIPT_LOCATION}.perf_counter") - self.mock_perf_counter = self._patcher_perf_counter.start() - - def teardown(self): - self._patcher_len.stop() - self._patcher_priority_queue_class.stop() - self._patcher_event_class.stop() - self._patcher_wait_for.stop() - self._patcher_warn.stop() - self._patcher_perf_counter.stop() - - # __init__ - - @pytest.mark.parametrize("pdu_type", ["something", Mock()]) - @patch(f"{SCRIPT_LOCATION}.issubclass") - def test_init__type_error(self, mock_issubclass, pdu_type): - mock_issubclass.return_value = False - with pytest.raises(TypeError): - TransmissionQueue.__init__(self=self.mock_transmission_queue, pdu_type=pdu_type) - mock_issubclass.assert_called_once_with(pdu_type, (UdsMessage, AbstractUdsPacket)) - - @pytest.mark.parametrize("pdu_type", ["something", Mock()]) - @patch(f"{SCRIPT_LOCATION}.issubclass") - def test_init__valid(self, mock_issubclass, pdu_type): - mock_issubclass.return_value = True - assert TransmissionQueue.__init__(self=self.mock_transmission_queue, pdu_type=pdu_type) is None - mock_issubclass.assert_called_once_with(pdu_type, (UdsMessage, AbstractUdsPacket)) - self.mock_event_class.assert_called_once_with() - assert self.mock_transmission_queue._TransmissionQueue__pdu_type == pdu_type - assert self.mock_transmission_queue._TransmissionQueue__async_queue \ - == self.mock_priority_queue_class.return_value - assert self.mock_transmission_queue._TransmissionQueue__event_pdu_added \ - == self.mock_event_class.return_value - assert self.mock_transmission_queue._TransmissionQueue__timestamps == set() - - # __len__ - - def test_len(self): - mock_len = Mock() - self.mock_transmission_queue._TransmissionQueue__async_queue = Mock(qsize=mock_len) - assert TransmissionQueue.__len__(self=self.mock_transmission_queue) == mock_len.return_value - - # __pdu_ready - - @pytest.mark.asyncio - @pytest.mark.parametrize("current_timestamp, timestamps", [ - (0, {0, 0.1}), - (100, {43.123, 102, 150}) - ]) - async def test_pdu_ready__now(self, current_timestamp, timestamps): - self.mock_perf_counter.return_value = current_timestamp - self.mock_transmission_queue._TransmissionQueue__timestamps = deepcopy(timestamps) - assert await TransmissionQueue._TransmissionQueue__pdu_ready(self=self.mock_transmission_queue) \ - == min(timestamps) - self.mock_perf_counter.assert_called_once_with() - assert self.mock_transmission_queue._TransmissionQueue__timestamps == timestamps - - @pytest.mark.asyncio - @pytest.mark.parametrize("current_timestamp, next_timestamp", [ - (0, 2.56789), - (9654.4312965, 93921321.2315312), - ]) - async def test_pdu_ready__await_packet(self, current_timestamp, next_timestamp): - self.mock_perf_counter.side_effect = [current_timestamp, next_timestamp] - self.mock_transmission_queue._TransmissionQueue__timestamps = set() - self.mock_wait_for.side_effect \ - = lambda *args, **kwargs: self.mock_transmission_queue._TransmissionQueue__timestamps.add(next_timestamp) - assert await TransmissionQueue._TransmissionQueue__pdu_ready(self=self.mock_transmission_queue) \ - == next_timestamp - self.mock_wait_for.assert_awaited_once() # TODO: https://stackoverflow.com/questions/70448262/how-to-use-asyncmock-and-get-coroutines-futures-returned-from-call - assert self.mock_wait_for.mock_calls[0].kwargs["timeout"] == float("inf") - self.mock_perf_counter.assert_has_calls([call(), call()]) - assert self.mock_transmission_queue._TransmissionQueue__timestamps == {next_timestamp} - - @pytest.mark.asyncio - @pytest.mark.parametrize("current_timestamp, timestamps", [ - (0, {1, 0.1}), - (100, {983221, 102, 150}) - ]) - async def test_pdu_ready__await_timestamp(self, current_timestamp, timestamps): - self.mock_perf_counter.return_value = current_timestamp - self.mock_transmission_queue._TransmissionQueue__timestamps = deepcopy(timestamps) - self.mock_wait_for.side_effect = AsyncioTimeoutError - assert await TransmissionQueue._TransmissionQueue__pdu_ready(self=self.mock_transmission_queue) \ - == min(timestamps) - self.mock_wait_for.assert_awaited_once() # TODO: https://stackoverflow.com/questions/70448262/how-to-use-asyncmock-and-get-coroutines-futures-returned-from-call - assert self.mock_wait_for.mock_calls[0].kwargs["timeout"] == min(timestamps) - current_timestamp - self.mock_perf_counter.assert_called_once_with() - assert self.mock_transmission_queue._TransmissionQueue__timestamps == timestamps - - # pdu_type - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_pdu_type__get(self, value): - self.mock_transmission_queue._TransmissionQueue__pdu_type = value - assert TransmissionQueue.pdu_type.fget(self.mock_transmission_queue) == value - - # is_empty - - @pytest.mark.parametrize("value", [True, False]) - def test_is_empty(self, value): - mock_eq = Mock(return_value=value) - self.mock_len.return_value = MagicMock(__eq__=mock_eq) - assert TransmissionQueue.is_empty.fget(self.mock_transmission_queue) is value - mock_eq.assert_called_once_with(0) - - # mark_pdu_sent - - def test_mark_pdu_sent(self): - mock_task_done = Mock() - self.mock_transmission_queue._TransmissionQueue__async_queue = Mock(task_done=mock_task_done) - assert TransmissionQueue.mark_pdu_sent(self=self.mock_transmission_queue) is None - mock_task_done.assert_called_once_with() - - # clear - - @pytest.mark.parametrize("packets_number", [0, 1, 99]) - def test_clear(self, packets_number): - self.mock_len.return_value = packets_number - mock_get_nowait = Mock() - self.mock_transmission_queue._TransmissionQueue__async_queue = Mock(get_nowait=mock_get_nowait) - assert TransmissionQueue.clear(self=self.mock_transmission_queue) is None - self.mock_len.assert_called_once_with(self.mock_transmission_queue) - mock_get_nowait.assert_has_calls([call()] * packets_number) - self.mock_warn.assert_not_called() - self.mock_transmission_queue.mark_pdu_sent.assert_has_calls([call()] * packets_number) - - @pytest.mark.parametrize("packets_number", [1, 99]) - def test_clear__queue_empty(self, packets_number): - self.mock_len.return_value = packets_number - mock_get_nowait = Mock(side_effect=QueueEmpty) - self.mock_transmission_queue._TransmissionQueue__async_queue = Mock(get_nowait=mock_get_nowait) - assert TransmissionQueue.clear(self=self.mock_transmission_queue) is None - self.mock_len.assert_called_once_with(self.mock_transmission_queue) - mock_get_nowait.assert_called_once() - self.mock_warn.assert_called_once() - self.mock_transmission_queue.mark_pdu_sent.assert_not_called() - - # get_pdu - - @pytest.mark.asyncio - @pytest.mark.parametrize("pdu_timestamp", [123.456, 0.91784]) - @pytest.mark.parametrize("pdu", ["something", Mock()]) - async def test_get_pdu(self, pdu_timestamp, pdu): - self.mock_transmission_queue._TransmissionQueue__pdu_ready.return_value = pdu_timestamp - self.mock_transmission_queue._TransmissionQueue__async_queue.get_nowait.return_value = [pdu_timestamp, pdu] - assert await TransmissionQueue.get_pdu(self=self.mock_transmission_queue) == pdu - self.mock_transmission_queue._TransmissionQueue__pdu_ready.assert_awaited_once_with() - self.mock_transmission_queue._TransmissionQueue__async_queue.get_nowait.assert_called_once_with() - self.mock_transmission_queue._TransmissionQueue__timestamps.remove.assert_called_once_with(pdu_timestamp) - - @pytest.mark.asyncio - @pytest.mark.parametrize("min_timestamp, pdu_timestamp", [ - (1, 2), - (9.5, 3.123), - ]) - @pytest.mark.parametrize("pdu", ["something", Mock()]) - async def test_get_pdu__runtime_error(self, min_timestamp, pdu_timestamp, pdu): - self.mock_transmission_queue._TransmissionQueue__pdu_ready.return_value = min_timestamp - self.mock_transmission_queue._TransmissionQueue__async_queue.get_nowait.return_value = [pdu_timestamp, pdu] - with pytest.raises(RuntimeError): - await TransmissionQueue.get_pdu(self=self.mock_transmission_queue) - self.mock_transmission_queue._TransmissionQueue__pdu_ready.assert_awaited_once_with() - self.mock_transmission_queue._TransmissionQueue__async_queue.get_nowait.assert_called_once_with() - - # put_pdu - - @pytest.mark.parametrize("pdu", [Mock(), "a pdu"]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_put_pdu__type_error__pdu(self, mock_isinstance, pdu): - mock_isinstance.return_value = False - with pytest.raises(TypeError): - TransmissionQueue.put_pdu(self=self.mock_transmission_queue, pdu=pdu) - mock_isinstance.assert_called_once_with(pdu, self.mock_transmission_queue.pdu_type) - - @pytest.mark.parametrize("pdu, timestamp", [ - (Mock(), Mock()), - ("a pdu", "some timestamp") - ]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_put_pdu__type_error__timestamp(self, mock_isinstance, pdu, timestamp): - mock_isinstance.side_effect = [True, False] - with pytest.raises(TypeError): - TransmissionQueue.put_pdu(self=self.mock_transmission_queue, - pdu=pdu, - timestamp=timestamp) - mock_isinstance.assert_has_calls([call(pdu, self.mock_transmission_queue.pdu_type), - call(timestamp, float)]) - - @pytest.mark.parametrize("pdu", [Mock(), "a pdu"]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_put_pdu__without_timestamp(self, mock_isinstance, pdu): - mock_isinstance.return_value = True - timestamp = self.mock_perf_counter.return_value - assert TransmissionQueue.put_pdu(self=self.mock_transmission_queue, pdu=pdu) is None - mock_isinstance.assert_called_once_with(pdu, self.mock_transmission_queue.pdu_type) - self.mock_transmission_queue._TransmissionQueue__async_queue.put_nowait.assert_called_once_with((timestamp, pdu)) - self.mock_transmission_queue._TransmissionQueue__timestamps.add.assert_called_once_with(timestamp) - self.mock_transmission_queue._TransmissionQueue__event_pdu_added.set.assert_called_once_with() - - @pytest.mark.parametrize("pdu, timestamp", [ - (Mock(), Mock()), - ("a pdu", "some timestamp") - ]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_put_pdu__with_timestamp(self, mock_isinstance, pdu, timestamp): - mock_isinstance.return_value = True - assert TransmissionQueue.put_pdu(self=self.mock_transmission_queue, - pdu=pdu, - timestamp=timestamp) is None - mock_isinstance.assert_has_calls([call(pdu, self.mock_transmission_queue.pdu_type), - call(timestamp, float)]) - self.mock_transmission_queue._TransmissionQueue__async_queue.put_nowait.assert_called_once_with((timestamp, pdu)) - self.mock_transmission_queue._TransmissionQueue__timestamps.add.assert_called_once_with(timestamp) - self.mock_transmission_queue._TransmissionQueue__event_pdu_added.set.assert_called_once_with() - - -@pytest.mark.performance -class TestTransmissionQueuePerformance: - - def setup(self): - self.transmission_queue = TransmissionQueue(AbstractUdsPacket) - - @pytest.mark.asyncio - @pytest.mark.skip("Sometimes fails due to issues with async timing - TO BE IMPROVED") - @pytest.mark.parametrize("delays", [ - (0.1, 0.2, 0.3, 0.4), - (0.19, 0.01, 0.35, 0.3), - (0.01, 0.02, 0.8, 0.05, 0.03, 0.07, 0.065, 0.015, 0.7, 0.123456)]) - async def test_put_and_get_pdu(self, delays): - pdu_list = [Mock(spec=AbstractUdsPacket) for _ in delays] - pdu_with_delays = list(zip(pdu_list, [perf_counter() + delay for delay in delays])) - for pdu, timestamp in pdu_with_delays: - self.transmission_queue.put_pdu(pdu=pdu, timestamp=timestamp) - for expected_pdu, expected_timestamp in sorted(pdu_with_delays, key=lambda pair: pair[1]): - assert await self.transmission_queue.get_pdu() == expected_pdu - out_time = perf_counter() - assert out_time >= expected_timestamp - print(expected_timestamp, out_time, out_time - expected_timestamp) diff --git a/uds/transport_interface/abstract_can_transport_interface.py b/uds/transport_interface/abstract_can_transport_interface.py index 4897cbf3..6bcfb6a0 100644 --- a/uds/transport_interface/abstract_can_transport_interface.py +++ b/uds/transport_interface/abstract_can_transport_interface.py @@ -3,17 +3,15 @@ __all__ = ["AbstractCanTransportInterface"] -from typing import Optional, Union, Any, Iterator, Iterable +from typing import Optional, Union, Any, Iterator from abc import abstractmethod from warnings import warn from uds.utilities import TimeMilliseconds, ValueWarning -from uds.packet import CanPacket, CanPacketRecord, CanPacketType -from uds.can import AbstractCanAddressingInformation, CanFlowStatus, CanSTminTranslator +from uds.packet import CanPacket +from uds.can import AbstractCanAddressingInformation from uds.segmentation import CanSegmenter from .abstract_transport_interface import AbstractTransportInterface -from .records_queue import RecordsQueue -from .transmission_queue import TransmissionQueue FlowControlGeneratorAlias = Union[CanPacket, Iterator[CanPacket]] @@ -41,27 +39,15 @@ class AbstractCanTransportInterface(AbstractTransportInterface): DEFAULT_N_CS: Optional[TimeMilliseconds] = None """Default value of :ref:`N_Cs ` time parameter.""" - DEFAULT_FLOW_CONTROL_ARGS = { - "packet_type": CanPacketType.FLOW_CONTROL, - "flow_status": CanFlowStatus.ContinueToSend, - "block_size": 0x10, - "st_min": CanSTminTranslator.encode(0), - } - """Default parameters of Flow Control CAN Packet.""" - def __init__(self, can_bus_manager: Any, addressing_information: AbstractCanAddressingInformation, - packet_records_number: int = AbstractTransportInterface.DEFAULT_PACKET_RECORDS_NUMBER, - message_records_number: int = AbstractTransportInterface.DEFAULT_MESSAGE_RECORDS_NUMBER, **kwargs: Any) -> None: """ Create Transport Interface (an object for handling UDS Transport and Network layers). :param can_bus_manager: An object that handles CAN bus (Physical and Data layers of OSI Model). :param addressing_information: Addressing Information of CAN Transport Interface. - :param packet_records_number: Number of UDS packet records to store. - :param message_records_number: Number of UDS Message records to store. :param kwargs: Optional arguments that are specific for CAN bus. - :parameter n_as_timeout: Timeout value for :ref:`N_As ` time parameter. @@ -73,43 +59,23 @@ def __init__(self, - :parameter dlc: Base CAN DLC value to use for CAN Packets. - :parameter use_data_optimization: Information whether to use CAN Frame Data Optimization. - :parameter filler_byte: Filler byte value to use for CAN Frame Data Padding. - - :parameter flow_control_generator: Generator of Flow Control CAN packets. :raise TypeError: Provided Addressing Information value has unexpected type. """ if not isinstance(addressing_information, AbstractCanAddressingInformation): raise TypeError("Unsupported type of Addressing Information was provided.") self.__addressing_information: AbstractCanAddressingInformation = addressing_information - super().__init__(bus_manager=can_bus_manager, - message_records_number=message_records_number) + super().__init__(bus_manager=can_bus_manager) 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) self.n_br = kwargs.pop("n_br", self.DEFAULT_N_BR) self.n_cs = kwargs.pop("n_cs", self.DEFAULT_N_CS) self.n_cr_timeout = kwargs.pop("n_cr_timeout", self.N_CR_TIMEOUT) - flow_control_generator = kwargs.pop("flow_control_generator", None) self.__segmenter = CanSegmenter(addressing_format=addressing_information.addressing_format, physical_ai=addressing_information.tx_packets_physical_ai, functional_ai=addressing_information.tx_packets_functional_ai, **kwargs) - self.__packet_records_queue = RecordsQueue(records_type=CanPacketRecord, history_size=packet_records_number) - self.__packet_transmission_queue = TransmissionQueue(pdu_type=CanPacket) - self.flow_control_generator = flow_control_generator if flow_control_generator is not None else \ - CanPacket(**self.DEFAULT_FLOW_CONTROL_ARGS, - **addressing_information.tx_packets_physical_ai, - dlc=None if self.use_data_optimization else self.dlc, - filler_byte=self.filler_byte) - - @property - def _packet_records_queue(self) -> RecordsQueue: - """Queue with UDS packet records that were either received or transmitted.""" - return self.__packet_records_queue - - @property # noqa - def _packet_transmission_queue(self) -> TransmissionQueue: - """Queue with UDS packets that are planned for the transmission.""" - return self.__packet_transmission_queue @property def segmenter(self) -> CanSegmenter: @@ -382,54 +348,3 @@ def filler_byte(self, value: int): :param value: Value to set. """ self.segmenter.filler_byte = value - - # Flow Control - - @property - def flow_control_generator(self) -> FlowControlGeneratorAlias: - """Get the generator of Flow Control CAN Packets.""" - return self.__flow_control_generator - - @flow_control_generator.setter - def flow_control_generator(self, value: FlowControlGeneratorAlias): - """ - Set the value of Flow Control generator. - - :param value: The value of Flow Control CAN Packet generator to use. - It must be either: - - - object of `CanPacket` class - in this case the same Flow Control CAN Packet will always be used - - generator of `CanPacket` objects - built-in functions `iter` and `next` will be used on the generator - to restart iteration (upon reception of a new First Frame) and to produce the following - Flow Control CAN Packets - - :raise TypeError: Provided value has unexpected type. - """ - if not isinstance(value, (CanPacket, Iterable)): - raise TypeError("Provided value is not CAN Packet neither CAN Packet generator.") - self.__flow_control_generator = value - self.__flow_control_iterator = None - - def _get_flow_control(self, is_first: bool) -> CanPacket: - """ - Get the next Flow Control CAN Packet to send. - - .. warning:: This method is restricted for internal use and shall not be called by the user as it might cause - malfunctioning of Flow Control CAN Packets generation. - - :param is_first: Information whether it is the first Flow Control to respond to a message. - - - True - Flow Control to return is the first Flow Control CAN Packet to send in current diagnostic message - reception. In other words, it is the first Flow Control which directly responds to - :ref:`First Frame `. - - False - Flow Control to return is the following Flow Control CAN Packet to send in current - diagnostic message reception. In other words, there was at least one Flow Control already sent since - the reception of the last :ref:`First Frame `. - - :return: Flow Control CAN Packet to send in this diagnostic message reception - """ - if isinstance(self.flow_control_generator, CanPacket): - return self.flow_control_generator - if is_first: - self.__flow_control_iterator = iter(self.flow_control_generator) # type: ignore - return next(self.__flow_control_iterator) # type: ignore diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index 491b6585..66265941 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -2,15 +2,12 @@ __all__ = ["AbstractTransportInterface"] -from typing import Optional, Any, Tuple +from typing import Optional, Any from abc import ABC, abstractmethod from uds.utilities import TimeMilliseconds from uds.packet import AbstractUdsPacket, AbstractUdsPacketRecord -from uds.message import UdsMessage, UdsMessageRecord from uds.segmentation import AbstractSegmenter -from .records_queue import RecordsQueue -from .transmission_queue import TransmissionQueue class AbstractTransportInterface(ABC): @@ -20,47 +17,18 @@ class AbstractTransportInterface(ABC): Transport Interfaces are meant to handle middle layers (Transport and Network) of UDS OSI Model. """ - DEFAULT_PACKET_RECORDS_NUMBER: int = 100 - """Default number of UDS packet records stored.""" - DEFAULT_MESSAGE_RECORDS_NUMBER: int = 10 - """Default number of UDS message records stored.""" - def __init__(self, - bus_manager: Any, - message_records_number: int = DEFAULT_MESSAGE_RECORDS_NUMBER) -> None: + bus_manager: Any) -> None: """ Create Transport Interface (an object for handling UDS Transport and Network layers). :param bus_manager: An object that handles the bus (Physical and Data layers of OSI Model). - :param message_records_number: Number of UDS Message records to store. :raise ValueError: Provided value of bus manager is not supported by this Transport Interface. """ if not self.is_supported_bus_manager(bus_manager): raise ValueError("Unsupported bus manager was provided.") self.__bus_manager = bus_manager - self.__message_records_queue = RecordsQueue(records_type=UdsMessageRecord, history_size=message_records_number) - self.__message_transmission_queue = TransmissionQueue(pdu_type=UdsMessage) - - @property - @abstractmethod - def _packet_records_queue(self) -> RecordsQueue: - """Queue with UDS packet records that were either received or transmitted.""" - - @property # noqa - @abstractmethod - def _packet_transmission_queue(self) -> TransmissionQueue: - """Queue with UDS packets that are planned for the transmission.""" - - @property - def _message_records_queue(self) -> RecordsQueue: - """Queue with UDS messages records that were either received or transmitted.""" - return self.__message_records_queue - - @property # noqa - def _message_transmission_queue(self) -> TransmissionQueue: - """Queue with UDS messages that are planned for the transmission.""" - return self.__message_transmission_queue @property def bus_manager(self) -> Any: @@ -71,16 +39,6 @@ def bus_manager(self) -> Any: """ return self.__bus_manager - @property # noqa - def message_records_history(self) -> Tuple[UdsMessageRecord]: - """Historic records of UDS messages that were either received or transmitted.""" - return self._message_records_queue.records_history # type: ignore - - @property # noqa - def packet_records_history(self) -> Tuple[AbstractUdsPacketRecord]: - """Historic records of UDS packets that were either received or transmitted.""" - return self._packet_records_queue.records_history # type: ignore - @property @abstractmethod def segmenter(self) -> AbstractSegmenter: @@ -102,118 +60,8 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: :return: True if provided bus object is compatible with this Transport Interface, False otherwise. """ - # async def await_packet_received(self, - # timeout: Optional[TimeMilliseconds] = None, # noqa: F841 - # ignore_interruptions: bool = False) -> AbstractUdsPacketRecord: # noqa: F841 - # """ - # Wait until the next UDS packet is received. - # - # :param timeout: Maximal time (in milliseconds) to wait. - # :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was transmitted. - # - # - True - ignore transmitted UDS packets and do not raise InterruptedError - # - False - raise InterruptedError if UDS packet is transmitted when awaiting - # - # :raise TypeError: Timeout value is not int or float type. - # :raise ValueError: Timeout value is less or equal 0. - # :raise TimeoutError: Timeout was reached. - # :raise InterruptedError: UDS packet was transmitted during awaiting. - # - # :return: Record with historic information of a packet that was just received. - # """ - # raise NotImplementedError - # - # async def await_packet_transmitted(self, - # timeout: Optional[TimeMilliseconds] = None, # noqa: F841 - # ignore_interruptions: bool = False) -> AbstractUdsPacketRecord: # noqa: F841 - # """ - # Wait until the next UDS packet is transmitted. - # - # :param timeout: Maximal time (in milliseconds) to wait. - # :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was received. - # - # - True - ignore received UDS packets and do not raise InterruptedError - # - False - raise InterruptedError if UDS packet is received when awaiting - # - # :raise TypeError: Timeout value is not int or float type. - # :raise ValueError: Timeout value is less or equal 0. - # :raise TimeoutError: Timeout was reached. - # :raise InterruptedError: UDS packet was received during awaiting. - # - # :return: Record with historic information of a packet that was just transmitted. - # """ - # raise NotImplementedError - # - # async def await_message_received(self, - # timeout: Optional[TimeMilliseconds] = None, # noqa: F841 - # ignore_interruptions: bool = False) -> UdsMessageRecord: # noqa: F841 - # """ - # Wait until the next UDS message is received. - # - # :param timeout: Maximal time (in milliseconds) to wait. - # :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was transmitted. - # - # - True - ignore transmitted UDS packets and do not raise InterruptedError - # - False - raise InterruptedError if UDS packet is transmitted when awaiting - # - # :raise TypeError: Timeout value is not int or float type. - # :raise ValueError: Timeout value is less or equal 0. - # :raise TimeoutError: Timeout was reached. - # :raise InterruptedError: UDS packet was transmitted during awaiting. - # - # :return: Record with historic information of a message that was just received. - # """ - # raise NotImplementedError - # - # async def await_message_transmitted(self, - # timeout: Optional[TimeMilliseconds] = None, # noqa: F841 - # ignore_interruptions: bool = False) -> UdsMessageRecord: # noqa: F841 - # """ - # Wait until the next UDS message is transmitted. - # - # :param timeout: Maximal time (in milliseconds) to wait. - # :param ignore_interruptions: Flag informing whether to stop if meanwhile UDS packet was received. - # - # - True - ignore received UDS packets and do not raise InterruptedError - # - False - raise InterruptedError if UDS packet is received when awaiting - # - # :raise TypeError: Timeout value is not int or float type. - # :raise ValueError: Timeout value is less or equal 0. - # :raise TimeoutError: Timeout was reached. - # :raise InterruptedError: UDS packet was received during awaiting. - # - # :return: Record with historic information of a message that was just transmitted. - # """ - # raise NotImplementedError - # - # def schedule_packet(self, packet: AbstractUdsPacket, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 - # """ - # Schedule UDS packet transmission. - # - # :param packet: A packet to send. - # :param delay: Value of a delay (in milliseconds) if the transmission to be scheduled in the future. - # None if the transmission to be scheduled immediately. - # - # :raise TypeError: Delay value is not int or float type. - # :raise ValueError: Delay value is less or equal 0. - # """ - # raise NotImplementedError - # - # def schedule_message(self, message: UdsMessage, delay: Optional[TimeMilliseconds] = None) -> None: # noqa: F841 - # """ - # Schedule UDS message transmissions. - # - # :param message: A message to send. - # :param delay: Value of a delay (in milliseconds) if the transmission to be scheduled in the future. - # None if the transmission to be scheduled immediately. - # - # :raise TypeError: Delay value is not int or float type. - # :raise ValueError: Delay value is less or equal 0. - # """ - # raise NotImplementedError - @abstractmethod - def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: + async def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: """ Transmit UDS packet. @@ -221,19 +69,10 @@ def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: :return: Record with historic information about transmitted UDS packet. """ + raise NotImplementedError @abstractmethod - def send_message(self, message: UdsMessage) -> UdsMessageRecord: - """ - Transmit UDS message. - - :param message: A message to send. - - :return: Record with historic information about transmitted UDS message. - """ - - @abstractmethod - def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> AbstractUdsPacketRecord: + async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> AbstractUdsPacketRecord: """ Receive UDS packet. @@ -243,15 +82,4 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> AbstractUdsPack :return: Record with historic information about received UDS packet. """ - - @abstractmethod - def receive_message(self, timeout: Optional[TimeMilliseconds]) -> UdsMessageRecord: - """ - Receive UDS message. - - :param timeout: Maximal time (in milliseconds) to wait. - - :raise TimeoutError: Timeout was reached. - - :return: Record with historic information about received UDS message. - """ + raise NotImplementedError diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index c4c3943f..79d4bf16 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -7,34 +7,28 @@ __all__ = ["PyCanTransportInterface"] from typing import Optional, Any -from warnings import warn from can import BusABC, AsyncBufferedReader, Notifier -from uds.utilities import TimeMilliseconds, ValueWarning +from uds.utilities import TimeMilliseconds from uds.can import AbstractCanAddressingInformation from uds.packet import CanPacket, CanPacketRecord from uds.message import UdsMessage, UdsMessageRecord -from .abstract_transport_interface import AbstractTransportInterface from .abstract_can_transport_interface import AbstractCanTransportInterface class PyCanTransportInterface(AbstractCanTransportInterface): - """Transport Interface for managing UDS on CAN using python-can package.""" + """Transport Interface for managing UDS on CAN with python-can package as bus handler.""" def __init__(self, can_bus_manager: Any, addressing_information: AbstractCanAddressingInformation, - packet_records_number: int = AbstractTransportInterface.DEFAULT_PACKET_RECORDS_NUMBER, - message_records_number: int = AbstractTransportInterface.DEFAULT_MESSAGE_RECORDS_NUMBER, **kwargs: Any) -> None: """ Create python-can Transport Interface. :param can_bus_manager: An object that handles CAN bus (Physical and Data layers of OSI Model). :param addressing_information: Addressing Information of CAN Transport Interface. - :param packet_records_number: Number of UDS packet records to store. - :param message_records_number: Number of UDS Message records to store. :param kwargs: Optional arguments that are specific for CAN bus. - :parameter n_as_timeout: Timeout value for :ref:`N_As ` time parameter. @@ -46,7 +40,6 @@ def __init__(self, - :parameter dlc: Base CAN DLC value to use for CAN Packets. - :parameter use_data_optimization: Information whether to use CAN Frame Data Optimization. - :parameter filler_byte: Filler byte value to use for CAN Frame Data Padding. - - :parameter flow_control_generator: Generator of Flow Control CAN packets. """ self.__n_as_measured: Optional[TimeMilliseconds] = None self.__n_ar_measured: Optional[TimeMilliseconds] = None @@ -54,8 +47,6 @@ def __init__(self, self.__n_cr_measured: Optional[TimeMilliseconds] = None super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, - packet_records_number=packet_records_number, - message_records_number=message_records_number, **kwargs) @property @@ -105,15 +96,15 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: """ return isinstance(bus_manager, BusABC) - def send_packet(self, packet: CanPacket) -> CanPacketRecord: + async def send_packet(self, packet: CanPacket) -> CanPacketRecord: """ - Transmit UDS packet. + Transmit CAN packet. - :param packet: A packet to send. + :param packet: CAN packet to send. - :raise TypeError: Provided packet value does not contain CAN Packet. + :raise TypeError: Provided packet is not CAN Packet. - :return: Record with historic information about transmitted UDS packet. + :return: Record with historic information about transmitted CAN packet. """ if not isinstance(packet, CanPacket): raise TypeError("Provided packet value does not contain CAN Packet.") @@ -125,19 +116,9 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # - make sure that `_packet_records_queue` (and `_message_records_queue' if needed) is updated # - return record of transmitted packet - def send_message(self, message: UdsMessage) -> UdsMessageRecord: - """ - Transmit UDS message. - - :param message: A message to send. - - :return: Record with historic information about transmitted UDS message. - """ - raise NotImplementedError - def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> CanPacketRecord: """ - Receive UDS packet. + Receive CAN packet. :param timeout: Maximal time (in milliseconds) to wait. @@ -145,7 +126,7 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> CanPacketRecord :raise ValueError: Provided timeout value is less or equal 0. :raise TimeoutError: Timeout was reached. - :return: Record with historic information about received UDS packet. + :return: Record with historic information about received CAN packet. """ if timeout is not None: if not isinstance(timeout, (int, float)): @@ -158,15 +139,3 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> CanPacketRecord # - use Bus Listener to filter out UDS Packets that targets this entity # - make sure that `_packet_records_queue` (and `_message_records_queue' if needed) is updated # - return record of received packet when received - - def receive_message(self, timeout: Optional[TimeMilliseconds]) -> UdsMessageRecord: - """ - Receive UDS message. - - :param timeout: Maximal time (in milliseconds) to wait. - - :raise TimeoutError: Timeout was reached. - - :return: Record with historic information about received UDS message. - """ - raise NotImplementedError diff --git a/uds/transport_interface/records_queue.py b/uds/transport_interface/records_queue.py deleted file mode 100644 index b4aa95e2..00000000 --- a/uds/transport_interface/records_queue.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Implementation of queues with historic records.""" - -__all__ = ["RecordsQueue"] - -from typing import Union, Type, Tuple, List -from asyncio import Event - -from uds.message import UdsMessageRecord -from uds.packet import AbstractUdsPacketRecord - - -class RecordsQueue: - """Queue with historic records of UDS Packets or Messages.""" - - RecordsTypeAlias = Union[Type[UdsMessageRecord], Type[AbstractUdsPacketRecord]] - """Alias of a record type that is accepted by this Queue.""" - RecordAlias = Union[UdsMessageRecord, AbstractUdsPacketRecord] - """Alias of a record stored by this Queue.""" - - def __init__(self, records_type: RecordsTypeAlias, history_size: int) -> None: - """ - Create FIFO queue with historic records of UDS Packets or Messages. - - :param records_type: Type of records to store. - :param history_size: Number of records to store. - - :raise TypeError: Provided value has unsupported type. - :raise ValueError: Provided value is out of range. - """ - if not issubclass(records_type, (UdsMessageRecord, AbstractUdsPacketRecord)): - raise TypeError("Provided 'records_type' value does not store supported record class.") - if not isinstance(history_size, int): - raise TypeError("Provided 'history_size' value is not int type.") - if history_size <= 0: - raise ValueError("Provided value of 'history_size' is lower than 0.") - self.__records_type = records_type - self.__history_size: int = history_size - self.__total_records_number: int = 0 - self.__records_history: List[Union[UdsMessageRecord, AbstractUdsPacketRecord]] = [] - self.__event_new_record = Event() - - @property - def records_type(self) -> RecordsTypeAlias: - """Type of records stored by this queue.""" - return self.__records_type - - @property - def history_size(self) -> int: - """ - Get number of records stored by this queue. - - .. note:: If a record beyond this number is received, then the oldest one is pushed out of the queue. - """ - return self.__history_size - - @property # noqa: F841 - def total_records_number(self) -> int: - """Total number of records that went through the queue.""" - return self.__total_records_number - - @property - def records_history(self) -> Tuple[RecordAlias, ...]: - """Historic records from the youngest to the oldest.""" - return tuple(self.__records_history) - - def clear_records_history(self) -> None: - """Clear all stored records.""" - self.__records_history = [] - self.__total_records_number = 0 - - def put_record(self, record: RecordAlias) -> None: - """ - Add a new record to the queue. - - :param record: A new record to add. - - :raise TypeError: Provided record value has unexpected type. - """ - if not isinstance(record, self.records_type): - raise TypeError("Provided value has unexpected type.") - self.__records_history.insert(0, record) - self.__records_history = self.__records_history[:self.history_size] - self.__total_records_number += 1 - self.__event_new_record.set() - - async def get_next_record(self) -> RecordAlias: - """ - Get the next record to enter the queue. - - .. warning:: More than one record might enter the queue when awaited. - Regardless of the number of new records, always the next record would be returned. - - :return: The next record that was put to the queue. - """ - self.__event_new_record.clear() - records_number_at_start = self.total_records_number - await self.__event_new_record.wait() - records_number_with_new_records = self.total_records_number - return self.records_history[records_number_with_new_records - records_number_at_start - 1] diff --git a/uds/transport_interface/transmission_queue.py b/uds/transport_interface/transmission_queue.py deleted file mode 100644 index 46bd6271..00000000 --- a/uds/transport_interface/transmission_queue.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Implementation of a queue with PDUs to transmit.""" - -__all__ = ["TransmissionQueue"] - -from typing import Union, Optional, Type, Set -from warnings import warn -from time import perf_counter -from asyncio import PriorityQueue, Event, QueueEmpty, wait_for -from asyncio import TimeoutError as AsyncioTimeoutError - -from uds.message import UdsMessage -from uds.packet import AbstractUdsPacket - - -PDUTypeAlias = Union[Type[UdsMessage], Type[AbstractUdsPacket]] -"""Alias of a PDU type that is accepted by this Queue.""" -PDUAlias = Union[UdsMessage, AbstractUdsPacket] -"""Alias of a PDU (either a message or a packet) stored by this Queue.""" - - -class TransmissionQueue: - """Queue with PDUs to transmit.""" - - def __init__(self, pdu_type: PDUTypeAlias) -> None: - """ - Create timestamped queue with PDUs to transmit. - - :param pdu_type: Type of PDUs to store. - - :raise TypeError: Provided value has unsupported type. - """ - if not issubclass(pdu_type, (UdsMessage, AbstractUdsPacket)): - raise TypeError("Provided 'pdu_type' value does not store supported UDS message or packet class.") - self.__pdu_type = pdu_type - self.__async_queue: PriorityQueue = PriorityQueue() - self.__event_pdu_added: Event = Event() - self.__timestamps: Set[float] = set() - - def __len__(self) -> int: - """Get number of elements that are currently stored.""" - return self.__async_queue.qsize() - - async def __pdu_ready(self) -> float: - """ - Await until the next PDU's timestamp is achieved. - - :return: The lowest timestamp of a PDU that is ready for transmission. - """ - min_timestamp = min(self.__timestamps) if self.__timestamps else float("inf") - current_time = perf_counter() - while current_time < min_timestamp: - self.__event_pdu_added.clear() - try: - await wait_for(fut=self.__event_pdu_added.wait(), timeout=min_timestamp - current_time) - except AsyncioTimeoutError: - return min_timestamp - current_time = perf_counter() - min_timestamp = min(self.__timestamps) - return min_timestamp - - @property - def pdu_type(self) -> PDUTypeAlias: - """Type of PDUs stored by this queue.""" - return self.__pdu_type - - @property # noqa - def is_empty(self) -> bool: - """Flag whether the queue is empty (does not contain any PDUs).""" - return len(self) == 0 - - def mark_pdu_sent(self) -> None: - """ - Inform that one PDU transmission was completed or aborted. - - This method is used for monitoring PDU transmission tasks, so they can be completed safely and closed quietly. - """ - self.__async_queue.task_done() - - def clear(self) -> None: - """Delete all PDUs stored by the queue.""" - for _ in range(len(self)): - try: - self.__async_queue.get_nowait() - except QueueEmpty: - warn(message=f"At least one packet was gotten from {self} queue during the clearing.", - category=RuntimeWarning) - break - else: - self.mark_pdu_sent() - - # TODO: improve to have better performance (currently up to 15 ms) - async def get_pdu(self) -> PDUAlias: # pylint: disable=undefined-variable - """ - Get the next PDU from the queue. - - .. note:: If called when there are no packets available in the queue, then the method would await until - the next packet is ready (timestamp is achieved). - - :raise RuntimeError: Internal implementation problem occurred. - - :return: The next PDU from the queue. - """ - min_timestamp = await self.__pdu_ready() - packet_timestamp, pdu = self.__async_queue.get_nowait() - if min_timestamp != packet_timestamp: - raise RuntimeError("Something went wrong - packet_timestamp does not match the lowest timestamp.") - self.__timestamps.remove(packet_timestamp) - return pdu - - def put_pdu(self, pdu: PDUAlias, timestamp: Optional[float] = None) -> None: - """ - Add a PDU to the queue. - - :param pdu: A PDU to add to the queue. - :param timestamp: Timestamp value (from perf_counter) when make the packet available (gettable) in the queue. - - :raise TypeError: Provided PDU or timestamp value has unexpected type. - """ - if not isinstance(pdu, self.pdu_type): - raise TypeError("Provided PDU value has unexpected type.") - if timestamp is None: - timestamp = perf_counter() - elif not isinstance(timestamp, float): - raise TypeError("Provided timestamp value is not float (perf_counter) value.") - self.__async_queue.put_nowait((timestamp, pdu)) - self.__timestamps.add(timestamp) - self.__event_pdu_added.set() From e9e0358f38b700cb244a7f42c93ae48be47dab5a Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski <51504507+mdabrowski1990@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:29:09 +0200 Subject: [PATCH 08/40] adjust api Rework Transport Interface API according to current needs. --- .../abstract_can_transport_interface.py | 7 +------ uds/transport_interface/abstract_transport_interface.py | 2 +- uds/transport_interface/python_can_transport_interface.py | 5 ++--- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/uds/transport_interface/abstract_can_transport_interface.py b/uds/transport_interface/abstract_can_transport_interface.py index 6bcfb6a0..763bc2b9 100644 --- a/uds/transport_interface/abstract_can_transport_interface.py +++ b/uds/transport_interface/abstract_can_transport_interface.py @@ -3,21 +3,16 @@ __all__ = ["AbstractCanTransportInterface"] -from typing import Optional, Union, Any, Iterator +from typing import Optional, Any from abc import abstractmethod from warnings import warn from uds.utilities import TimeMilliseconds, ValueWarning -from uds.packet import CanPacket from uds.can import AbstractCanAddressingInformation from uds.segmentation import CanSegmenter from .abstract_transport_interface import AbstractTransportInterface -FlowControlGeneratorAlias = Union[CanPacket, Iterator[CanPacket]] -"""Alias of :ref:`Flow Control ` CAN Packets generator.""" - - class AbstractCanTransportInterface(AbstractTransportInterface): """ Abstract definition of Transport Interface for managing UDS on CAN bus. diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index 66265941..e1e1c636 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -61,7 +61,7 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: """ @abstractmethod - async def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: + def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: """ Transmit UDS packet. diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index 79d4bf16..19ceea00 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -13,7 +13,6 @@ from uds.utilities import TimeMilliseconds from uds.can import AbstractCanAddressingInformation from uds.packet import CanPacket, CanPacketRecord -from uds.message import UdsMessage, UdsMessageRecord from .abstract_can_transport_interface import AbstractCanTransportInterface @@ -96,7 +95,7 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: """ return isinstance(bus_manager, BusABC) - async def send_packet(self, packet: CanPacket) -> CanPacketRecord: + def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore """ Transmit CAN packet. @@ -116,7 +115,7 @@ async def send_packet(self, packet: CanPacket) -> CanPacketRecord: # - make sure that `_packet_records_queue` (and `_message_records_queue' if needed) is updated # - return record of transmitted packet - def receive_packet(self, timeout: Optional[TimeMilliseconds]) -> CanPacketRecord: + async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: """ Receive CAN packet. From 678c02608aacf81297d209ca52125859a9c0e579 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 17 May 2023 12:00:40 +0200 Subject: [PATCH 09/40] save Temporary version --- examples/kvaser.py | 30 ++++++++++++++----- .../test_abstract_can_transport_interface.py | 1 + .../test_abstract_transport_interface.py | 1 + .../python_can_transport_interface.py | 29 ++++++++---------- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/examples/kvaser.py b/examples/kvaser.py index 240ac101..cac87c38 100644 --- a/examples/kvaser.py +++ b/examples/kvaser.py @@ -1,9 +1,11 @@ import can -bus1 = can.Bus(interface="kvaser", channel=0, fd=True) -bus2 = can.Bus(interface="kvaser", channel=1, fd=True) -listener = can.BufferedReader() -notifier = can.Notifier(bus=bus1, listeners=[listener]) +bus1 = can.Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +bus2 = can.Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) +listener1 = can.BufferedReader() +listener2 = can.BufferedReader() +notifier1 = can.Notifier(bus=bus1, listeners=[listener1]) +notifier2 = can.Notifier(bus=bus2, listeners=[listener2]) msg1 = can.Message(arbitration_id=0x123456, @@ -14,9 +16,21 @@ dlc=8, data=[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF][::-1]) +print("#MSG 1 sent to bus1") bus1.send(msg=msg1) -bus1.send(msg=msg1) -bus2.send(msg=msg2) -print(listener.get_message()) -print(listener.get_message()) +print("#MSG 2 sent to bus1") +bus1.send(msg=msg2) + +print("#MSG 2 sent to bus2") +bus2.send(msg=msg1) + +print(listener1.get_message()) +print(listener2.get_message()) +print(listener1.get_message()) +print(listener2.get_message()) +print(listener1.get_message()) +print(listener2.get_message()) +print(listener1.get_message()) +print(listener2.get_message()) + diff --git a/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py b/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py index dea31b9c..431864f7 100644 --- a/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py @@ -5,6 +5,7 @@ AbstractCanAddressingInformation +@pytest.mark.skip class TestAbstractCanTransportInterface: """Unit tests for `AbstractCanTransportInterface` class.""" diff --git a/tests/software_tests/transport_interface/test_abstract_transport_interface.py b/tests/software_tests/transport_interface/test_abstract_transport_interface.py index 839f6ed0..c959f7e5 100644 --- a/tests/software_tests/transport_interface/test_abstract_transport_interface.py +++ b/tests/software_tests/transport_interface/test_abstract_transport_interface.py @@ -4,6 +4,7 @@ from uds.transport_interface.abstract_transport_interface import AbstractTransportInterface, AbstractUdsPacket +@pytest.mark.skip class TestAbstractTransportInterface: """Unit tests for `AbstractTransportInterface` class.""" diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index 19ceea00..b1d7e7e5 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -8,10 +8,10 @@ from typing import Optional, Any -from can import BusABC, AsyncBufferedReader, Notifier +from can import BusABC, AsyncBufferedReader, BufferedReader, Notifier, Message from uds.utilities import TimeMilliseconds -from uds.can import AbstractCanAddressingInformation +from uds.can import AbstractCanAddressingInformation, CanIdHandler, CanDlcHandler from uds.packet import CanPacket, CanPacketRecord from .abstract_can_transport_interface import AbstractCanTransportInterface @@ -47,6 +47,10 @@ def __init__(self, super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, **kwargs) + self.__async_listener = AsyncBufferedReader() + self.__sync_listener = BufferedReader() + self.__can_notifier = Notifier(bus=self.bus_manager, + listeners=[self.__async_listener, self.__sync_listener]) @property def n_as_measured(self) -> Optional[TimeMilliseconds]: @@ -93,7 +97,7 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: :return: True if provided bus object is compatible with this Transport Interface, False otherwise. """ - return isinstance(bus_manager, BusABC) + return isinstance(bus_manager, BusABC) # TODO: check that receive_own_messages is set def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore """ @@ -107,13 +111,11 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore """ if not isinstance(packet, CanPacket): raise TypeError("Provided packet value does not contain CAN Packet.") - # TODO: - # - make sure packet uses proper AddressingInformation - Warning - # - measure N_AS / N_AR - # - use CAN Bus to transmit packet - # - get confirmation from Bus Listener that the packet was received - # - make sure that `_packet_records_queue` (and `_message_records_queue' if needed) is updated - # - return record of transmitted packet + can_message = Message(arbitration_id=packet.can_id, + 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)) + self.bus_manager.send(can_message) async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: """ @@ -132,9 +134,4 @@ async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> Ca 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.") - # TODO: - # - update `_packet_records_queue` - # - measure N_As / N_Ar - # - use Bus Listener to filter out UDS Packets that targets this entity - # - make sure that `_packet_records_queue` (and `_message_records_queue' if needed) is updated - # - return record of received packet when received + From 79bf2856a73ae9c5edcc7b5776d5e57d255186cb Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 17 May 2023 12:21:43 +0200 Subject: [PATCH 10/40] Update python_can_transport_interface.py add TODOs --- uds/transport_interface/python_can_transport_interface.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index b1d7e7e5..ecd70f29 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -116,6 +116,8 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore data=packet.raw_frame_data, is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) self.bus_manager.send(can_message) + # TODO: get sent packet from self.__sync_listener - NOTE: we have to see sent messages + # TODO: create CanPacketRecord and return it async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: """ @@ -134,4 +136,5 @@ async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> Ca 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.") - + # TODO: wait till packet received by self.__async_listener + # TODO: create CanPacketRecord and return it From 6d77b4eee60935f17e1498aae9eb8bc542e02d82 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 17 May 2023 14:03:12 +0200 Subject: [PATCH 11/40] SCA update minor upgrade for static code analysis --- .github/workflows/ci.yml | 4 ---- tests/prospector_profile.yaml | 10 +++------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c633a760..85faa9ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,10 +133,6 @@ jobs: run: | prospector --profile tests/prospector_profile.yaml uds - - name: Execute static code analysis [pyroma] - run: | - pyroma -a . - dependency_checks: runs-on: ubuntu-latest diff --git a/tests/prospector_profile.yaml b/tests/prospector_profile.yaml index c31685d5..0cd940ad 100644 --- a/tests/prospector_profile.yaml +++ b/tests/prospector_profile.yaml @@ -6,6 +6,7 @@ doc-warnings: true autodetect: false test-warnings: true + # default tools configuration: http://prospector.landscape.io/en/master/supported_tools.html#defaults pep8: @@ -35,11 +36,6 @@ pep257: - D212 - D407 - D413 -# Should I add these? For details visit: -# https://stackoverflow.com/questions/66408022/what-is-the-reason-for-d407-error-raised-by-pydocstyle -# - D213 -# - D215 -# - D408 mccabe: run: true @@ -57,8 +53,8 @@ mypy: vulture: run: true -bandit: # there is probably no use of it (not applicable for this project), but it is run anyways to make sure everything is secured +bandit: # there is probably no use of it (not applicable for this project), but it is run anyway to make sure everything is secured run: true pyroma: - run: false + run: true From 7d121395489d4eea5166de790e1e051bf957f719 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 26 Jul 2023 14:26:08 +0200 Subject: [PATCH 12/40] tmp save changes --- tests/requirements_for_software_tests.txt | 4 +- tests/software_tests/conftest.py | 38 ++--- .../segmentation/test_abstract_segmenter.py | 20 ++- .../segmentation/test_can_segmenter.py | 16 +-- uds/can/abstract_addressing_information.py | 1 + uds/segmentation/abstract_segmenter.py | 52 ++++--- uds/segmentation/can_segmenter.py | 135 +++++++++--------- .../python_can_transport_interface.py | 14 +- 8 files changed, 161 insertions(+), 119 deletions(-) diff --git a/tests/requirements_for_software_tests.txt b/tests/requirements_for_software_tests.txt index 3fffb44b..99187e2a 100644 --- a/tests/requirements_for_software_tests.txt +++ b/tests/requirements_for_software_tests.txt @@ -1,4 +1,4 @@ -pytest>=6.2.2 +pytest>=6.0.0 pytest-cov>=3.0.0 pytest-asyncio>=0.17.2 -mock>=4.0.2 \ No newline at end of file +mock>=4.0.0 \ No newline at end of file diff --git a/tests/software_tests/conftest.py b/tests/software_tests/conftest.py index f7d8e814..5a20dd0d 100644 --- a/tests/software_tests/conftest.py +++ b/tests/software_tests/conftest.py @@ -52,22 +52,22 @@ def example_can_addressing_format(request): return request.param -@fixture(params=[ - CanSegmenter(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - physical_ai=dict(can_id=0x724), - functional_ai=dict(can_id=0x7FF)), - CanSegmenter(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, - physical_ai=dict(target_address=0x1E, source_address=0xFF), - functional_ai=dict(target_address=0xB1, source_address=0xFF), - dlc=0xF, - use_data_optimization=True, - filler_byte=0x71), - CanSegmenter(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, - physical_ai=dict(target_address=0xE0, can_id=0x129834), - functional_ai=dict(target_address=0xFF, can_id=0x12FFFF), - dlc=0xC, - use_data_optimization=True, - filler_byte=0x8E) -]) -def example_can_segmenter(request): - return request.param +# @fixture(params=[ +# CanSegmenter(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, +# physical_ai=dict(can_id=0x724), +# functional_ai=dict(can_id=0x7FF)), +# CanSegmenter(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, +# physical_ai=dict(target_address=0x1E, source_address=0xFF), +# functional_ai=dict(target_address=0xB1, source_address=0xFF), +# dlc=0xF, +# use_data_optimization=True, +# filler_byte=0x71), +# CanSegmenter(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, +# physical_ai=dict(target_address=0xE0, can_id=0x129834), +# functional_ai=dict(target_address=0xFF, can_id=0x12FFFF), +# dlc=0xC, +# use_data_optimization=True, +# filler_byte=0x8E) +# ]) +# def example_can_segmenter(request): +# return request.param diff --git a/tests/software_tests/segmentation/test_abstract_segmenter.py b/tests/software_tests/segmentation/test_abstract_segmenter.py index 210f417d..6ad9b636 100644 --- a/tests/software_tests/segmentation/test_abstract_segmenter.py +++ b/tests/software_tests/segmentation/test_abstract_segmenter.py @@ -19,14 +19,16 @@ def setup(self): @patch(f"{SCRIPT_PATH}.isinstance") def test_is_supported_packet(self, mock_isinstance, value, result): mock_isinstance.return_value = result - assert AbstractSegmenter.is_supported_packet(self=self.mock_abstract_segmenter, value=value) is result - mock_isinstance.assert_called_once_with(value, self.mock_abstract_segmenter.supported_packet_classes) + assert AbstractSegmenter.is_supported_packet_type(self=self.mock_abstract_segmenter, packet=value) is result + mock_isinstance.assert_called_once_with(value, (self.mock_abstract_segmenter.supported_packet_class, + self.mock_abstract_segmenter.supported_packet_record_class)) # is_supported_packets_sequence @pytest.mark.parametrize("value", [None, True, 1, Mock(), {1, 2, 3}]) def test_is_supported_packets_sequence__false__invalid_type(self, value): - assert AbstractSegmenter.is_supported_packets_sequence(self=self.mock_abstract_segmenter, value=value) is False + assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, + packets=value) is False self.mock_abstract_segmenter.is_supported_packet.assert_not_called() @pytest.mark.parametrize("value", [ @@ -38,7 +40,8 @@ def test_is_supported_packets_sequence__false__invalid_type(self, value): ]) def test_is_supported_packets_sequence__false__invalid_elements_type(self, value): self.mock_abstract_segmenter.is_supported_packet.return_value = False - assert AbstractSegmenter.is_supported_packets_sequence(self=self.mock_abstract_segmenter, value=value) is False + assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, + packets=value) is False self.mock_abstract_segmenter.is_supported_packet.assert_called_once() @pytest.mark.parametrize("value", [ @@ -47,12 +50,14 @@ def test_is_supported_packets_sequence__false__invalid_elements_type(self, value ]) def test_is_supported_packets_sequence__false__more_element_types(self, value): self.mock_abstract_segmenter.is_supported_packet.return_value = True - assert AbstractSegmenter.is_supported_packets_sequence(self=self.mock_abstract_segmenter, value=value) is False + assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, + packets=value) is False self.mock_abstract_segmenter.is_supported_packet.assert_called() def test_is_supported_packets_sequence__false__empty_sequence(self): self.mock_abstract_segmenter.is_supported_packet.return_value = True - assert AbstractSegmenter.is_supported_packets_sequence(self=self.mock_abstract_segmenter, value=[]) is False + assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, + packets=[]) is False @pytest.mark.parametrize("value", [ (1, 2, 3, 4), @@ -61,5 +66,6 @@ def test_is_supported_packets_sequence__false__empty_sequence(self): ]) def test_is_supported_packets_sequence__true(self, value): self.mock_abstract_segmenter.is_supported_packet.return_value = True - assert AbstractSegmenter.is_supported_packets_sequence(self=self.mock_abstract_segmenter, value=value) is True + assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, + packets=value) is True self.mock_abstract_segmenter.is_supported_packet.assert_called() diff --git a/tests/software_tests/segmentation/test_can_segmenter.py b/tests/software_tests/segmentation/test_can_segmenter.py index a922a7a2..7d877035 100644 --- a/tests/software_tests/segmentation/test_can_segmenter.py +++ b/tests/software_tests/segmentation/test_can_segmenter.py @@ -324,7 +324,7 @@ def test_segmentation__physical(self, mock_isinstance): def test_is_complete_packets_sequence__value_error(self, packets): self.mock_can_segmenter.is_supported_packets_sequence.return_value = False with pytest.raises(ValueError): - CanSegmenter.is_complete_packets_sequence(self=self.mock_can_segmenter, packets=packets) + CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) @pytest.mark.parametrize("packets", [ @@ -335,7 +335,7 @@ def test_is_complete_packets_sequence__not_implemented_error(self, packets): self.mock_can_segmenter.is_supported_packets_sequence.return_value = True self.mock_is_initial_packet_type.return_value = True with pytest.raises(NotImplementedError): - CanSegmenter.is_complete_packets_sequence(self=self.mock_can_segmenter, packets=packets) + CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) @@ -346,7 +346,7 @@ def test_is_complete_packets_sequence__not_implemented_error(self, packets): def test_is_complete_packets_sequence__false__not_initial_packet(self, packets): self.mock_can_segmenter.is_supported_packets_sequence.return_value = True self.mock_is_initial_packet_type.return_value = False - assert CanSegmenter.is_complete_packets_sequence(self=self.mock_can_segmenter, packets=packets) is False + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) @@ -358,7 +358,7 @@ def test_is_complete_packets_sequence__false__not_initial_packet(self, packets): def test_is_complete_packets_sequence__single_frame(self, packets): self.mock_can_segmenter.is_supported_packets_sequence.return_value = True self.mock_is_initial_packet_type.return_value = True - assert CanSegmenter.is_complete_packets_sequence(self=self.mock_can_segmenter, packets=packets) \ + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) \ is (len(packets) == 1) self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) @@ -370,7 +370,7 @@ def test_is_complete_packets_sequence__single_frame(self, packets): def test_is_complete_packets_sequence__first_frame__multiple_initial_packets(self, packets): self.mock_can_segmenter.is_supported_packets_sequence.return_value = True self.mock_is_initial_packet_type.return_value = True - assert CanSegmenter.is_complete_packets_sequence(self=self.mock_can_segmenter, packets=packets) is False + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) self.mock_is_initial_packet_type.assert_has_calls( [call(packets[0].packet_type), call(packets[1].packet_type)]) @@ -383,7 +383,7 @@ def test_is_complete_packets_sequence__first_frame__multiple_initial_packets(sel def test_is_complete_packets_sequence__first_frame__too_little_packets(self, packets): self.mock_can_segmenter.is_supported_packets_sequence.return_value = True self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] - assert CanSegmenter.is_complete_packets_sequence(self=self.mock_can_segmenter, packets=packets) is False + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) self.mock_is_initial_packet_type.assert_has_calls( [call(packet.packet_type) for packet in packets]) @@ -397,7 +397,7 @@ def test_is_complete_packets_sequence__first_frame__too_little_packets(self, pac def test_is_complete_packets_sequence__first_frame__too_many_packets(self, packets): self.mock_can_segmenter.is_supported_packets_sequence.return_value = True self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] - assert CanSegmenter.is_complete_packets_sequence(self=self.mock_can_segmenter, packets=packets) is False + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) self.mock_is_initial_packet_type.assert_has_calls( [call(packet.packet_type) for packet in packets]) @@ -410,7 +410,7 @@ def test_is_complete_packets_sequence__first_frame__too_many_packets(self, packe def test_is_complete_packets_sequence__first_frame__true(self, packets): self.mock_can_segmenter.is_supported_packets_sequence.return_value = True self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] - assert CanSegmenter.is_complete_packets_sequence(self=self.mock_can_segmenter, packets=packets) is True + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is True self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) self.mock_is_initial_packet_type.assert_has_calls( [call(packet.packet_type) for packet in packets]) diff --git a/uds/can/abstract_addressing_information.py b/uds/can/abstract_addressing_information.py index 05b4a6ab..6a455242 100644 --- a/uds/can/abstract_addressing_information.py +++ b/uds/can/abstract_addressing_information.py @@ -6,6 +6,7 @@ from abc import ABC, abstractmethod from copy import deepcopy +from uds.utilities import RawBytes from uds.transmission_attributes import AddressingTypeAlias, AddressingType from .addressing_format import CanAddressingFormatAlias from .frame_fields import CanIdHandler diff --git a/uds/segmentation/abstract_segmenter.py b/uds/segmentation/abstract_segmenter.py index acedb04d..2afbb181 100644 --- a/uds/segmentation/abstract_segmenter.py +++ b/uds/segmentation/abstract_segmenter.py @@ -2,11 +2,13 @@ __all__ = ["SegmentationError", "AbstractSegmenter"] -from typing import Tuple, Type, Union, Any +from typing import Optional, Union, Sequence, Type from abc import ABC, abstractmethod from uds.message import UdsMessage, UdsMessageRecord -from uds.packet import AbstractUdsPacketContainer, PacketsContainersSequence, PacketsTuple +from uds.packet import AbstractUdsPacketContainer, AbstractUdsPacket, AbstractUdsPacketRecord, \ + PacketsContainersSequence, PacketsTuple +from uds.transmission_attributes import AddressingType class SegmentationError(ValueError): @@ -27,38 +29,54 @@ class AbstractSegmenter(ABC): @property @abstractmethod - def supported_packet_classes(self) -> Tuple[Type[AbstractUdsPacketContainer], ...]: - """Classes that define packet objects supported by this segmenter.""" + def supported_packet_class(self) -> Type[AbstractUdsPacket]: + """Class of UDS Packet supported by this segmenter.""" - def is_supported_packet(self, value: Any) -> bool: + @property + @abstractmethod + def supported_packet_record_class(self) -> Type[AbstractUdsPacketRecord]: + """Class of UDS Packet Record supported by this segmenter.""" + + def is_supported_packet_type(self, packet: AbstractUdsPacketContainer) -> bool: """ Check if the argument value is a packet object of a supported type. - :param value: Value to check. + :param packet: Packet object to check. :return: True if provided value is an object of a supported packet type, False otherwise. """ - return isinstance(value, self.supported_packet_classes) # type: ignore + return isinstance(packet, (self.supported_packet_class, self.supported_packet_record_class)) + + @abstractmethod + def is_input_packet(self, **kwargs) -> Optional[AddressingType]: + """ + Check if provided frame attributes belong to a UDS packet that is an input for this Segmenter. + + :param kwargs: Attributes of a frame to check. - def is_supported_packets_sequence(self, value: Any) -> bool: + :return: Addressing Type used for transmission according to configuration of this Segmenter + if provided attributes belongs to an input UDS packet, otherwise None. """ - Check if the argument value is a packet sequence of a supported type. - :param value: Value to check. + def is_supported_packets_sequence_type(self, packets: Sequence[AbstractUdsPacketContainer]) -> bool: + """ + Check if the argument value is a packets sequence of a supported type. + + :param packets: Packets sequence to check. - :return: True if provided value is a packet sequence of a supported type, False otherwise. + :return: True if provided value is a packets sequence of a supported type, False otherwise. """ - if not isinstance(value, (list, tuple)): + if not isinstance(packets, Sequence): # not a sequence return False - if not all(self.is_supported_packet(element) for element in value): + if not all(self.is_supported_packet_type(packet) for packet in packets): # at least one element is not a packet of a supported type return False # check if all packets are the same type - return len({type(element) for element in value}) == 1 + return len({type(packet) for packet in packets}) == 1 @abstractmethod - def is_complete_packets_sequence(self, packets: PacketsContainersSequence) -> bool: + def is_desegmented_message(self, packets: PacketsContainersSequence) -> bool: """ Check whether provided packets are full sequence of packets that form exactly one diagnostic message. @@ -77,7 +95,7 @@ def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage :raise SegmentationError: Provided packets are not a complete packet sequence that form a diagnostic message. - :return: A diagnostic message that is an outcome of UDS packets desegmentation. + :return: A diagnostic message that is carried by provided UDS packets. """ @abstractmethod @@ -89,5 +107,5 @@ def segmentation(self, message: UdsMessage) -> PacketsTuple: :raise SegmentationError: Provided diagnostic message cannot be segmented. - :return: UDS packets that are an outcome of UDS message segmentation. + :return: UDS packet(s) that carry provided diagnostic message. """ diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py index fb5a5da9..98e30c03 100644 --- a/uds/segmentation/can_segmenter.py +++ b/uds/segmentation/can_segmenter.py @@ -2,7 +2,7 @@ __all__ = ["CanSegmenter"] -from typing import Union, Tuple, Type +from typing import Optional, Union, Tuple, Type from copy import copy from uds.utilities import RawBytesList, validate_raw_byte @@ -11,97 +11,86 @@ CanAddressingFormat, CanAddressingFormatAlias, \ CanDlcHandler, CanSingleFrameHandler, CanFirstFrameHandler, CanConsecutiveFrameHandler, DEFAULT_FILLER_BYTE from uds.packet import CanPacket, CanPacketRecord, CanPacketType, \ - AbstractUdsPacketContainer, PacketsContainersSequence, PacketsTuple + AbstractUdsPacket, AbstractUdsPacketRecord, AbstractUdsPacketContainer, PacketsContainersSequence, PacketsTuple from uds.message import UdsMessage, UdsMessageRecord from .abstract_segmenter import AbstractSegmenter, SegmentationError -InputAiParamsAlias = Union[AbstractCanAddressingInformation.InputAIParamsAlias, - AbstractCanAddressingInformation.PacketAIParamsAlias] -"""Alias of :ref:`Addressing Information ` configuration parameters.""" - - class CanSegmenter(AbstractSegmenter): """Segmenter class that provides utilities for segmentation and desegmentation on CAN bus.""" def __init__(self, *, - addressing_format: CanAddressingFormatAlias, - physical_ai: InputAiParamsAlias, - functional_ai: InputAiParamsAlias, + addressing_information: AbstractCanAddressingInformation, dlc: int = CanDlcHandler.MIN_BASE_UDS_DLC, use_data_optimization: bool = False, filler_byte: int = DEFAULT_FILLER_BYTE) -> None: """ Configure CAN Segmenter. - :param addressing_format: CAN Addressing format used. - :param physical_ai: CAN Addressing Information parameters to use for physically addressed communication. - :param functional_ai: CAN Addressing Information parameters to use for functionally addressed communication. - :param dlc: Base CAN DLC value to use for CAN Packets. - :param use_data_optimization: Information whether to use CAN Frame Data Optimization during segmentation. - :param filler_byte: Filler byte value to use for CAN Frame Data Padding during segmentation. + :param addressing_information: Addressing Information configuration of a CAN entity. + :param dlc: Base CAN DLC value to use for creating CAN Packets. + :param use_data_optimization: Information whether to use CAN Frame Data Optimization in created CAN Packets + during segmentation. + :param filler_byte: Filler byte value to use for CAN Frame Data Padding in created CAN Packets during + segmentation. """ - CanAddressingFormat.validate_member(addressing_format) - self.__addressing_format: CanAddressingFormatAlias = CanAddressingFormat(addressing_format) - self.physical_ai = physical_ai # type: ignore - self.functional_ai = functional_ai # type: ignore + self.addressing_information = addressing_information self.dlc = dlc self.use_data_optimization = use_data_optimization self.filler_byte = filler_byte @property - def supported_packet_classes(self) -> Tuple[Type[AbstractUdsPacketContainer], ...]: - """Classes that define packet objects supported by this segmenter.""" - return CanPacket, CanPacketRecord + def supported_packet_class(self) -> Type[AbstractUdsPacket]: + """Class of UDS Packet supported by CAN segmenter.""" + return CanPacket + + @property + def supported_packet_record_class(self) -> Type[AbstractUdsPacketRecord]: + """Class of UDS Packet Record supported by CAN segmenter.""" + return CanPacketRecord @property def addressing_format(self) -> CanAddressingFormatAlias: """CAN Addressing format used.""" - return self.__addressing_format + return self.addressing_information.addressing_format @property - def physical_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: - """CAN Addressing Information parameters used for outgoing physically addressed communication.""" - return copy(self.__physical_ai) + def rx_packets_physical_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + """Addressing Information parameters of incoming physically addressed CAN packets.""" + return self.addressing_information.rx_packets_physical_ai - @physical_ai.setter - def physical_ai(self, value: InputAiParamsAlias): - """ - Set Addressing Information parameters of physically addressed CAN packets. + @property + def tx_packets_physical_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + """Addressing Information parameters of outgoing physically addressed CAN packets.""" + return self.addressing_information.tx_packets_physical_ai - :param value: Addressing Information parameters to set. - """ - kwargs = { - AbstractCanAddressingInformation.ADDRESSING_FORMAT_NAME: self.addressing_format, - AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME: AddressingType.PHYSICAL - } - for item_name, item_value in value.items(): - if item_name not in (AbstractCanAddressingInformation.ADDRESSING_FORMAT_NAME, - AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME): - kwargs[item_name] = item_value # type: ignore - self.__physical_ai = CanAddressingInformation.validate_packet_ai(**kwargs) # type: ignore + @property + def rx_packets_functional_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + """Addressing Information parameters of incoming functionally addressed CAN packets.""" + return self.addressing_information.rx_packets_functional_ai + + @property + def tx_packets_functional_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + """Addressing Information parameters of outgoing functionally addressed CAN packets.""" + return self.addressing_information.tx_packets_functional_ai @property - def functional_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: - """CAN Addressing Information parameters used for outgoing functionally addressed communication.""" - return copy(self.__functional_ai) + def addressing_information(self) -> AbstractCanAddressingInformation: + """Addressing Information configuration of a CAN entity.""" + return self.__addressing_information - @functional_ai.setter - def functional_ai(self, value: InputAiParamsAlias): + @addressing_information.setter + def addressing_information(self, value: AbstractCanAddressingInformation): """ - Set Addressing Information parameters of functionally addressed CAN packets. + Set Addressing Information configuration to be used for segmentation and desegmentation. - :param value: Addressing Information parameters to set. + :param value: Addressing Information configuration to set. + + :raise TypeError: Provided value has unexpected type. """ - kwargs = { - AbstractCanAddressingInformation.ADDRESSING_FORMAT_NAME: self.addressing_format, - AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME: AddressingType.FUNCTIONAL - } - for item_name, item_value in value.items(): - if item_name not in (AbstractCanAddressingInformation.ADDRESSING_FORMAT_NAME, - AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME): - kwargs[item_name] = item_value # type: ignore - self.__functional_ai = CanAddressingInformation.validate_packet_ai(**kwargs) # type: ignore + if not isinstance(value, AbstractCanAddressingInformation): + raise TypeError(f"Provided `value` is not CAN Addressing Information type. Actual type: {type(value)}") + self.__addressing_information: AbstractCanAddressingInformation = value @property def dlc(self) -> int: @@ -158,7 +147,23 @@ def filler_byte(self, value: int): validate_raw_byte(value) self.__filler_byte: int = value - def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage, UdsMessageRecord]: + def is_input_packet(self, **kwargs) -> Optional[AddressingType]: + """ + Check if provided CAN frame attributes belong to a CAN packet that is an input for this CAN Segmenter. + + :param kwargs: Attributes of a CAN frame to check. + - can_id: CAN Identifier of a CAN Frame + - data: Raw data bytes of a CAN frame + + :return: Addressing Type used for transmission if provided attributes belongs to an input CAN packet, + otherwise None. + """ + can_id = kwargs.pop("can_id", None) + data = kwargs.pop("data", None) + self.addressing_information. + + + def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage, UdsMessageRecord]: # TODO: review """ Perform desegmentation of CAN packets. @@ -171,7 +176,7 @@ def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage :return: A diagnostic message that is an outcome of CAN packets desegmentation. """ - if not self.is_complete_packets_sequence(packets): + if not self.is_desegmented_message(packets): raise SegmentationError("Provided packets are not a complete packets sequence") if isinstance(packets[0], CanPacketRecord): return UdsMessageRecord(packets) # type: ignore @@ -210,7 +215,7 @@ def segmentation(self, message: UdsMessage) -> PacketsTuple: return self.__functional_segmentation(message) raise NotImplementedError(f"Unknown addressing type received: {message.addressing_type}") - def is_complete_packets_sequence(self, packets: PacketsContainersSequence) -> bool: + def is_desegmented_message(self, packets: PacketsContainersSequence) -> bool: # TODO: review """ Check whether provided packets are full sequence of packets that form exactly one diagnostic message. @@ -224,7 +229,7 @@ def is_complete_packets_sequence(self, packets: PacketsContainersSequence) -> bo :return: True if the packets form exactly one diagnostic message. False if there are missing, additional or inconsistent (e.g. two packets that initiate a message) packets. """ - if not self.is_supported_packets_sequence(packets): + if not self.is_supported_packets_sequence_type(packets): raise ValueError("Provided packets are not consistent CAN Packets sequence.") if not CanPacketType.is_initial_packet_type(packets[0].packet_type): return False @@ -243,7 +248,9 @@ def is_complete_packets_sequence(self, packets: PacketsContainersSequence) -> bo return payload_bytes_found >= total_payload_size # type: ignore raise NotImplementedError(f"Unknown packet type received: {packets[0].packet_type}") - def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: + + + def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: rework """ Segment physically addressed diagnostic message. @@ -292,7 +299,7 @@ def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: consecutive_frames.append(consecutive_frame) return (first_frame, *consecutive_frames) - def __functional_segmentation(self, message: UdsMessage) -> PacketsTuple: + def __functional_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: rework """ Segment functionally addressed diagnostic message. diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py index ecd70f29..68bc094c 100644 --- a/uds/transport_interface/python_can_transport_interface.py +++ b/uds/transport_interface/python_can_transport_interface.py @@ -13,6 +13,7 @@ from uds.utilities import TimeMilliseconds from uds.can import AbstractCanAddressingInformation, CanIdHandler, CanDlcHandler from uds.packet import CanPacket, CanPacketRecord +from uds.transmission_attributes import TransmissionDirection from .abstract_can_transport_interface import AbstractCanTransportInterface @@ -136,5 +137,14 @@ async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> Ca 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.") - # TODO: wait till packet received by self.__async_listener - # TODO: create CanPacketRecord and return it + received_frame = await self.__async_listener.get_message() + while False: # TODO: check if received message is either physically or functionally addressed packet directed for this node + received_message = await self.__async_listener.get_message() + packet_addressing_type = ... # TODO: extract + packet_addressing_format = ... # TODO: extract + received_datetime = ... # TODO: extract + return CanPacketRecord(frame=received_frame, + direction=TransmissionDirection.RECEIVED, + addressing_type=packet_addressing_type, + addressing_format=packet_addressing_format, + transmission_time=received_datetime) From 9a12faef3c149cbeb983f73d2f145e4a1d538d4d Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 17 Aug 2023 12:04:02 +0200 Subject: [PATCH 13/40] adjust abstract classes Rework abstract classes and get rid of LGTM errors suppression (no longer in use). --- .../test_abstract_transport_interface.py | 23 ++++--------------- uds/can/abstract_addressing_information.py | 5 ---- uds/packet/can_packet.py | 4 ++-- uds/packet/can_packet_record.py | 2 +- uds/segmentation/abstract_segmenter.py | 6 ++--- .../abstract_transport_interface.py | 2 -- 6 files changed, 10 insertions(+), 32 deletions(-) diff --git a/tests/software_tests/transport_interface/test_abstract_transport_interface.py b/tests/software_tests/transport_interface/test_abstract_transport_interface.py index c959f7e5..208b46da 100644 --- a/tests/software_tests/transport_interface/test_abstract_transport_interface.py +++ b/tests/software_tests/transport_interface/test_abstract_transport_interface.py @@ -1,15 +1,15 @@ import pytest from mock import Mock -from uds.transport_interface.abstract_transport_interface import AbstractTransportInterface, AbstractUdsPacket +from uds.transport_interface.abstract_transport_interface import AbstractTransportInterface + + +SCRIPT_LOCATION = "uds.transport_interface.abstract_transport_interface" -@pytest.mark.skip class TestAbstractTransportInterface: """Unit tests for `AbstractTransportInterface` class.""" - SCRIPT_LOCATION = "uds.transport_interface.abstract_transport_interface" - def setup(self): self.mock_transport_interface = Mock(spec=AbstractTransportInterface) @@ -39,18 +39,3 @@ def test_init__valid(self, bus_manager, message_records_number, packet_records_n def test_bus_manager(self, value): self.mock_transport_interface._AbstractTransportInterface__bus_manager = value assert AbstractTransportInterface.bus_manager.fget(self.mock_transport_interface) == value - - # send_packet - - @pytest.mark.asyncio - async def test_send_packet(self): - with pytest.raises(NotImplementedError): - await AbstractTransportInterface.send_packet(self=self.mock_transport_interface, - packet=Mock(spec=AbstractUdsPacket)) - - # receive_packet - - @pytest.mark.asyncio - async def test_receive_packet(self): - with pytest.raises(NotImplementedError): - await AbstractTransportInterface.receive_packet(self=self.mock_transport_interface) diff --git a/uds/can/abstract_addressing_information.py b/uds/can/abstract_addressing_information.py index 6a455242..ccbfc0a9 100644 --- a/uds/can/abstract_addressing_information.py +++ b/uds/can/abstract_addressing_information.py @@ -6,7 +6,6 @@ from abc import ABC, abstractmethod from copy import deepcopy -from uds.utilities import RawBytes from uds.transmission_attributes import AddressingTypeAlias, AddressingType from .addressing_format import CanAddressingFormatAlias from .frame_fields import CanIdHandler @@ -88,7 +87,6 @@ def rx_packets_physical_ai(self, value: InputAIParamsAlias): :param value: Addressing Information parameters to set. """ - # lgtm [py/procedure-return-value-used] self.__rx_packets_physical_ai: AbstractCanAddressingInformation.PacketAIParamsAlias \ = self.validate_packet_ai(**{self.ADDRESSING_TYPE_NAME: AddressingType.PHYSICAL}, **value) @@ -104,7 +102,6 @@ def tx_packets_physical_ai(self, value: InputAIParamsAlias): :param value: Addressing Information parameters to set. """ - # lgtm [py/procedure-return-value-used] self.__tx_packets_physical_ai: AbstractCanAddressingInformation.PacketAIParamsAlias \ = self.validate_packet_ai(**{self.ADDRESSING_TYPE_NAME: AddressingType.PHYSICAL}, **value) @@ -120,7 +117,6 @@ def rx_packets_functional_ai(self, value: InputAIParamsAlias): :param value: Addressing Information parameters to set. """ - # lgtm [py/procedure-return-value-used] self.__rx_packets_functional_ai: AbstractCanAddressingInformation.PacketAIParamsAlias \ = self.validate_packet_ai(**{self.ADDRESSING_TYPE_NAME: AddressingType.FUNCTIONAL}, **value) @@ -136,7 +132,6 @@ def tx_packets_functional_ai(self, value: InputAIParamsAlias): :param value: Addressing Information parameters to set. """ - # lgtm [py/procedure-return-value-used] self.__tx_packets_functional_ai: AbstractCanAddressingInformation.PacketAIParamsAlias \ = self.validate_packet_ai(**{self.ADDRESSING_TYPE_NAME: AddressingType.FUNCTIONAL}, **value) diff --git a/uds/packet/can_packet.py b/uds/packet/can_packet.py index 04909fae..ad56bcc9 100644 --- a/uds/packet/can_packet.py +++ b/uds/packet/can_packet.py @@ -18,7 +18,7 @@ from .can_packet_type import CanPacketType, CanPacketTypeAlias -class CanPacket(AbstractCanPacketContainer, AbstractUdsPacket): # lgtm [py/conflicting-attributes] +class CanPacket(AbstractCanPacketContainer, AbstractUdsPacket): """ Definition of a CAN packet. @@ -562,7 +562,7 @@ def __update_ai_data_byte(self) -> None: self.__raw_frame_data = tuple(ai_data_bytes + list(self.__raw_frame_data[len(ai_data_bytes):])) -class AnyCanPacket(AbstractCanPacketContainer, AbstractUdsPacket): # lgtm [py/conflicting-attributes] +class AnyCanPacket(AbstractCanPacketContainer, AbstractUdsPacket): """ Definition of a CAN packet in any format. diff --git a/uds/packet/can_packet_record.py b/uds/packet/can_packet_record.py index ab858963..6906a390 100644 --- a/uds/packet/can_packet_record.py +++ b/uds/packet/can_packet_record.py @@ -20,7 +20,7 @@ """Alias of supported CAN frames objects.""" -class CanPacketRecord(AbstractCanPacketContainer, AbstractUdsPacketRecord): # lgtm [py/conflicting-attributes] +class CanPacketRecord(AbstractCanPacketContainer, AbstractUdsPacketRecord): """ Definition of a CAN packet Record. diff --git a/uds/segmentation/abstract_segmenter.py b/uds/segmentation/abstract_segmenter.py index 2afbb181..b35939bd 100644 --- a/uds/segmentation/abstract_segmenter.py +++ b/uds/segmentation/abstract_segmenter.py @@ -50,12 +50,12 @@ def is_supported_packet_type(self, packet: AbstractUdsPacketContainer) -> bool: @abstractmethod def is_input_packet(self, **kwargs) -> Optional[AddressingType]: """ - Check if provided frame attributes belong to a UDS packet that is an input for this Segmenter. + Check if provided frame attributes belong to a UDS packet which is an input for this Segmenter. :param kwargs: Attributes of a frame to check. - :return: Addressing Type used for transmission according to configuration of this Segmenter - if provided attributes belongs to an input UDS packet, otherwise None. + :return: Addressing Type used for transmission of this UDS packet according to the configuration of this + Segmenter (if provided attributes belongs to an input UDS packet), otherwise None. """ def is_supported_packets_sequence_type(self, packets: Sequence[AbstractUdsPacketContainer]) -> bool: diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index e1e1c636..6f188a66 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -69,7 +69,6 @@ def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: :return: Record with historic information about transmitted UDS packet. """ - raise NotImplementedError @abstractmethod async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> AbstractUdsPacketRecord: @@ -82,4 +81,3 @@ async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> Ab :return: Record with historic information about received UDS packet. """ - raise NotImplementedError From 3e50fefd99d3e048ed70ac34f052788cd727f49c Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 23 Aug 2023 14:17:17 +0200 Subject: [PATCH 14/40] addressing information fix Change dictionaries used by addressing information classes to always contain every attribute - to make it compatible and easily replaced by other addressing information types. --- .../can/test_addressing_information.py | 64 +++++-- .../test_extended_addressing_information.py | 2 + .../can/test_mixed_addressing_information.py | 2 + .../can/test_normal_addressing_information.py | 9 +- ...ace.py => test_can_transport_interface.py} | 150 +++++++++++++--- .../test_python_can_transport_interface.py | 119 ------------- uds/can/__init__.py | 2 +- uds/can/abstract_addressing_information.py | 43 +++-- uds/can/addressing_information.py | 4 +- uds/can/extended_addressing_information.py | 18 +- uds/can/mixed_addressing_information.py | 52 +++--- uds/can/normal_addressing_information.py | 49 +++--- uds/segmentation/can_segmenter.py | 27 +-- uds/transport_interface/__init__.py | 3 +- ...nterface.py => can_transport_interface.py} | 166 ++++++++++++++++-- .../python_can_transport_interface.py | 150 ---------------- 16 files changed, 443 insertions(+), 417 deletions(-) rename tests/software_tests/transport_interface/{test_abstract_can_transport_interface.py => test_can_transport_interface.py} (80%) delete mode 100644 tests/software_tests/transport_interface/test_python_can_transport_interface.py rename uds/transport_interface/{abstract_can_transport_interface.py => can_transport_interface.py} (63%) delete mode 100644 uds/transport_interface/python_can_transport_interface.py diff --git a/tests/software_tests/can/test_addressing_information.py b/tests/software_tests/can/test_addressing_information.py index d877f096..0013fa11 100644 --- a/tests/software_tests/can/test_addressing_information.py +++ b/tests/software_tests/can/test_addressing_information.py @@ -259,16 +259,28 @@ class TestCanAddressingInformationIntegration: "tx_functional": {"can_id": 0x6FF}}, {"rx_packets_physical_ai": {"addressing_format": CanAddressingFormat.NORMAL_11BIT_ADDRESSING, "addressing_type": AddressingType.PHYSICAL, - "can_id": 0x601}, + "can_id": 0x601, + "target_address": None, + "source_address": None, + "address_extension": None}, "tx_packets_physical_ai": {"addressing_format": CanAddressingFormat.NORMAL_11BIT_ADDRESSING, "addressing_type": AddressingType.PHYSICAL, - "can_id": 0x602}, + "can_id": 0x602, + "target_address": None, + "source_address": None, + "address_extension": None}, "rx_packets_functional_ai": {"addressing_format": CanAddressingFormat.NORMAL_11BIT_ADDRESSING, "addressing_type": AddressingType.FUNCTIONAL, - "can_id": 0x6FE}, + "can_id": 0x6FE, + "target_address": None, + "source_address": None, + "address_extension": None}, "tx_packets_functional_ai": {"addressing_format": CanAddressingFormat.NORMAL_11BIT_ADDRESSING, "addressing_type": AddressingType.FUNCTIONAL, - "can_id": 0x6FF}}), + "can_id": 0x6FF, + "target_address": None, + "source_address": None, + "address_extension": None}}), ({"addressing_format": CanAddressingFormat.NORMAL_FIXED_ADDRESSING, "rx_physical": {"can_id": 0x18DA0E2B}, "tx_physical": {"target_address": 0x2B, "source_address": 0x0E}, @@ -278,22 +290,26 @@ class TestCanAddressingInformationIntegration: "addressing_type": AddressingType.PHYSICAL, "can_id": 0x18DA0E2B, "target_address": 0x0E, - "source_address": 0x2B}, + "source_address": 0x2B, + "address_extension": None}, "tx_packets_physical_ai": {"addressing_format": CanAddressingFormat.NORMAL_FIXED_ADDRESSING, "addressing_type": AddressingType.PHYSICAL, "can_id": 0x18DA2B0E, "target_address": 0x2B, - "source_address": 0x0E}, + "source_address": 0x0E, + "address_extension": None}, "rx_packets_functional_ai": {"addressing_format": CanAddressingFormat.NORMAL_FIXED_ADDRESSING, "addressing_type": AddressingType.FUNCTIONAL, "can_id": 0x18DBFEDC, "target_address": 0xFE, - "source_address": 0xDC}, + "source_address": 0xDC, + "address_extension": None}, "tx_packets_functional_ai": {"addressing_format": CanAddressingFormat.NORMAL_FIXED_ADDRESSING, "addressing_type": AddressingType.FUNCTIONAL, "can_id": 0x18DBFD92, "target_address": 0xFD, - "source_address": 0x92}}), + "source_address": 0x92, + "address_extension": None}}), ({"addressing_format": CanAddressingFormat.EXTENDED_ADDRESSING, "rx_physical": {"can_id": 0x621, "target_address": 0x1F}, "tx_physical": {"can_id": 0x621, "target_address": 0x91}, @@ -302,19 +318,27 @@ class TestCanAddressingInformationIntegration: {"rx_packets_physical_ai": {"addressing_format": CanAddressingFormat.EXTENDED_ADDRESSING, "addressing_type": AddressingType.PHYSICAL, "can_id": 0x621, - "target_address": 0x1F}, + "target_address": 0x1F, + "source_address": None, + "address_extension": None}, "tx_packets_physical_ai": {"addressing_format": CanAddressingFormat.EXTENDED_ADDRESSING, "addressing_type": AddressingType.PHYSICAL, "can_id": 0x621, - "target_address": 0x91}, + "target_address": 0x91, + "source_address": None, + "address_extension": None}, "rx_packets_functional_ai": {"addressing_format": CanAddressingFormat.EXTENDED_ADDRESSING, "addressing_type": AddressingType.FUNCTIONAL, "can_id": 0x12345, - "target_address": 0x82}, + "target_address": 0x82, + "source_address": None, + "address_extension": None}, "tx_packets_functional_ai": {"addressing_format": CanAddressingFormat.EXTENDED_ADDRESSING, "addressing_type": AddressingType.FUNCTIONAL, "can_id": 0x12346, - "target_address": 0x83}}), + "target_address": 0x83, + "source_address": None, + "address_extension": None}}), ({"addressing_format": CanAddressingFormat.MIXED_11BIT_ADDRESSING, "rx_physical": {"can_id": 0x641, "address_extension": 0x00}, "tx_physical": {"can_id": 0x642, "address_extension": 0xFF}, @@ -323,19 +347,27 @@ class TestCanAddressingInformationIntegration: {"rx_packets_physical_ai": {"addressing_format": CanAddressingFormat.MIXED_11BIT_ADDRESSING, "addressing_type": AddressingType.PHYSICAL, "can_id": 0x641, - "address_extension": 0x00}, + "address_extension": 0x00, + "target_address": None, + "source_address": None}, "tx_packets_physical_ai": {"addressing_format": CanAddressingFormat.MIXED_11BIT_ADDRESSING, "addressing_type": AddressingType.PHYSICAL, "can_id": 0x642, - "address_extension": 0xFF}, + "address_extension": 0xFF, + "target_address": None, + "source_address": None}, "rx_packets_functional_ai": {"addressing_format": CanAddressingFormat.MIXED_11BIT_ADDRESSING, "addressing_type": AddressingType.FUNCTIONAL, "can_id": 0x6DE, - "address_extension": 0xFE}, + "address_extension": 0xFE, + "target_address": None, + "source_address": None}, "tx_packets_functional_ai": {"addressing_format": CanAddressingFormat.MIXED_11BIT_ADDRESSING, "addressing_type": AddressingType.FUNCTIONAL, "can_id": 0x6DF, - "address_extension": 0xFF}}), + "address_extension": 0xFF, + "target_address": None, + "source_address": None}}), ({"addressing_format": CanAddressingFormat.MIXED_29BIT_ADDRESSING, "rx_physical": {"can_id": 0x18CE1234, "target_address": 0x12, "source_address": 0x34, "address_extension": 0x00}, "tx_physical": {"can_id": 0x18CEFF0F, "target_address": 0xFF, "address_extension": 0x5E}, diff --git a/tests/software_tests/can/test_extended_addressing_information.py b/tests/software_tests/can/test_extended_addressing_information.py index b19bccd5..47c7e8d8 100644 --- a/tests/software_tests/can/test_extended_addressing_information.py +++ b/tests/software_tests/can/test_extended_addressing_information.py @@ -73,6 +73,8 @@ def test_validate_packet_ai__valid(self, addressing_type, can_id, target_address AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME: addressing_type, AbstractCanAddressingInformation.CAN_ID_NAME: can_id, AbstractCanAddressingInformation.TARGET_ADDRESS_NAME: target_address, + AbstractCanAddressingInformation.SOURCE_ADDRESS_NAME: None, + AbstractCanAddressingInformation.ADDRESS_EXTENSION_NAME: None, } self.mock_can_id_handler_class.validate_can_id.assert_called_once_with(can_id) self.mock_can_id_handler_class.is_extended_addressed_can_id.assert_called_once_with(can_id) diff --git a/tests/software_tests/can/test_mixed_addressing_information.py b/tests/software_tests/can/test_mixed_addressing_information.py index d1275ba5..0f3cb68e 100644 --- a/tests/software_tests/can/test_mixed_addressing_information.py +++ b/tests/software_tests/can/test_mixed_addressing_information.py @@ -73,6 +73,8 @@ def test_validate_ai_mixed_11bit__valid(self, addressing_type, can_id, address_e AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME: addressing_type, AbstractCanAddressingInformation.CAN_ID_NAME: can_id, AbstractCanAddressingInformation.ADDRESS_EXTENSION_NAME: address_extension, + AbstractCanAddressingInformation.TARGET_ADDRESS_NAME: None, + AbstractCanAddressingInformation.SOURCE_ADDRESS_NAME: None, } self.mock_can_id_handler_class.validate_can_id.assert_called_once_with(can_id) self.mock_can_id_handler_class.is_mixed_11bit_addressed_can_id.assert_called_once_with(can_id) diff --git a/tests/software_tests/can/test_normal_addressing_information.py b/tests/software_tests/can/test_normal_addressing_information.py index 66d133dc..9f10416a 100644 --- a/tests/software_tests/can/test_normal_addressing_information.py +++ b/tests/software_tests/can/test_normal_addressing_information.py @@ -66,7 +66,10 @@ def test_validate_packet_ai__valid(self, addressing_type, can_id): can_id=can_id) == { AbstractCanAddressingInformation.ADDRESSING_FORMAT_NAME: CanAddressingFormat.NORMAL_11BIT_ADDRESSING, AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME: addressing_type, - AbstractCanAddressingInformation.CAN_ID_NAME: can_id + AbstractCanAddressingInformation.CAN_ID_NAME: can_id, + AbstractCanAddressingInformation.TARGET_ADDRESS_NAME: None, + AbstractCanAddressingInformation.SOURCE_ADDRESS_NAME: None, + AbstractCanAddressingInformation.ADDRESS_EXTENSION_NAME: None, } self.mock_can_id_handler_class.validate_can_id.assert_called_once_with(can_id) self.mock_can_id_handler_class.is_normal_11bit_addressed_can_id.assert_called_once_with(can_id) @@ -160,7 +163,8 @@ def test_validate_packet_ai__valid_without_can_id(self, addressing_type, target_ AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME: addressing_type, AbstractCanAddressingInformation.CAN_ID_NAME: self.mock_can_id_handler_class.encode_normal_fixed_addressed_can_id.return_value, AbstractCanAddressingInformation.TARGET_ADDRESS_NAME: target_address, - AbstractCanAddressingInformation.SOURCE_ADDRESS_NAME: source_address + AbstractCanAddressingInformation.SOURCE_ADDRESS_NAME: source_address, + AbstractCanAddressingInformation.ADDRESS_EXTENSION_NAME: None, } self.mock_validate_addressing_type.assert_called_once_with(addressing_type) self.mock_validate_raw_byte.assert_has_calls([call(target_address), call(source_address)], any_order=True) @@ -197,6 +201,7 @@ def test_validate_packet_ai__valid_with_can_id(self, addressing_type, can_id, ta AbstractCanAddressingInformation.CAN_ID_NAME: can_id, AbstractCanAddressingInformation.TARGET_ADDRESS_NAME: decoded_target_address, AbstractCanAddressingInformation.SOURCE_ADDRESS_NAME: decoded_source_address, + AbstractCanAddressingInformation.ADDRESS_EXTENSION_NAME: None, } self.mock_validate_addressing_type.assert_called_once_with(addressing_type) self.mock_validate_raw_byte.assert_not_called() diff --git a/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py b/tests/software_tests/transport_interface/test_can_transport_interface.py similarity index 80% rename from tests/software_tests/transport_interface/test_abstract_can_transport_interface.py rename to tests/software_tests/transport_interface/test_can_transport_interface.py index 431864f7..45ccfd00 100644 --- a/tests/software_tests/transport_interface/test_abstract_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -1,33 +1,31 @@ import pytest from mock import MagicMock, Mock, patch -from uds.transport_interface.abstract_can_transport_interface import AbstractCanTransportInterface, \ - AbstractCanAddressingInformation +from uds.transport_interface.can_transport_interface import AbstractCanTransportInterface, PyCanTransportInterface, \ + AbstractCanAddressingInformation, BusABC +from uds.can import CanAddressingInformation, CanAddressingFormat + + +SCRIPT_LOCATION = "uds.transport_interface.can_transport_interface" -@pytest.mark.skip class TestAbstractCanTransportInterface: """Unit tests for `AbstractCanTransportInterface` class.""" - SCRIPT_LOCATION = "uds.transport_interface.abstract_can_transport_interface" - def setup(self): self.mock_can_transport_interface = Mock(spec=AbstractCanTransportInterface) # patching self._patcher_abstract_transport_interface_init \ - = patch(f"{self.SCRIPT_LOCATION}.AbstractTransportInterface.__init__") + = patch(f"{SCRIPT_LOCATION}.AbstractTransportInterface.__init__") self.mock_abstract_transport_interface_init = self._patcher_abstract_transport_interface_init.start() - self._patcher_can_segmenter_class = patch(f"{self.SCRIPT_LOCATION}.CanSegmenter") + self._patcher_can_segmenter_class = patch(f"{SCRIPT_LOCATION}.CanSegmenter") self.mock_can_segmenter_class = self._patcher_can_segmenter_class.start() - self._patcher_can_packet_class = patch(f"{self.SCRIPT_LOCATION}.CanPacket") - self.mock_can_packet_class = self._patcher_can_packet_class.start() - self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") + self._patcher_warn = patch(f"{SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() def teardown(self): self._patcher_abstract_transport_interface_init.stop() self._patcher_can_segmenter_class.stop() - self._patcher_can_packet_class.stop() self._patcher_warn.stop() # __init__ @@ -47,9 +45,8 @@ def test_init__type_error(self, mock_isinstance, mock_isinstance.assert_called_once_with(addressing_information, AbstractCanAddressingInformation) @pytest.mark.parametrize("can_bus_manager, addressing_information", [ - ("can_bus_manager", Mock(tx_packets_physical_ai={"addressing_format": Mock(), - "addressing_type": Mock()})), - (Mock(), Mock(tx_packets_physical_ai={"arg1": 1, "arg2": 2})), + ("can_bus_manager", "some addressing information"), + (Mock(), Mock()), ]) @patch(f"{SCRIPT_LOCATION}.isinstance") def test_init__valid_mandatory_args(self, mock_isinstance, @@ -60,10 +57,7 @@ def test_init__valid_mandatory_args(self, mock_isinstance, addressing_information=addressing_information) mock_isinstance.assert_called_once_with(addressing_information, AbstractCanAddressingInformation) self.mock_abstract_transport_interface_init.assert_called_once_with(bus_manager=can_bus_manager) - self.mock_can_segmenter_class.assert_called_once_with( - addressing_format=addressing_information.addressing_format, - physical_ai=addressing_information.tx_packets_physical_ai, - functional_ai=addressing_information.tx_packets_functional_ai) + self.mock_can_segmenter_class.assert_called_once_with(addressing_information=addressing_information) assert self.mock_can_transport_interface._AbstractCanTransportInterface__addressing_information \ == addressing_information assert self.mock_can_transport_interface._AbstractCanTransportInterface__segmenter \ @@ -76,9 +70,8 @@ def test_init__valid_mandatory_args(self, mock_isinstance, assert self.mock_can_transport_interface.n_cr_timeout == self.mock_can_transport_interface.N_CR_TIMEOUT @pytest.mark.parametrize("can_bus_manager, addressing_information", [ - ("can_bus_manager", Mock(tx_packets_physical_ai={"addressing_format": Mock(), - "addressing_type": Mock()})), - (Mock(), Mock(tx_packets_physical_ai={"arg1": 1, "arg2": 2})), + ("can_bus_manager", "addressing_information"), + (Mock(), Mock()), ]) @pytest.mark.parametrize("n_as_timeout, n_ar_timeout, n_bs_timeout, n_br, n_cs, n_cr_timeout, " "dlc, use_data_optimization, filler_byte", [ @@ -107,9 +100,7 @@ def test_init__valid_all_args(self, mock_isinstance, mock_isinstance.assert_called_once_with(addressing_information, AbstractCanAddressingInformation) self.mock_abstract_transport_interface_init.assert_called_once_with(bus_manager=can_bus_manager) self.mock_can_segmenter_class.assert_called_once_with( - addressing_format=addressing_information.addressing_format, - physical_ai=addressing_information.tx_packets_physical_ai, - functional_ai=addressing_information.tx_packets_functional_ai, + addressing_information=addressing_information, dlc=dlc, use_data_optimization=use_data_optimization, filler_byte=filler_byte) @@ -500,3 +491,114 @@ def test_filler_byte__get(self): def test_filler_byte__set(self, value): AbstractCanTransportInterface.filler_byte.fset(self.mock_can_transport_interface, value) assert self.mock_can_transport_interface.segmenter.filler_byte == value + + +class TestPyCanTransportInterface: + """Unit tests for `PyCanTransportInterface` class.""" + + def setup(self): + self.mock_can_transport_interface = Mock(spec=PyCanTransportInterface) + # patching + self._patcher_abstract_can_ti_init = patch(f"{SCRIPT_LOCATION}.AbstractCanTransportInterface.__init__") + self.mock_abstract_can_ti_init = self._patcher_abstract_can_ti_init.start() + + def teardown(self): + self._patcher_abstract_can_ti_init.stop() + + # __init__ + + @pytest.mark.parametrize("can_bus_manager, addressing_information", [ + ("can_bus_manager", "addressing_information"), + (Mock(), Mock()) + ]) + def test_init__default_args(self, can_bus_manager, addressing_information): + PyCanTransportInterface.__init__(self=self.mock_can_transport_interface, + can_bus_manager=can_bus_manager, + addressing_information=addressing_information) + self.mock_abstract_can_ti_init.assert_called_once_with( + can_bus_manager=can_bus_manager, + 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"), + (Mock(), Mock()) + ]) + @pytest.mark.parametrize("kwargs", [ + {"a": Mock(), "b": Mock()}, + {"param1": Mock(), "param2": Mock(), "something_else": Mock()}, + ]) + def test_init__all_args(self, can_bus_manager, addressing_information, kwargs): + PyCanTransportInterface.__init__(self=self.mock_can_transport_interface, + can_bus_manager=can_bus_manager, + addressing_information=addressing_information, + **kwargs) + self.mock_abstract_can_ti_init.assert_called_once_with(can_bus_manager=can_bus_manager, + addressing_information=addressing_information, + **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 + + # n_as_measured + + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_n_as_measured(self, value): + self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured = value + assert PyCanTransportInterface.n_as_measured.fget(self.mock_can_transport_interface) == value + + # n_as_measured + + @pytest.mark.parametrize("value", ["something", Mock()]) + 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 + + # is_supported_bus_manager + + @pytest.mark.parametrize("value", ["something", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + def test_is_supported_bus_manager(self, mock_isinstance, value): + assert PyCanTransportInterface.is_supported_bus_manager(value) == mock_isinstance.return_value + mock_isinstance.assert_called_once_with(value, BusABC) + + +class TestPyCanTransportInterfaceIntegration: + """Integration tests for `PyCanTransportInterface` class.""" + + @pytest.mark.parametrize("init_kwargs", [ + { + "can_bus_manager": Mock(spec=BusABC), + "addressing_information": CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + rx_physical={"can_id": 0x641}, + tx_physical={"can_id": 0x642}, + rx_functional={"can_id": 0x6FE}, + tx_functional={"can_id": 0x6FF}, + ), + } + ]) + def test_init(self, init_kwargs): + py_can_ti = PyCanTransportInterface(**init_kwargs) + assert py_can_ti.n_as_measured is None + assert py_can_ti.n_ar_measured is None + assert py_can_ti.n_bs_measured is None + assert py_can_ti.n_cr_measured is None diff --git a/tests/software_tests/transport_interface/test_python_can_transport_interface.py b/tests/software_tests/transport_interface/test_python_can_transport_interface.py deleted file mode 100644 index 0409d41f..00000000 --- a/tests/software_tests/transport_interface/test_python_can_transport_interface.py +++ /dev/null @@ -1,119 +0,0 @@ -import pytest -from mock import MagicMock, Mock, patch - -from uds.transport_interface.python_can_transport_interface import PyCanTransportInterface, \ - BusABC, CanPacket -from uds.can import CanAddressingInformation, CanAddressingFormat - - -class TestPyCanTransportInterface: - """Unit tests for `PyCanTransportInterface` class.""" - - SCRIPT_LOCATION = "uds.transport_interface.python_can_transport_interface" - - def setup(self): - self.mock_can_transport_interface = Mock(spec=PyCanTransportInterface) - # patching - self._patcher_abstract_can_ti_init = patch(f"{self.SCRIPT_LOCATION}.AbstractCanTransportInterface.__init__") - self.mock_abstract_can_ti_init = self._patcher_abstract_can_ti_init.start() - - def teardown(self): - self._patcher_abstract_can_ti_init.stop() - - # __init__ - - @pytest.mark.parametrize("can_bus_manager, addressing_information", [ - ("can_bus_manager", "addressing_information"), - (Mock(), Mock()) - ]) - def test_init__default_args(self, can_bus_manager, addressing_information): - PyCanTransportInterface.__init__(self=self.mock_can_transport_interface, - can_bus_manager=can_bus_manager, - addressing_information=addressing_information) - self.mock_abstract_can_ti_init.assert_called_once_with( - can_bus_manager=can_bus_manager, - 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"), - (Mock(), Mock()) - ]) - @pytest.mark.parametrize("kwargs", [ - {"a": Mock(), "b": Mock()}, - {"param1": Mock(), "param2": Mock(), "something_else": Mock()}, - ]) - def test_init__all_args(self, can_bus_manager, addressing_information, kwargs): - PyCanTransportInterface.__init__(self=self.mock_can_transport_interface, - can_bus_manager=can_bus_manager, - addressing_information=addressing_information, - **kwargs) - self.mock_abstract_can_ti_init.assert_called_once_with(can_bus_manager=can_bus_manager, - addressing_information=addressing_information, - **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 - - # n_as_measured - - @pytest.mark.parametrize("value", ["something", Mock()]) - def test_n_as_measured(self, value): - self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured = value - assert PyCanTransportInterface.n_as_measured.fget(self.mock_can_transport_interface) == value - - # n_as_measured - - @pytest.mark.parametrize("value", ["something", Mock()]) - 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 - - # is_supported_bus_manager - - @pytest.mark.parametrize("value", ["something", Mock()]) - @patch(f"{SCRIPT_LOCATION}.isinstance") - def test_is_supported_bus_manager(self, mock_isinstance, value): - assert PyCanTransportInterface.is_supported_bus_manager(value) == mock_isinstance.return_value - mock_isinstance.assert_called_once_with(value, BusABC) - - -class TestPyCanTransportInterfaceIntegration: - """Integration tests for `PyCanTransportInterface` class.""" - - @pytest.mark.parametrize("init_kwargs", [ - { - "can_bus_manager": Mock(spec=BusABC), - "addressing_information": CanAddressingInformation( - addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - rx_physical={"can_id": 0x641}, - tx_physical={"can_id": 0x642}, - rx_functional={"can_id": 0x6FE}, - tx_functional={"can_id": 0x6FF}, - ), - } - ]) - def test_init(self, init_kwargs): - py_can_ti = PyCanTransportInterface(**init_kwargs) - assert py_can_ti.n_as_measured is None - assert py_can_ti.n_ar_measured is None - assert py_can_ti.n_bs_measured is None - assert py_can_ti.n_cr_measured is None diff --git a/uds/can/__init__.py b/uds/can/__init__.py index 20f49c36..116f88d1 100644 --- a/uds/can/__init__.py +++ b/uds/can/__init__.py @@ -16,7 +16,7 @@ """ from .addressing_format import CanAddressingFormat, CanAddressingFormatAlias -from .abstract_addressing_information import AbstractCanAddressingInformation +from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias from .normal_addressing_information import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation from .extended_addressing_information import ExtendedCanAddressingInformation from .mixed_addressing_information import Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation diff --git a/uds/can/abstract_addressing_information.py b/uds/can/abstract_addressing_information.py index ccbfc0a9..bf60471c 100644 --- a/uds/can/abstract_addressing_information.py +++ b/uds/can/abstract_addressing_information.py @@ -1,6 +1,6 @@ """Abstract definition of Addressing Information handler.""" -__all__ = ["AbstractCanAddressingInformation"] +__all__ = ["AbstractCanAddressingInformation", "PacketAIParamsAlias"] from typing import Optional, TypedDict from abc import ABC, abstractmethod @@ -11,10 +11,21 @@ from .frame_fields import CanIdHandler +class PacketAIParamsAlias(TypedDict): + """Alias of :ref:`Addressing Information ` parameters of CAN packets stream.""" + + addressing_format: CanAddressingFormatAlias + addressing_type: AddressingTypeAlias + can_id: int + target_address: Optional[int] + source_address: Optional[int] + address_extension: Optional[int] + + class AbstractCanAddressingInformation(ABC): """Abstract definition of CAN Entity (either server or client) Addressing Information.""" - ADDRESSING_FORMAT_NAME: str = "addressing_format" + ADDRESSING_FORMAT_NAME: str = "addressing_format" # noqa: F841 """Name of :ref:`CAN Addressing Format ` parameter in Addressing Information.""" ADDRESSING_TYPE_NAME: str = CanIdHandler.ADDRESSING_TYPE_NAME """Name of :ref:`Addressing Type ` parameter in Addressing Information.""" @@ -27,6 +38,9 @@ class AbstractCanAddressingInformation(ABC): ADDRESS_EXTENSION_NAME: str = "address_extension" """Name of Address Extension parameter in Addressing Information.""" + AI_DATA_BYTES_NUMBER: int + """Number of CAN Frame data bytes that are used to carry Addressing Information.""" + class InputAIParamsAlias(TypedDict, total=False): """Alias of :ref:`Addressing Information ` configuration parameters.""" @@ -35,23 +49,6 @@ class InputAIParamsAlias(TypedDict, total=False): source_address: int address_extension: int - class _OptionalPacketAIParamsAlias(TypedDict, total=False): - """Alias of optional :ref:`Addressing Information ` parameters of CAN packets stream.""" - - target_address: int - source_address: int - address_extension: int - - class PacketAIParamsAlias(_OptionalPacketAIParamsAlias, total=True): - """Alias of :ref:`Addressing Information ` parameters of CAN packets stream.""" - - addressing_format: CanAddressingFormatAlias - addressing_type: AddressingTypeAlias - can_id: int - - AI_DATA_BYTES_NUMBER: int - """Number of CAN Frame data bytes that are used to carry Addressing Information.""" - def __init__(self, rx_physical: InputAIParamsAlias, tx_physical: InputAIParamsAlias, @@ -87,7 +84,7 @@ def rx_packets_physical_ai(self, value: InputAIParamsAlias): :param value: Addressing Information parameters to set. """ - self.__rx_packets_physical_ai: AbstractCanAddressingInformation.PacketAIParamsAlias \ + self.__rx_packets_physical_ai: PacketAIParamsAlias \ = self.validate_packet_ai(**{self.ADDRESSING_TYPE_NAME: AddressingType.PHYSICAL}, **value) @property @@ -102,7 +99,7 @@ def tx_packets_physical_ai(self, value: InputAIParamsAlias): :param value: Addressing Information parameters to set. """ - self.__tx_packets_physical_ai: AbstractCanAddressingInformation.PacketAIParamsAlias \ + self.__tx_packets_physical_ai: PacketAIParamsAlias \ = self.validate_packet_ai(**{self.ADDRESSING_TYPE_NAME: AddressingType.PHYSICAL}, **value) @property @@ -117,7 +114,7 @@ def rx_packets_functional_ai(self, value: InputAIParamsAlias): :param value: Addressing Information parameters to set. """ - self.__rx_packets_functional_ai: AbstractCanAddressingInformation.PacketAIParamsAlias \ + self.__rx_packets_functional_ai: PacketAIParamsAlias \ = self.validate_packet_ai(**{self.ADDRESSING_TYPE_NAME: AddressingType.FUNCTIONAL}, **value) @property @@ -132,7 +129,7 @@ def tx_packets_functional_ai(self, value: InputAIParamsAlias): :param value: Addressing Information parameters to set. """ - self.__tx_packets_functional_ai: AbstractCanAddressingInformation.PacketAIParamsAlias \ + self.__tx_packets_functional_ai: PacketAIParamsAlias \ = self.validate_packet_ai(**{self.ADDRESSING_TYPE_NAME: AddressingType.FUNCTIONAL}, **value) @classmethod diff --git a/uds/can/addressing_information.py b/uds/can/addressing_information.py index 0d330afe..400e69bb 100644 --- a/uds/can/addressing_information.py +++ b/uds/can/addressing_information.py @@ -12,7 +12,7 @@ from uds.transmission_attributes import AddressingTypeAlias from .addressing_format import CanAddressingFormat, CanAddressingFormatAlias from .frame_fields import CanIdHandler -from .abstract_addressing_information import AbstractCanAddressingInformation +from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias from .normal_addressing_information import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation from .extended_addressing_information import ExtendedCanAddressingInformation from .mixed_addressing_information import Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation @@ -73,7 +73,7 @@ def validate_packet_ai(cls, target_address: Optional[int] = None, source_address: Optional[int] = None, address_extension: Optional[int] = None - ) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + ) -> PacketAIParamsAlias: """ Validate Addressing Information parameters of a CAN packet. diff --git a/uds/can/extended_addressing_information.py b/uds/can/extended_addressing_information.py index 98f16e7d..4aa3f9a7 100644 --- a/uds/can/extended_addressing_information.py +++ b/uds/can/extended_addressing_information.py @@ -8,7 +8,7 @@ from uds.transmission_attributes import AddressingType, AddressingTypeAlias from .addressing_format import CanAddressingFormat, CanAddressingFormatAlias from .frame_fields import CanIdHandler -from .abstract_addressing_information import AbstractCanAddressingInformation +from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias class ExtendedCanAddressingInformation(AbstractCanAddressingInformation): @@ -29,7 +29,7 @@ def validate_packet_ai(cls, target_address: Optional[int] = None, source_address: Optional[int] = None, address_extension: Optional[int] = None - ) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + ) -> PacketAIParamsAlias: """ Validate Addressing Information parameters of a CAN packet that uses Extended Addressing format. @@ -53,9 +53,11 @@ def validate_packet_ai(cls, if not CanIdHandler.is_extended_addressed_can_id(can_id): # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with " f"Extended Addressing Format. Actual value: {can_id}") - return { - cls.ADDRESSING_FORMAT_NAME: CanAddressingFormat.EXTENDED_ADDRESSING, # type: ignore - cls.ADDRESSING_TYPE_NAME: addressing_type, - cls.CAN_ID_NAME: can_id, - cls.TARGET_ADDRESS_NAME: target_address, - } + return PacketAIParamsAlias( + addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + addressing_type=addressing_type, + can_id=can_id, # type: ignore + target_address=target_address, + source_address=source_address, + address_extension=address_extension + ) diff --git a/uds/can/mixed_addressing_information.py b/uds/can/mixed_addressing_information.py index b5d35592..21566910 100644 --- a/uds/can/mixed_addressing_information.py +++ b/uds/can/mixed_addressing_information.py @@ -8,7 +8,7 @@ from uds.transmission_attributes import AddressingType, AddressingTypeAlias from .addressing_format import CanAddressingFormat, CanAddressingFormatAlias from .frame_fields import CanIdHandler -from .abstract_addressing_information import AbstractCanAddressingInformation +from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias class Mixed11BitCanAddressingInformation(AbstractCanAddressingInformation): @@ -29,7 +29,7 @@ def validate_packet_ai(cls, target_address: Optional[int] = None, source_address: Optional[int] = None, address_extension: Optional[int] = None - ) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + ) -> PacketAIParamsAlias: """ Validate Addressing Information parameters of a CAN packet that uses Mixed 11-bit Addressing format. @@ -53,12 +53,14 @@ def validate_packet_ai(cls, if not CanIdHandler.is_mixed_11bit_addressed_can_id(can_id): # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with " f"Mixed 11-bit Addressing Format. Actual value: {can_id}") - return { - cls.ADDRESSING_FORMAT_NAME: CanAddressingFormat.MIXED_11BIT_ADDRESSING, # type: ignore - cls.ADDRESSING_TYPE_NAME: addressing_type, - cls.CAN_ID_NAME: can_id, - cls.ADDRESS_EXTENSION_NAME: address_extension, - } + return PacketAIParamsAlias( + addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + addressing_type=addressing_type, + can_id=can_id, # type: ignore + target_address=target_address, + source_address=source_address, + address_extension=address_extension + ) class Mixed29BitCanAddressingInformation(AbstractCanAddressingInformation): @@ -79,7 +81,7 @@ def validate_packet_ai(cls, target_address: Optional[int] = None, source_address: Optional[int] = None, address_extension: Optional[int] = None - ) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + ) -> PacketAIParamsAlias: """ Validate Addressing Information parameters of a CAN packet that uses Mixed 29-bit Addressing format. @@ -108,14 +110,14 @@ def validate_packet_ai(cls, addressing_type=addressing_type, target_address=target_address, # type: ignore source_address=source_address) # type: ignore - return { - cls.ADDRESSING_FORMAT_NAME: CanAddressingFormat.MIXED_29BIT_ADDRESSING, # type: ignore - cls.ADDRESSING_TYPE_NAME: addressing_type, - cls.CAN_ID_NAME: encoded_can_id, - cls.TARGET_ADDRESS_NAME: target_address, - cls.SOURCE_ADDRESS_NAME: source_address, - cls.ADDRESS_EXTENSION_NAME: address_extension, - } + return PacketAIParamsAlias( + addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + addressing_type=addressing_type, + can_id=encoded_can_id, + target_address=target_address, + source_address=source_address, + address_extension=address_extension + ) decoded_info = CanIdHandler.decode_mixed_addressed_29bit_can_id(can_id) if addressing_type != decoded_info[CanIdHandler.ADDRESSING_TYPE_NAME]: # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with Addressing Type." @@ -126,11 +128,11 @@ def validate_packet_ai(cls, if source_address not in (decoded_info[CanIdHandler.SOURCE_ADDRESS_NAME], None): # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with Source Address." f"Actual values: can_id={can_id}, source_address={source_address}") - return { - cls.ADDRESSING_FORMAT_NAME: CanAddressingFormat.MIXED_29BIT_ADDRESSING, # type: ignore - cls.ADDRESSING_TYPE_NAME: addressing_type, - cls.CAN_ID_NAME: can_id, - cls.TARGET_ADDRESS_NAME: decoded_info[CanIdHandler.TARGET_ADDRESS_NAME], - cls.SOURCE_ADDRESS_NAME: decoded_info[CanIdHandler.SOURCE_ADDRESS_NAME], - cls.ADDRESS_EXTENSION_NAME: address_extension, - } + return PacketAIParamsAlias( + addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + addressing_type=addressing_type, + can_id=can_id, + target_address=decoded_info[CanIdHandler.TARGET_ADDRESS_NAME], # type: ignore + source_address=decoded_info[CanIdHandler.SOURCE_ADDRESS_NAME], # type: ignore + address_extension=address_extension + ) diff --git a/uds/can/normal_addressing_information.py b/uds/can/normal_addressing_information.py index 68d88dd9..6176ab70 100644 --- a/uds/can/normal_addressing_information.py +++ b/uds/can/normal_addressing_information.py @@ -8,7 +8,7 @@ from uds.transmission_attributes import AddressingType, AddressingTypeAlias from .addressing_format import CanAddressingFormat, CanAddressingFormatAlias from .frame_fields import CanIdHandler -from .abstract_addressing_information import AbstractCanAddressingInformation +from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias class Normal11BitCanAddressingInformation(AbstractCanAddressingInformation): @@ -29,7 +29,7 @@ def validate_packet_ai(cls, target_address: Optional[int] = None, source_address: Optional[int] = None, address_extension: Optional[int] = None - ) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + ) -> PacketAIParamsAlias: """ Validate Addressing Information parameters of a CAN packet that uses Normal 11-bit Addressing format. @@ -52,11 +52,14 @@ def validate_packet_ai(cls, if not CanIdHandler.is_normal_11bit_addressed_can_id(can_id): # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with " f"Normal 11-bit Addressing Format. Actual value: {can_id}") - return { - cls.ADDRESSING_FORMAT_NAME: CanAddressingFormat.NORMAL_11BIT_ADDRESSING, # type: ignore - cls.ADDRESSING_TYPE_NAME: addressing_type, - cls.CAN_ID_NAME: can_id - } + return PacketAIParamsAlias( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + addressing_type=addressing_type, + can_id=can_id, # type: ignore + target_address=target_address, + source_address=source_address, + address_extension=address_extension + ) class NormalFixedCanAddressingInformation(AbstractCanAddressingInformation): @@ -77,7 +80,7 @@ def validate_packet_ai(cls, target_address: Optional[int] = None, source_address: Optional[int] = None, address_extension: Optional[int] = None - ) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + ) -> PacketAIParamsAlias: """ Validate Addressing Information parameters of a CAN packet that uses Normal Fixed Addressing format. @@ -109,13 +112,14 @@ def validate_packet_ai(cls, addressing_type=addressing_type, target_address=target_address, # type: ignore source_address=source_address) # type: ignore - return { - cls.ADDRESSING_FORMAT_NAME: CanAddressingFormat.NORMAL_FIXED_ADDRESSING, # type: ignore - cls.ADDRESSING_TYPE_NAME: addressing_type, - cls.CAN_ID_NAME: encoded_can_id, - cls.TARGET_ADDRESS_NAME: target_address, - cls.SOURCE_ADDRESS_NAME: source_address, - } + return PacketAIParamsAlias( + addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + addressing_type=addressing_type, + can_id=encoded_can_id, + target_address=target_address, + source_address=source_address, + address_extension=address_extension + ) decoded_info = CanIdHandler.decode_normal_fixed_addressed_can_id(can_id) if addressing_type != decoded_info[CanIdHandler.ADDRESSING_TYPE_NAME]: # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with Addressing Type." @@ -126,10 +130,11 @@ def validate_packet_ai(cls, if source_address not in (decoded_info[CanIdHandler.SOURCE_ADDRESS_NAME], None): # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with Source Address." f"Actual values: can_id={can_id}, source_address={source_address}") - return { - cls.ADDRESSING_FORMAT_NAME: CanAddressingFormat.NORMAL_FIXED_ADDRESSING, # type: ignore - cls.ADDRESSING_TYPE_NAME: addressing_type, - cls.CAN_ID_NAME: can_id, - cls.TARGET_ADDRESS_NAME: decoded_info[CanIdHandler.TARGET_ADDRESS_NAME], - cls.SOURCE_ADDRESS_NAME: decoded_info[CanIdHandler.SOURCE_ADDRESS_NAME], - } + return PacketAIParamsAlias( + addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + addressing_type=addressing_type, + can_id=can_id, + target_address=decoded_info[CanIdHandler.TARGET_ADDRESS_NAME], # type: ignore + source_address=decoded_info[CanIdHandler.SOURCE_ADDRESS_NAME], # type: ignore + address_extension=address_extension + ) diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py index 98e30c03..a5e44acb 100644 --- a/uds/segmentation/can_segmenter.py +++ b/uds/segmentation/can_segmenter.py @@ -149,19 +149,26 @@ def filler_byte(self, value: int): def is_input_packet(self, **kwargs) -> Optional[AddressingType]: """ - Check if provided CAN frame attributes belong to a CAN packet that is an input for this CAN Segmenter. + Check if provided frame attributes belong to a UDS CAN packet which is an input for this CAN Segmenter. :param kwargs: Attributes of a CAN frame to check. - - can_id: CAN Identifier of a CAN Frame - - data: Raw data bytes of a CAN frame - :return: Addressing Type used for transmission if provided attributes belongs to an input CAN packet, - otherwise None. + :return: Addressing Type used for transmission of this UDS CAN packet according to the configuration of this + CAN Segmenter (if provided attributes belongs to an input UDS CAN packet), otherwise None. """ - can_id = kwargs.pop("can_id", None) - data = kwargs.pop("data", None) - self.addressing_information. - + try: + decoded_frame_ai = CanAddressingInformation.decode_packet_ai( + addressing_format=self.addressing_format, + can_id=kwargs["can_id"], + ai_data_bytes=kwargs["data"][:self.addressing_information.AI_DATA_BYTES_NUMBER]) + decoded_frame_ai.update(can_id=kwargs["can_id"], addressing_format=self.addressing_format) + except ValueError: + return None + if decoded_frame_ai == self.rx_packets_physical_ai: + return AddressingType.PHYSICAL + if decoded_frame_ai == self.rx_packets_functional_ai: + return AddressingType.FUNCTIONAL + # TODO: make sure it works def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage, UdsMessageRecord]: # TODO: review """ @@ -248,8 +255,6 @@ 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 __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: rework """ Segment physically addressed diagnostic message. diff --git a/uds/transport_interface/__init__.py b/uds/transport_interface/__init__.py index aee2bce5..adcaba2f 100644 --- a/uds/transport_interface/__init__.py +++ b/uds/transport_interface/__init__.py @@ -10,5 +10,4 @@ """ from .abstract_transport_interface import AbstractTransportInterface -from .abstract_can_transport_interface import AbstractCanTransportInterface -from .python_can_transport_interface import PyCanTransportInterface +from .can_transport_interface import AbstractCanTransportInterface, PyCanTransportInterface diff --git a/uds/transport_interface/abstract_can_transport_interface.py b/uds/transport_interface/can_transport_interface.py similarity index 63% rename from uds/transport_interface/abstract_can_transport_interface.py rename to uds/transport_interface/can_transport_interface.py index 763bc2b9..debd7f7a 100644 --- a/uds/transport_interface/abstract_can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -1,14 +1,19 @@ -"""Abstract definition of UDS Transport Interface for CAN bus.""" - -__all__ = ["AbstractCanTransportInterface"] +"""Definition and implementation of UDS Transport Interface for CAN bus.""" +__all__ = ["AbstractCanTransportInterface", "PyCanTransportInterface"] from typing import Optional, Any from abc import abstractmethod from warnings import warn +from asyncio import wait_for +from datetime import datetime + +from can import BusABC, AsyncBufferedReader, BufferedReader, Notifier, Message from uds.utilities import TimeMilliseconds, ValueWarning -from uds.can import AbstractCanAddressingInformation +from uds.can import AbstractCanAddressingInformation, CanIdHandler, CanDlcHandler +from uds.packet import CanPacket, CanPacketRecord +from uds.transmission_attributes import TransmissionDirection from uds.segmentation import CanSegmenter from .abstract_transport_interface import AbstractTransportInterface @@ -67,10 +72,7 @@ def __init__(self, self.n_br = kwargs.pop("n_br", self.DEFAULT_N_BR) self.n_cs = kwargs.pop("n_cs", self.DEFAULT_N_CS) self.n_cr_timeout = kwargs.pop("n_cr_timeout", self.N_CR_TIMEOUT) - self.__segmenter = CanSegmenter(addressing_format=addressing_information.addressing_format, - physical_ai=addressing_information.tx_packets_physical_ai, - functional_ai=addressing_information.tx_packets_functional_ai, - **kwargs) + self.__segmenter = CanSegmenter(addressing_information=addressing_information, **kwargs) @property def segmenter(self) -> CanSegmenter: @@ -300,7 +302,7 @@ def addressing_information(self) -> AbstractCanAddressingInformation: @property def dlc(self) -> int: """ - Value of base CAN DLC to use for CAN Packets. + Value of base CAN DLC to use for output CAN Packets. .. note:: All output CAN Packets will have this DLC value set unless :ref:`CAN Frame Data Optimization ` is used. @@ -310,7 +312,7 @@ def dlc(self) -> int: @dlc.setter def dlc(self, value: int): """ - Set value of base CAN DLC to use for CAN Packets. + Set value of base CAN DLC to use for output CAN Packets. :param value: Value to set. """ @@ -332,14 +334,154 @@ def use_data_optimization(self, value: bool): @property def filler_byte(self) -> int: - """Filler byte value to use for CAN Frame Data Padding during segmentation.""" + """Filler byte value to use for output CAN Frame Data Padding during segmentation.""" return self.segmenter.filler_byte @filler_byte.setter def filler_byte(self, value: int): """ - Set value of filler byte to use for CAN Frame Data Padding. + Set value of filler byte to use for output CAN Frame Data Padding. :param value: Value to set. """ self.segmenter.filler_byte = value + + +class PyCanTransportInterface(AbstractCanTransportInterface): + """ + Transport Interface for managing UDS on CAN with python-can package as bus handler. + + .. note:: Documentation for python-can package: https://python-can.readthedocs.io/ + """ + + def __init__(self, + can_bus_manager: Any, + addressing_information: AbstractCanAddressingInformation, + **kwargs: Any) -> None: + """ + Create python-can Transport Interface. + + :param can_bus_manager: An object that handles CAN bus (Physical and Data layers of OSI Model). + :param addressing_information: Addressing Information of CAN Transport Interface. + :param kwargs: Optional arguments that are specific for CAN bus. + + - :parameter n_as_timeout: Timeout value for :ref:`N_As ` time parameter. + - :parameter n_ar_timeout: Timeout value for :ref:`N_Ar ` time parameter. + - :parameter n_bs_timeout: Timeout value for :ref:`N_Bs ` time parameter. + - :parameter n_br: Value of :ref:`N_Br ` time parameter to use in communication. + - :parameter n_cs: Value of :ref:`N_Cs ` time parameter to use in communication. + - :parameter n_cr_timeout: Timeout value for :ref:`N_Cr ` time parameter. + - :parameter dlc: Base CAN DLC value to use for CAN Packets. + - :parameter use_data_optimization: Information whether to use CAN Frame Data Optimization. + - :parameter filler_byte: Filler byte value to use for CAN Frame Data Padding. + """ + self.__n_as_measured: Optional[TimeMilliseconds] = None + self.__n_ar_measured: Optional[TimeMilliseconds] = None + self.__n_bs_measured: Optional[TimeMilliseconds] = None + self.__n_cr_measured: Optional[TimeMilliseconds] = None + super().__init__(can_bus_manager=can_bus_manager, + addressing_information=addressing_information, + **kwargs) + self.__async_listener = AsyncBufferedReader() + self.__sync_listener = BufferedReader() + self.__can_notifier = Notifier(bus=self.bus_manager, + listeners=[self.__async_listener, self.__sync_listener]) + + @property + def n_as_measured(self) -> Optional[TimeMilliseconds]: + """ + Get the last measured value of N_As time parameter. + + :return: Time in milliseconds or None if the value was never measured. + """ + return self.__n_as_measured + + @property + def n_ar_measured(self) -> Optional[TimeMilliseconds]: + """ + Get the last measured value of 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[TimeMilliseconds]: + """ + 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[TimeMilliseconds]: + """ + 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 + + @staticmethod + def is_supported_bus_manager(bus_manager: Any) -> bool: + """ + Check whether provided value is a bus manager that is supported by this Transport Interface. + + :param bus_manager: Value to check. + + :return: True if provided bus object is compatible with this Transport Interface, False otherwise. + """ + return isinstance(bus_manager, BusABC) # TODO: check that receive_own_messages is set (if possible) + + def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore + """ + Transmit CAN packet. + + :param packet: CAN packet to send. + + :raise TypeError: Provided packet is not CAN Packet. + + :return: Record with historic information about transmitted CAN packet. + """ + if not isinstance(packet, CanPacket): + raise TypeError("Provided packet value does not contain CAN Packet.") + can_message = Message(arbitration_id=packet.can_id, + 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)) + self.bus_manager.send(can_message) + # TODO: get sent packet from self.__sync_listener - NOTE: we have to see sent messages + # TODO: create CanPacketRecord and return it + + async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: + """ + Receive CAN packet. + + :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. + """ + if timeout is not None: + if not isinstance(timeout, (int, float)): + raise TypeError("Provided timeout value is not None neither int nor float type.") + if timeout <= 0: + raise ValueError("Provided timeout value is less or equal 0.") + received_frame = await wait_for(self.__async_listener.get_message(), timeout) + packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, + data=received_frame.data) + while packet_addressing_type is None: + received_frame = await wait_for(self.__async_listener.get_message(), timeout) + packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, + data=received_frame.data) + packet_addressing_format = self.segmenter.addressing_format + received_datetime = datetime.now() # TODO: get better accuracy: use received_frame.timestamp + return CanPacketRecord(frame=received_frame, + direction=TransmissionDirection.RECEIVED, + addressing_type=packet_addressing_type, + addressing_format=packet_addressing_format, + transmission_time=received_datetime) diff --git a/uds/transport_interface/python_can_transport_interface.py b/uds/transport_interface/python_can_transport_interface.py deleted file mode 100644 index 68bc094c..00000000 --- a/uds/transport_interface/python_can_transport_interface.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Implementation of python-can Transport Interface. - -Documentation for python-can package: https://python-can.readthedocs.io/ -""" - -__all__ = ["PyCanTransportInterface"] - -from typing import Optional, Any - -from can import BusABC, AsyncBufferedReader, BufferedReader, Notifier, Message - -from uds.utilities import TimeMilliseconds -from uds.can import AbstractCanAddressingInformation, CanIdHandler, CanDlcHandler -from uds.packet import CanPacket, CanPacketRecord -from uds.transmission_attributes import TransmissionDirection -from .abstract_can_transport_interface import AbstractCanTransportInterface - - -class PyCanTransportInterface(AbstractCanTransportInterface): - """Transport Interface for managing UDS on CAN with python-can package as bus handler.""" - - def __init__(self, - can_bus_manager: Any, - addressing_information: AbstractCanAddressingInformation, - **kwargs: Any) -> None: - """ - Create python-can Transport Interface. - - :param can_bus_manager: An object that handles CAN bus (Physical and Data layers of OSI Model). - :param addressing_information: Addressing Information of CAN Transport Interface. - :param kwargs: Optional arguments that are specific for CAN bus. - - - :parameter n_as_timeout: Timeout value for :ref:`N_As ` time parameter. - - :parameter n_ar_timeout: Timeout value for :ref:`N_Ar ` time parameter. - - :parameter n_bs_timeout: Timeout value for :ref:`N_Bs ` time parameter. - - :parameter n_br: Value of :ref:`N_Br ` time parameter to use in communication. - - :parameter n_cs: Value of :ref:`N_Cs ` time parameter to use in communication. - - :parameter n_cr_timeout: Timeout value for :ref:`N_Cr ` time parameter. - - :parameter dlc: Base CAN DLC value to use for CAN Packets. - - :parameter use_data_optimization: Information whether to use CAN Frame Data Optimization. - - :parameter filler_byte: Filler byte value to use for CAN Frame Data Padding. - """ - self.__n_as_measured: Optional[TimeMilliseconds] = None - self.__n_ar_measured: Optional[TimeMilliseconds] = None - self.__n_bs_measured: Optional[TimeMilliseconds] = None - self.__n_cr_measured: Optional[TimeMilliseconds] = None - super().__init__(can_bus_manager=can_bus_manager, - addressing_information=addressing_information, - **kwargs) - self.__async_listener = AsyncBufferedReader() - self.__sync_listener = BufferedReader() - self.__can_notifier = Notifier(bus=self.bus_manager, - listeners=[self.__async_listener, self.__sync_listener]) - - @property - def n_as_measured(self) -> Optional[TimeMilliseconds]: - """ - Get the last measured value of N_As time parameter. - - :return: Time in milliseconds or None if the value was never measured. - """ - return self.__n_as_measured - - @property - def n_ar_measured(self) -> Optional[TimeMilliseconds]: - """ - Get the last measured value of 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[TimeMilliseconds]: - """ - 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[TimeMilliseconds]: - """ - 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 - - @staticmethod - def is_supported_bus_manager(bus_manager: Any) -> bool: - """ - Check whether provided value is a bus manager that is supported by this Transport Interface. - - :param bus_manager: Value to check. - - :return: True if provided bus object is compatible with this Transport Interface, False otherwise. - """ - return isinstance(bus_manager, BusABC) # TODO: check that receive_own_messages is set - - def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore - """ - Transmit CAN packet. - - :param packet: CAN packet to send. - - :raise TypeError: Provided packet is not CAN Packet. - - :return: Record with historic information about transmitted CAN packet. - """ - if not isinstance(packet, CanPacket): - raise TypeError("Provided packet value does not contain CAN Packet.") - can_message = Message(arbitration_id=packet.can_id, - 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)) - self.bus_manager.send(can_message) - # TODO: get sent packet from self.__sync_listener - NOTE: we have to see sent messages - # TODO: create CanPacketRecord and return it - - async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: - """ - Receive CAN packet. - - :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. - """ - if timeout is not None: - if not isinstance(timeout, (int, float)): - raise TypeError("Provided timeout value is not None neither int nor float type.") - if timeout <= 0: - raise ValueError("Provided timeout value is less or equal 0.") - received_frame = await self.__async_listener.get_message() - while False: # TODO: check if received message is either physically or functionally addressed packet directed for this node - received_message = await self.__async_listener.get_message() - packet_addressing_type = ... # TODO: extract - packet_addressing_format = ... # TODO: extract - received_datetime = ... # TODO: extract - return CanPacketRecord(frame=received_frame, - direction=TransmissionDirection.RECEIVED, - addressing_type=packet_addressing_type, - addressing_format=packet_addressing_format, - transmission_time=received_datetime) From 8637c7df30835f6d56a76917337476f2f996f145 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 13 Sep 2023 15:08:02 +0200 Subject: [PATCH 15/40] adjust can segmenter Adjust code and tests for CanSegmenter class. Move to pytest>=7.0.0 --- tests/requirements_for_software_tests.txt | 2 +- .../test_abstract_addressing_information.py | 4 +- .../can/test_addressing_information.py | 4 +- .../can/test_consecutive_frame.py | 4 +- .../test_extended_addressing_information.py | 4 +- tests/software_tests/can/test_first_frame.py | 4 +- tests/software_tests/can/test_flow_control.py | 8 +- tests/software_tests/can/test_frame_fields.py | 4 +- .../can/test_mixed_addressing_information.py | 8 +- .../can/test_normal_addressing_information.py | 8 +- tests/software_tests/can/test_single_frame.py | 4 +- tests/software_tests/conftest.py | 54 +- .../message/test_service_identifiers.py | 18 +- .../message/test_uds_message.py | 8 +- .../test_abstract_can_packet_container.py | 4 +- .../packet/test_abstract_packet.py | 4 +- .../software_tests/packet/test_can_packet.py | 8 +- .../packet/test_can_packet_record.py | 4 +- .../packet/test_can_packet_type.py | 4 +- .../segmentation/test_abstract_segmenter.py | 34 +- .../segmentation/test_can_segmenter.py | 539 +++++++++++------- .../test_abstract_transport_interface.py | 2 +- .../test_can_transport_interface.py | 8 +- .../utilities/test_bytes_operations.py | 4 +- uds/message/nrc.py | 2 - uds/message/service_identifiers.py | 16 +- uds/segmentation/can_segmenter.py | 116 ++-- uds/transmission_attributes/addressing.py | 4 +- 28 files changed, 506 insertions(+), 377 deletions(-) diff --git a/tests/requirements_for_software_tests.txt b/tests/requirements_for_software_tests.txt index 99187e2a..4cbc1651 100644 --- a/tests/requirements_for_software_tests.txt +++ b/tests/requirements_for_software_tests.txt @@ -1,4 +1,4 @@ -pytest>=6.0.0 +pytest>=7.0.0 pytest-cov>=3.0.0 pytest-asyncio>=0.17.2 mock>=4.0.0 \ No newline at end of file diff --git a/tests/software_tests/can/test_abstract_addressing_information.py b/tests/software_tests/can/test_abstract_addressing_information.py index 2e7376da..2f8a42bb 100644 --- a/tests/software_tests/can/test_abstract_addressing_information.py +++ b/tests/software_tests/can/test_abstract_addressing_information.py @@ -9,7 +9,7 @@ class TestAbstractCanAddressingInformation: SCRIPT_LOCATION = "uds.can.abstract_addressing_information" - def setup(self): + def setup_method(self): self.mock_addressing_information = Mock(spec=AbstractCanAddressingInformation, ADDRESSING_FORMAT_NAME="addressing_format", ADDRESSING_TYPE_NAME="addressing_type", @@ -20,7 +20,7 @@ def setup(self): self._patcher_deepcopy = patch(f"{self.SCRIPT_LOCATION}.deepcopy") self.mock_deepcopy = self._patcher_deepcopy.start() - def teardown(self): + def teardown_method(self): self._patcher_deepcopy.stop() # __init__ diff --git a/tests/software_tests/can/test_addressing_information.py b/tests/software_tests/can/test_addressing_information.py index 0013fa11..15583a2f 100644 --- a/tests/software_tests/can/test_addressing_information.py +++ b/tests/software_tests/can/test_addressing_information.py @@ -11,7 +11,7 @@ class TestCanAddressingInformation: SCRIPT_LOCATION = "uds.can.addressing_information" - def setup(self): + def setup_method(self): # patching self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() @@ -32,7 +32,7 @@ def setup(self): self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - def teardown(self): + def teardown_method(self): self._patcher_can_id_handler_class.stop() self._patcher_validate_addressing_format.stop() self._patcher_normal_11bit_ai_class.stop() diff --git a/tests/software_tests/can/test_consecutive_frame.py b/tests/software_tests/can/test_consecutive_frame.py index a7a34128..4a3e26b0 100644 --- a/tests/software_tests/can/test_consecutive_frame.py +++ b/tests/software_tests/can/test_consecutive_frame.py @@ -11,7 +11,7 @@ class TestCanConsecutiveFrameHandler: SCRIPT_LOCATION = "uds.can.consecutive_frame" - def setup(self): + def setup_method(self): self._patcher_validate_nibble = patch(f"{self.SCRIPT_LOCATION}.validate_nibble") self.mock_validate_nibble = self._patcher_validate_nibble.start() self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") @@ -31,7 +31,7 @@ def setup(self): patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") self.mock_get_ai_data_bytes_number = self._patcher_get_ai_data_bytes_number.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_nibble.stop() self._patcher_validate_raw_byte.stop() self._patcher_validate_raw_bytes.stop() diff --git a/tests/software_tests/can/test_extended_addressing_information.py b/tests/software_tests/can/test_extended_addressing_information.py index 47c7e8d8..d8c736f1 100644 --- a/tests/software_tests/can/test_extended_addressing_information.py +++ b/tests/software_tests/can/test_extended_addressing_information.py @@ -10,7 +10,7 @@ class TestExtendedCanAddressingInformation: SCRIPT_LOCATION = "uds.can.extended_addressing_information" - def setup(self): + def setup_method(self): self.mock_addressing_information = Mock(spec=ExtendedCanAddressingInformation) # patching self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") @@ -20,7 +20,7 @@ def setup(self): self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_byte.stop() self._patcher_validate_addressing_type.stop() self._patcher_can_id_handler_class.stop() diff --git a/tests/software_tests/can/test_first_frame.py b/tests/software_tests/can/test_first_frame.py index a8d45098..0ac611ab 100644 --- a/tests/software_tests/can/test_first_frame.py +++ b/tests/software_tests/can/test_first_frame.py @@ -11,7 +11,7 @@ class TestCanFirstFrameHandler: SCRIPT_LOCATION = "uds.can.first_frame" - def setup(self): + def setup_method(self): self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() self._patcher_bytes_list_to_int = patch(f"{self.SCRIPT_LOCATION}.bytes_list_to_int") @@ -27,7 +27,7 @@ def setup(self): self._patcher_get_max_sf_dl = patch(f"{self.SCRIPT_LOCATION}.CanSingleFrameHandler.get_max_payload_size") self.mock_get_max_sf_dl = self._patcher_get_max_sf_dl.start() - def teardown(self): + def teardown_method(self): self._patcher_bytes_list_to_int.stop() self._patcher_validate_raw_bytes.stop() self._patcher_get_ai_data_bytes_number.stop() diff --git a/tests/software_tests/can/test_flow_control.py b/tests/software_tests/can/test_flow_control.py index 0992ac6d..3a6cb3b3 100644 --- a/tests/software_tests/can/test_flow_control.py +++ b/tests/software_tests/can/test_flow_control.py @@ -22,13 +22,13 @@ class TestCanSTmin: SCRIPT_LOCATION = "uds.can.flow_control" - def setup(self): + def setup_method(self): self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_byte.stop() self._patcher_warn.stop() @@ -137,7 +137,7 @@ class TestCanFlowControlHandler: SCRIPT_LOCATION = TestCanSTmin.SCRIPT_LOCATION - def setup(self): + def setup_method(self): self._patcher_validate_nibble = patch(f"{self.SCRIPT_LOCATION}.validate_nibble") self.mock_validate_nibble = self._patcher_validate_nibble.start() self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") @@ -159,7 +159,7 @@ def setup(self): self._patcher_validate_flow_status = patch(f"{self.SCRIPT_LOCATION}.CanFlowStatus.validate_member") self.mock_validate_flow_status = self._patcher_validate_flow_status.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_nibble.stop() self._patcher_validate_raw_byte.stop() self._patcher_validate_raw_bytes.stop() diff --git a/tests/software_tests/can/test_frame_fields.py b/tests/software_tests/can/test_frame_fields.py index 7d6c7540..bafe1284 100644 --- a/tests/software_tests/can/test_frame_fields.py +++ b/tests/software_tests/can/test_frame_fields.py @@ -10,7 +10,7 @@ class TestCanIdHandler: SCRIPT_LOCATION = "uds.can.frame_fields" - def setup(self): + def setup_method(self): self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() self._patcher_validate_addressing_format = patch(f"{self.SCRIPT_LOCATION}.CanAddressingFormat.validate_member") @@ -18,7 +18,7 @@ def setup(self): self._patcher_validate_addressing_type = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing_type = self._patcher_validate_addressing_type.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_byte.stop() self._patcher_validate_addressing_format.stop() self._patcher_validate_addressing_type.stop() diff --git a/tests/software_tests/can/test_mixed_addressing_information.py b/tests/software_tests/can/test_mixed_addressing_information.py index 0f3cb68e..6e14fe29 100644 --- a/tests/software_tests/can/test_mixed_addressing_information.py +++ b/tests/software_tests/can/test_mixed_addressing_information.py @@ -10,7 +10,7 @@ class TestMixed11BitCanAddressingInformation: SCRIPT_LOCATION = "uds.can.mixed_addressing_information" - def setup(self): + def setup_method(self): self.mock_addressing_information = Mock(spec=Mixed11BitCanAddressingInformation) # patching self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") @@ -20,7 +20,7 @@ def setup(self): self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_byte.stop() self._patcher_validate_addressing_type.stop() self._patcher_can_id_handler_class.stop() @@ -87,7 +87,7 @@ class TestMixed29BitCanAddressingInformation: SCRIPT_LOCATION = TestMixed11BitCanAddressingInformation.SCRIPT_LOCATION - def setup(self): + def setup_method(self): self.mock_addressing_information = Mock(spec=Mixed29BitCanAddressingInformation) # patching self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") @@ -97,7 +97,7 @@ def setup(self): self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_byte.stop() self._patcher_validate_addressing_type.stop() self._patcher_can_id_handler_class.stop() diff --git a/tests/software_tests/can/test_normal_addressing_information.py b/tests/software_tests/can/test_normal_addressing_information.py index 9f10416a..53deb426 100644 --- a/tests/software_tests/can/test_normal_addressing_information.py +++ b/tests/software_tests/can/test_normal_addressing_information.py @@ -10,7 +10,7 @@ class TestNormal11BitCanAddressingInformation: SCRIPT_LOCATION = "uds.can.normal_addressing_information" - def setup(self): + def setup_method(self): self.mock_addressing_information = Mock(spec=Normal11BitCanAddressingInformation) # patching self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") @@ -20,7 +20,7 @@ def setup(self): self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_byte.stop() self._patcher_validate_addressing_type.stop() self._patcher_can_id_handler_class.stop() @@ -81,7 +81,7 @@ class TestNormalFixedCanAddressingInformation: SCRIPT_LOCATION = TestNormal11BitCanAddressingInformation.SCRIPT_LOCATION - def setup(self): + def setup_method(self): self.mock_addressing_information = Mock(spec=NormalFixedCanAddressingInformation) # patching self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") @@ -91,7 +91,7 @@ def setup(self): self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_byte.stop() self._patcher_validate_addressing_type.stop() self._patcher_can_id_handler_class.stop() diff --git a/tests/software_tests/can/test_single_frame.py b/tests/software_tests/can/test_single_frame.py index 5b22dab5..eafbac5b 100644 --- a/tests/software_tests/can/test_single_frame.py +++ b/tests/software_tests/can/test_single_frame.py @@ -11,7 +11,7 @@ class TestCanSingleFrameHandler: SCRIPT_LOCATION = "uds.can.single_frame" - def setup(self): + def setup_method(self): self._patcher_validate_nibble = patch(f"{self.SCRIPT_LOCATION}.validate_nibble") self.mock_validate_nibble = self._patcher_validate_nibble.start() self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") @@ -33,7 +33,7 @@ def setup(self): patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") self.mock_get_ai_data_bytes_number = self._patcher_get_ai_data_bytes_number.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_nibble.stop() self._patcher_validate_raw_byte.stop() self._patcher_validate_raw_bytes.stop() diff --git a/tests/software_tests/conftest.py b/tests/software_tests/conftest.py index 5a20dd0d..18efe23d 100644 --- a/tests/software_tests/conftest.py +++ b/tests/software_tests/conftest.py @@ -3,7 +3,8 @@ from can import Message from uds.transmission_attributes import TransmissionDirection, AddressingType -from uds.can import CanAddressingFormat +from uds.can import CanAddressingFormat, Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation, \ + ExtendedCanAddressingInformation, Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation from uds.segmentation import CanSegmenter @@ -52,22 +53,35 @@ def example_can_addressing_format(request): return request.param -# @fixture(params=[ -# CanSegmenter(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, -# physical_ai=dict(can_id=0x724), -# functional_ai=dict(can_id=0x7FF)), -# CanSegmenter(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, -# physical_ai=dict(target_address=0x1E, source_address=0xFF), -# functional_ai=dict(target_address=0xB1, source_address=0xFF), -# dlc=0xF, -# use_data_optimization=True, -# filler_byte=0x71), -# CanSegmenter(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, -# physical_ai=dict(target_address=0xE0, can_id=0x129834), -# functional_ai=dict(target_address=0xFF, can_id=0x12FFFF), -# dlc=0xC, -# use_data_optimization=True, -# filler_byte=0x8E) -# ]) -# def example_can_segmenter(request): -# return request.param +@fixture(params=[ + CanSegmenter(addressing_information=Normal11BitCanAddressingInformation(rx_physical={"can_id": 0x643}, + tx_physical={"can_id": 0x644}, + rx_functional={"can_id": 0x7DE}, + tx_functional={"can_id": 0x7DF})), + CanSegmenter(addressing_information=NormalFixedCanAddressingInformation(rx_physical={"target_address": 0x12, "source_address": 0xF8}, + tx_physical={"can_id": 0x18DAF812,"target_address": 0xF8, "source_address": 0x12}, + rx_functional={"can_id": 0x18DB0BFF, "target_address": 0x0B, "source_address": 0xFF}, + tx_functional={"target_address": 0xFF, "source_address": 0x0B}), + dlc=0xF, + use_data_optimization=True, + filler_byte=0xE9), + CanSegmenter(addressing_information=ExtendedCanAddressingInformation(rx_physical={"can_id": 0x752, "target_address": 0x9C}, + tx_physical={"can_id": 0x752, "target_address": 0xF0}, + rx_functional={"can_id": 0x12CDEF59, "target_address": 0x9A}, + tx_functional={"can_id": 0x9876543, "target_address": 0x0E}), + dlc=0xA, + filler_byte=0x55), + CanSegmenter(addressing_information=Mixed11BitCanAddressingInformation(rx_physical={"can_id": 0x6FE, "address_extension": 0x6B}, + tx_physical={"can_id": 0x720, "address_extension": 0xD0}, + rx_functional={"can_id": 0x7BD, "address_extension": 0x9C}, + tx_functional={"can_id": 0x7BD, "address_extension": 0xF2}), + use_data_optimization=True), + CanSegmenter(addressing_information=Mixed29BitCanAddressingInformation(rx_physical={"can_id": 0x18CEF1E2, "address_extension": 0x6B}, + tx_physical={"can_id": 0x18CEE2F1, "target_address": 0xE2, "source_address": 0xF1, "address_extension": 0xD0}, + rx_functional={"target_address": 0xFF, "source_address": 0xFF, "address_extension": 0x55}, + tx_functional={"can_id": 0x18CDFFFF, "address_extension": 0xEF}), + dlc=0xF, + filler_byte=0xAA), +]) +def example_can_segmenter(request): + return request.param diff --git a/tests/software_tests/message/test_service_identifiers.py b/tests/software_tests/message/test_service_identifiers.py index 8442c874..9a8e91b5 100644 --- a/tests/software_tests/message/test_service_identifiers.py +++ b/tests/software_tests/message/test_service_identifiers.py @@ -10,7 +10,7 @@ class TestRequestSID: SCRIPT_LOCATION = "uds.message.service_identifiers" - def setup(self): + def setup_method(self): self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() self._patcher_is_member = patch(f"{self.SCRIPT_LOCATION}.RequestSID.is_member") @@ -18,7 +18,7 @@ def setup(self): self._patcher_possible_request_sids = patch(f"{self.SCRIPT_LOCATION}.POSSIBLE_REQUEST_SIDS") self.mock_possible_request_sids = self._patcher_possible_request_sids.start() - def teardown(self): + def teardown_method(self): self._patcher_warn.stop() self._patcher_is_member.stop() self._patcher_possible_request_sids.stop() @@ -35,10 +35,11 @@ def test_inheritance__extendable_enum(self): @pytest.mark.parametrize("value", [1, 0x55, 0xFF]) def test_is_request_sid__member(self, value): self.mock_is_member.return_value = True + self.mock_possible_request_sids.__contains__.return_value = True assert RequestSID.is_request_sid(value=value) is True self.mock_warn.assert_not_called() self.mock_is_member.assert_called_once_with(value) - self.mock_possible_request_sids.__contains__.assert_not_called() + self.mock_possible_request_sids.__contains__.assert_called_once_with(value) @pytest.mark.parametrize("value", [1, 0x55, 0xFF]) def test_is_request_sid__unsupported(self, value): @@ -55,7 +56,7 @@ def test_is_request_sid__invalid(self, value): self.mock_possible_request_sids.__contains__.return_value = False assert RequestSID.is_request_sid(value=value) is False self.mock_warn.assert_not_called() - self.mock_is_member.assert_called_once_with(value) + self.mock_is_member.assert_not_called() self.mock_possible_request_sids.__contains__.assert_called_once_with(value) @@ -64,7 +65,7 @@ class TestResponseSID: SCRIPT_LOCATION = TestRequestSID.SCRIPT_LOCATION - def setup(self): + def setup_method(self): self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() self._patcher_is_member = patch(f"{self.SCRIPT_LOCATION}.ResponseSID.is_member") @@ -72,7 +73,7 @@ def setup(self): self._patcher_possible_response_sids = patch(f"{self.SCRIPT_LOCATION}.POSSIBLE_RESPONSE_SIDS") self.mock_possible_response_sids = self._patcher_possible_response_sids.start() - def teardown(self): + def teardown_method(self): self._patcher_warn.stop() self._patcher_is_member.stop() self._patcher_possible_response_sids.stop() @@ -89,10 +90,11 @@ def test_inheritance__extendable_enum(self): @pytest.mark.parametrize("value", [1, 0x55, 0xFF]) def test_is_response_sid__member(self, value): self.mock_is_member.return_value = True + self.mock_possible_response_sids.__contains__.return_value = True assert ResponseSID.is_response_sid(value=value) is True self.mock_warn.assert_not_called() self.mock_is_member.assert_called_once_with(value) - self.mock_possible_response_sids.__contains__.assert_not_called() + self.mock_possible_response_sids.__contains__.assert_called_once_with(value) @pytest.mark.parametrize("value", [1, 0x55, 0xFF]) def test_is_response_sid__unsupported(self, value): @@ -109,7 +111,7 @@ def test_is_response_sid__invalid(self, value): self.mock_possible_response_sids.__contains__.return_value = False assert ResponseSID.is_response_sid(value=value) is False self.mock_warn.assert_not_called() - self.mock_is_member.assert_called_once_with(value) + self.mock_is_member.assert_not_called() self.mock_possible_response_sids.__contains__.assert_called_once_with(value) diff --git a/tests/software_tests/message/test_uds_message.py b/tests/software_tests/message/test_uds_message.py index 352295ed..7f41f78d 100644 --- a/tests/software_tests/message/test_uds_message.py +++ b/tests/software_tests/message/test_uds_message.py @@ -11,7 +11,7 @@ class TestUdsMessage: SCRIPT_LOCATION = "uds.message.uds_message" - def setup(self): + def setup_method(self): self.mock_uds_message = Mock(spec=UdsMessage) # patching self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") @@ -19,7 +19,7 @@ def setup(self): self._patcher_validate_addressing = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing = self._patcher_validate_addressing.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_bytes.stop() self._patcher_validate_addressing.stop() @@ -90,7 +90,7 @@ class TestUdsMessageRecord: SCRIPT_LOCATION = TestUdsMessage.SCRIPT_LOCATION - def setup(self): + def setup_method(self): self.mock_uds_message_record = Mock(spec=UdsMessageRecord) # patching self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") @@ -98,7 +98,7 @@ def setup(self): self._patcher_validate_addressing = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing = self._patcher_validate_addressing.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_bytes.stop() self._patcher_validate_addressing.stop() diff --git a/tests/software_tests/packet/test_abstract_can_packet_container.py b/tests/software_tests/packet/test_abstract_can_packet_container.py index 3ec646ea..3420b72e 100644 --- a/tests/software_tests/packet/test_abstract_can_packet_container.py +++ b/tests/software_tests/packet/test_abstract_can_packet_container.py @@ -11,7 +11,7 @@ class TestAbstractCanPacketContainer: SCRIPT_LOCATION = "uds.packet.abstract_can_packet_container" - def setup(self): + def setup_method(self): self.mock_can_packet_container = Mock(spec=AbstractCanPacketContainer) mock_can_packet_type_class = Mock(SINGLE_FRAME=CanPacketType.SINGLE_FRAME, FIRST_FRAME=CanPacketType.FIRST_FRAME, @@ -33,7 +33,7 @@ def setup(self): self._patcher_can_packet_type_class = patch(f"{self.SCRIPT_LOCATION}.CanPacketType", mock_can_packet_type_class) self.mock_can_packet_type_class = self._patcher_can_packet_type_class.start() - def teardown(self): + def teardown_method(self): self._patcher_ai_handler_class.stop() self._patcher_can_dlc_handler_class.stop() self._patcher_single_frame_handler_class.stop() diff --git a/tests/software_tests/packet/test_abstract_packet.py b/tests/software_tests/packet/test_abstract_packet.py index d20bb7db..51770416 100644 --- a/tests/software_tests/packet/test_abstract_packet.py +++ b/tests/software_tests/packet/test_abstract_packet.py @@ -10,13 +10,13 @@ class TestAbstractUdsPacketRecord: SCRIPT_LOCATION = "uds.packet.abstract_packet" - def setup(self): + def setup_method(self): self.mock_packet_record = Mock(spec=AbstractUdsPacketRecord) # patching self._patcher_validate_direction = patch(f"{self.SCRIPT_LOCATION}.TransmissionDirection.validate_member") self.mock_validate_direction = self._patcher_validate_direction.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_direction.stop() # __init__ diff --git a/tests/software_tests/packet/test_can_packet.py b/tests/software_tests/packet/test_can_packet.py index 16b40b09..1f8508f7 100644 --- a/tests/software_tests/packet/test_can_packet.py +++ b/tests/software_tests/packet/test_can_packet.py @@ -12,7 +12,7 @@ class TestCanPacket: SCRIPT_LOCATION = "uds.packet.can_packet" - def setup(self): + def setup_method(self): self.mock_can_packet = Mock(spec=CanPacket) mock_can_id_handler_class = Mock(spec=CanIdHandler, ADDRESSING_TYPE_NAME=CanIdHandler.ADDRESSING_TYPE_NAME, @@ -52,7 +52,7 @@ def setup(self): self._patcher_validate_packet_type = patch(f"{self.SCRIPT_LOCATION}.CanPacketType.validate_member") self.mock_validate_packet_type = self._patcher_validate_packet_type.start() - def teardown(self): + def teardown_method(self): self._patcher_warn.stop() self._patcher_can_dlc_handler_class.stop() self._patcher_can_id_handler_class.stop() @@ -814,7 +814,7 @@ class TestAnyCanPacket: SCRIPT_LOCATION = TestCanPacket.SCRIPT_LOCATION - def setup(self): + def setup_method(self): self.mock_any_can_packet = Mock(spec=AnyCanPacket) # patching self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") @@ -832,7 +832,7 @@ def setup(self): self._patcher_can_packet_class = patch(f"{self.SCRIPT_LOCATION}.CanPacket") self.mock_can_packet_class = self._patcher_can_packet_class.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_bytes.stop() self._patcher_addressing_type_class.stop() self._patcher_addressing_format_class.stop() diff --git a/tests/software_tests/packet/test_can_packet_record.py b/tests/software_tests/packet/test_can_packet_record.py index 03d9b1ac..99a7e59a 100644 --- a/tests/software_tests/packet/test_can_packet_record.py +++ b/tests/software_tests/packet/test_can_packet_record.py @@ -14,7 +14,7 @@ class TestCanPacketRecord: SCRIPT_LOCATION = "uds.packet.can_packet_record" - def setup(self): + def setup_method(self): self.mock_can_packet_record = Mock(spec=CanPacketRecord) # patching self._patcher_addressing_type_class = patch(f"{self.SCRIPT_LOCATION}.AddressingType") @@ -32,7 +32,7 @@ def setup(self): self._patcher_abstract_uds_packet_record_init = patch(f"{self.SCRIPT_LOCATION}.AbstractUdsPacketRecord.__init__") self.mock_abstract_uds_packet_record_init = self._patcher_abstract_uds_packet_record_init.start() - def teardown(self): + def teardown_method(self): self._patcher_addressing_type_class.stop() self._patcher_can_addressing_format_class.stop() self._patcher_can_ai_class.stop() diff --git a/tests/software_tests/packet/test_can_packet_type.py b/tests/software_tests/packet/test_can_packet_type.py index 85e2aee6..3b9c278d 100644 --- a/tests/software_tests/packet/test_can_packet_type.py +++ b/tests/software_tests/packet/test_can_packet_type.py @@ -8,11 +8,11 @@ class TestCanPacketType: """Unit tests for `CanPacketType` class.""" - def setup(self): + def setup_method(self): self._patcher_validate_member = patch("uds.utilities.ValidatedEnum.validate_member") self.mock_validate_member = self._patcher_validate_member.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_member.stop() # inheritance diff --git a/tests/software_tests/segmentation/test_abstract_segmenter.py b/tests/software_tests/segmentation/test_abstract_segmenter.py index 6ad9b636..06e1b991 100644 --- a/tests/software_tests/segmentation/test_abstract_segmenter.py +++ b/tests/software_tests/segmentation/test_abstract_segmenter.py @@ -9,27 +9,27 @@ class TestAbstractSegmenter: SCRIPT_PATH = "uds.segmentation.abstract_segmenter" - def setup(self): + def setup_method(self): self.mock_abstract_segmenter = Mock(spec=AbstractSegmenter) - # is_supported_packet + # is_supported_packet_type @pytest.mark.parametrize("result", [True, False]) @pytest.mark.parametrize("value", [None, 5, "some value", Mock()]) @patch(f"{SCRIPT_PATH}.isinstance") - def test_is_supported_packet(self, mock_isinstance, value, result): + def test_is_supported_packet_type(self, mock_isinstance, value, result): mock_isinstance.return_value = result assert AbstractSegmenter.is_supported_packet_type(self=self.mock_abstract_segmenter, packet=value) is result mock_isinstance.assert_called_once_with(value, (self.mock_abstract_segmenter.supported_packet_class, self.mock_abstract_segmenter.supported_packet_record_class)) - # is_supported_packets_sequence + # is_supported_packets_sequence_type @pytest.mark.parametrize("value", [None, True, 1, Mock(), {1, 2, 3}]) - def test_is_supported_packets_sequence__false__invalid_type(self, value): + def test_is_supported_packets_sequence_type__false__invalid_type(self, value): assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, packets=value) is False - self.mock_abstract_segmenter.is_supported_packet.assert_not_called() + self.mock_abstract_segmenter.is_supported_packet_type.assert_not_called() @pytest.mark.parametrize("value", [ (1, 2, 3, 4), @@ -38,24 +38,24 @@ def test_is_supported_packets_sequence__false__invalid_type(self, value): [None, True, False], (True, False), ]) - def test_is_supported_packets_sequence__false__invalid_elements_type(self, value): - self.mock_abstract_segmenter.is_supported_packet.return_value = False + def test_is_supported_packets_sequence_type__false__invalid_elements_type(self, value): + self.mock_abstract_segmenter.is_supported_packet_type.return_value = False assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, packets=value) is False - self.mock_abstract_segmenter.is_supported_packet.assert_called_once() + self.mock_abstract_segmenter.is_supported_packet_type.assert_called_once() @pytest.mark.parametrize("value", [ (1, 2.1, 3, 4.4), [None, True, False], ]) - def test_is_supported_packets_sequence__false__more_element_types(self, value): - self.mock_abstract_segmenter.is_supported_packet.return_value = True + def test_is_supported_packets_sequence_type__false__more_element_types(self, value): + self.mock_abstract_segmenter.is_supported_packet_type.return_value = True assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, packets=value) is False - self.mock_abstract_segmenter.is_supported_packet.assert_called() + self.mock_abstract_segmenter.is_supported_packet_type.assert_called() - def test_is_supported_packets_sequence__false__empty_sequence(self): - self.mock_abstract_segmenter.is_supported_packet.return_value = True + def test_is_supported_packets_sequence_type__false__empty_sequence(self): + self.mock_abstract_segmenter.is_supported_packet_type.return_value = True assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, packets=[]) is False @@ -64,8 +64,8 @@ def test_is_supported_packets_sequence__false__empty_sequence(self): [2.2, 3.3, 4.4], (True, False), ]) - def test_is_supported_packets_sequence__true(self, value): - self.mock_abstract_segmenter.is_supported_packet.return_value = True + def test_is_supported_packets_sequence_type__true(self, value): + self.mock_abstract_segmenter.is_supported_packet_type.return_value = True assert AbstractSegmenter.is_supported_packets_sequence_type(self=self.mock_abstract_segmenter, packets=value) is True - self.mock_abstract_segmenter.is_supported_packet.assert_called() + self.mock_abstract_segmenter.is_supported_packet_type.assert_called() diff --git a/tests/software_tests/segmentation/test_can_segmenter.py b/tests/software_tests/segmentation/test_can_segmenter.py index 7d877035..44f9825e 100644 --- a/tests/software_tests/segmentation/test_can_segmenter.py +++ b/tests/software_tests/segmentation/test_can_segmenter.py @@ -1,46 +1,47 @@ import pytest -from mock import Mock, patch, call +from mock import Mock, MagicMock, patch, call from uds.segmentation.can_segmenter import CanSegmenter, \ CanDlcHandler, CanPacket, CanPacketRecord, CanPacketType, CanFirstFrameHandler, \ - AddressingType, UdsMessage, DEFAULT_FILLER_BYTE, SegmentationError + AddressingType, UdsMessage, DEFAULT_FILLER_BYTE, SegmentationError, AbstractCanAddressingInformation +from uds.can import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation, \ + ExtendedCanAddressingInformation, Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation + + +SCRIPT_LOCATION = "uds.segmentation.can_segmenter" class TestCanSegmenter: """Unit tests for `CanSegmenter` class.""" - SCRIPT_LOCATION = "uds.segmentation.can_segmenter" - - def setup(self): + def setup_method(self): self.mock_can_segmenter = Mock(spec=CanSegmenter) + self.mock_can_segmenter.addressing_information.AI_DATA_BYTES_NUMBER = 0 # patching - self._patcher_can_addressing_format_class = patch(f"{self.SCRIPT_LOCATION}.CanAddressingFormat") - self.mock_can_addressing_format_class = self._patcher_can_addressing_format_class.start() - self._patcher_can_ai_class = patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation") + self._patcher_can_ai_class = patch(f"{SCRIPT_LOCATION}.CanAddressingInformation") self.mock_can_ai_class = self._patcher_can_ai_class.start() - self._patcher_validate_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.validate_dlc") + self._patcher_validate_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.validate_dlc") self.mock_validate_dlc = self._patcher_validate_dlc.start() - self._patcher_is_initial_packet_type = patch(f"{self.SCRIPT_LOCATION}.CanPacketType.is_initial_packet_type") + self._patcher_is_initial_packet_type = patch(f"{SCRIPT_LOCATION}.CanPacketType.is_initial_packet_type") self.mock_is_initial_packet_type = self._patcher_is_initial_packet_type.start() self._patcher_get_max_sf_payload_size \ - = patch(f"{self.SCRIPT_LOCATION}.CanSingleFrameHandler.get_max_payload_size") + = patch(f"{SCRIPT_LOCATION}.CanSingleFrameHandler.get_max_payload_size") self.mock_get_max_sf_payload_size = self._patcher_get_max_sf_payload_size.start() - self._patcher_get_ff_payload_size = patch(f"{self.SCRIPT_LOCATION}.CanFirstFrameHandler.get_payload_size") + self._patcher_get_ff_payload_size = patch(f"{SCRIPT_LOCATION}.CanFirstFrameHandler.get_payload_size") self.mock_get_ff_payload_size = self._patcher_get_ff_payload_size.start() self._patcher_get_max_cf_payload_size \ - = patch(f"{self.SCRIPT_LOCATION}.CanConsecutiveFrameHandler.get_max_payload_size") + = patch(f"{SCRIPT_LOCATION}.CanConsecutiveFrameHandler.get_max_payload_size") self.mock_get_max_cf_payload_size = self._patcher_get_max_cf_payload_size.start() - self._patcher_can_packet_class = patch(f"{self.SCRIPT_LOCATION}.CanPacket") + self._patcher_can_packet_class = patch(f"{SCRIPT_LOCATION}.CanPacket") self.mock_can_packet_class = self._patcher_can_packet_class.start() - self._patcher_uds_message_class = patch(f"{self.SCRIPT_LOCATION}.UdsMessage") + self._patcher_uds_message_class = patch(f"{SCRIPT_LOCATION}.UdsMessage") self.mock_uds_message_class = self._patcher_uds_message_class.start() - self._patcher_uds_message_record_class = patch(f"{self.SCRIPT_LOCATION}.UdsMessageRecord") + self._patcher_uds_message_record_class = patch(f"{SCRIPT_LOCATION}.UdsMessageRecord") self.mock_uds_message_record_class = self._patcher_uds_message_record_class.start() - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - def teardown(self): - self._patcher_can_addressing_format_class.stop() + def teardown_method(self): self._patcher_can_ai_class.stop() self._patcher_validate_dlc.stop() self._patcher_is_initial_packet_type.stop() @@ -54,100 +55,91 @@ def teardown(self): # __init__ - @pytest.mark.parametrize("addressing_format, physical_ai, functional_ai", [ - ("some format", "Addressing Info1 ", "Addressing Info 2"), - ("Normal", None, {}), + @pytest.mark.parametrize("addressing_information", [ + "some addressing information", + Mock() ]) - def test_init__mandatory_args(self, addressing_format, physical_ai, functional_ai): + def test_init__mandatory_args(self, addressing_information): CanSegmenter.__init__(self=self.mock_can_segmenter, - addressing_format=addressing_format, - physical_ai=physical_ai, - functional_ai=functional_ai) - assert self.mock_can_segmenter._CanSegmenter__addressing_format \ - == self.mock_can_addressing_format_class.return_value - assert self.mock_can_segmenter.physical_ai == physical_ai - assert self.mock_can_segmenter.functional_ai == functional_ai + addressing_information=addressing_information) + assert self.mock_can_segmenter.addressing_information == addressing_information assert self.mock_can_segmenter.dlc == CanDlcHandler.MIN_BASE_UDS_DLC assert self.mock_can_segmenter.use_data_optimization is False assert self.mock_can_segmenter.filler_byte == DEFAULT_FILLER_BYTE - self.mock_can_addressing_format_class.validate_member.assert_called_once_with(addressing_format) - self.mock_can_addressing_format_class.assert_called_once_with(addressing_format) - @pytest.mark.parametrize("addressing_format, physical_ai, functional_ai, dlc, use_data_optimization, filler_byte", [ - ("some format", "Addressing Info1 ", "Addressing Info 2", "DLC", "whether tyo use data optimization", "filler"), - ("Normal", None, {}, 0xF, False, 0x71), + @pytest.mark.parametrize("addressing_information, dlc, use_data_optimization, filler_byte", [ + ("some addressing info", "some dlc", "True or False", "filler"), + (Mock(), Mock(), Mock(), Mock()), ]) - def test_init__all_args(self, addressing_format, physical_ai, functional_ai, dlc, use_data_optimization, - filler_byte): + def test_init__all_args(self, addressing_information, dlc, use_data_optimization, filler_byte): CanSegmenter.__init__(self=self.mock_can_segmenter, - addressing_format=addressing_format, - physical_ai=physical_ai, - functional_ai=functional_ai, + addressing_information=addressing_information, dlc=dlc, use_data_optimization=use_data_optimization, filler_byte=filler_byte) - assert self.mock_can_segmenter._CanSegmenter__addressing_format \ - == self.mock_can_addressing_format_class.return_value - assert self.mock_can_segmenter.physical_ai == physical_ai - assert self.mock_can_segmenter.functional_ai == functional_ai + assert self.mock_can_segmenter.addressing_information == addressing_information assert self.mock_can_segmenter.dlc == dlc assert self.mock_can_segmenter.use_data_optimization == use_data_optimization assert self.mock_can_segmenter.filler_byte == filler_byte - self.mock_can_addressing_format_class.validate_member.assert_called_once_with(addressing_format) - self.mock_can_addressing_format_class.assert_called_once_with(addressing_format) # supported_packet_classes - def test_supported_packet_classes__get(self): - assert CanSegmenter.supported_packet_classes.fget(self.mock_can_segmenter) \ - == (self.mock_can_packet_class, CanPacketRecord) + def test_supported_packet_class__get(self): + assert CanSegmenter.supported_packet_class.fget(self.mock_can_segmenter) == self.mock_can_packet_class + + # supported_packet_record_class + + def test_supported_packet_record_class__get(self): + assert CanSegmenter.supported_packet_record_class.fget(self.mock_can_segmenter) == CanPacketRecord # addressing_format - @pytest.mark.parametrize("value", ["something", 5.5]) - def test_addressing_format__get(self, value): - self.mock_can_segmenter._CanSegmenter__addressing_format = value - assert CanSegmenter.addressing_format.fget(self.mock_can_segmenter) == value + def test_addressing_format__get(self): + assert CanSegmenter.addressing_format.fget(self.mock_can_segmenter) \ + == self.mock_can_segmenter.addressing_information.addressing_format - # physical_ai + # rx_packets_physical_ai - @pytest.mark.parametrize("value", ["something", 5.5]) - def test_physical_ai__get(self, value): - self.mock_can_segmenter._CanSegmenter__physical_ai = value - assert CanSegmenter.physical_ai.fget(self.mock_can_segmenter) == value - - @pytest.mark.parametrize("value", [{"a": 1, "b": 2, "addressing_format": Mock(), "addressing_type": Mock()}, - {"arg1": "something", "arg2": "something else"}]) - def test_physical_ai__set(self, value): - CanSegmenter.physical_ai.fset(self.mock_can_segmenter, value=value) - value.pop("addressing_format", None) - value.pop("addressing_type", None) - self.mock_can_ai_class.validate_packet_ai.assert_called_once_with( - addressing_format=self.mock_can_segmenter.addressing_format, - addressing_type=AddressingType.PHYSICAL, - **value) - assert self.mock_can_segmenter._CanSegmenter__physical_ai \ - == self.mock_can_ai_class.validate_packet_ai.return_value + def test_rx_packets_physical_ai__get(self): + assert CanSegmenter.rx_packets_physical_ai.fget(self.mock_can_segmenter) \ + == self.mock_can_segmenter.addressing_information.rx_packets_physical_ai - # functional_ai + # tx_packets_physical_ai - @pytest.mark.parametrize("value", ["something", 5.5]) - def test_functional_ai__get(self, value): - self.mock_can_segmenter._CanSegmenter__functional_ai = value - assert CanSegmenter.functional_ai.fget(self.mock_can_segmenter) == value - - @pytest.mark.parametrize("value", [{"a": 1, "b": 2, "addressing_format": Mock(), "addressing_type": Mock()}, - {"arg1": "something", "arg2": "something else"}]) - def test_functional_ai__set(self, value): - CanSegmenter.functional_ai.fset(self.mock_can_segmenter, value=value) - value.pop("addressing_format", None) - value.pop("addressing_type", None) - self.mock_can_ai_class.validate_packet_ai.assert_called_once_with( - addressing_format=self.mock_can_segmenter.addressing_format, - addressing_type=AddressingType.FUNCTIONAL, - **value) - assert self.mock_can_segmenter._CanSegmenter__functional_ai \ - == self.mock_can_ai_class.validate_packet_ai.return_value + def test_tx_packets_physical_ai__get(self): + assert CanSegmenter.tx_packets_physical_ai.fget(self.mock_can_segmenter) \ + == self.mock_can_segmenter.addressing_information.tx_packets_physical_ai + + # rx_packets_functional_ai + + def test_rx_packets_functional_ai__get(self): + assert CanSegmenter.rx_packets_functional_ai.fget(self.mock_can_segmenter) \ + == self.mock_can_segmenter.addressing_information.rx_packets_functional_ai + + # tx_packets_functional_ai + + def test_tx_packets_functional_ai__get(self): + assert CanSegmenter.tx_packets_functional_ai.fget(self.mock_can_segmenter) \ + == self.mock_can_segmenter.addressing_information.tx_packets_functional_ai + + # addressing_information + + @pytest.mark.parametrize("value", ["some AI", Mock()]) + def test_addressing_information__get(self, value): + self.mock_can_segmenter._CanSegmenter__addressing_information = value + assert CanSegmenter.addressing_information.fget(self.mock_can_segmenter) \ + == self.mock_can_segmenter._CanSegmenter__addressing_information + + @pytest.mark.parametrize("value", [Mock(), "some value"]) + def test_addressing_information__set__type_error(self, value): + with pytest.raises(TypeError): + CanSegmenter.addressing_information.fset(self.mock_can_segmenter, value=value) + + @pytest.mark.parametrize("value", [Mock(spec=AbstractCanAddressingInformation), + Mock(spec=Normal11BitCanAddressingInformation)]) + def test_addressing_information__set(self, value): + CanSegmenter.addressing_information.fset(self.mock_can_segmenter, value=value) + assert self.mock_can_segmenter._CanSegmenter__addressing_information == value # dlc @@ -170,12 +162,12 @@ def test_dlc__set(self, value): # use_data_optimization - @pytest.mark.parametrize("value", ["something", 5.5]) + @pytest.mark.parametrize("value", [False, True]) def test_use_data_optimization__get(self, value): self.mock_can_segmenter._CanSegmenter__use_data_optimization = value assert CanSegmenter.use_data_optimization.fget(self.mock_can_segmenter) == value - @pytest.mark.parametrize("value", ["something", 5.5]) + @pytest.mark.parametrize("value", [False, True, "something"]) def test_use_data_optimization__set(self, value): CanSegmenter.use_data_optimization.fset(self.mock_can_segmenter, value=value) assert self.mock_can_segmenter._CanSegmenter__use_data_optimization == bool(value) @@ -193,6 +185,153 @@ 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 + # is_input_packet + + @pytest.mark.parametrize("frame_can_id, frame_data", [ + ("some ID", "some data"), + (Mock(), MagicMock()), + ]) + def test_is_input_packet__decoding_error(self, frame_can_id, frame_data): + self.mock_can_ai_class.decode_packet_ai.side_effect = ValueError + assert CanSegmenter.is_input_packet(self.mock_can_segmenter, can_id=frame_can_id, data=frame_data) is None + + @pytest.mark.parametrize("frame_can_id, frame_data, decoded_ai", [ + ("some ID", "some data", {"a": 1, "b": 2}), + (Mock(), MagicMock(), MagicMock()), + ]) + def test_is_input_packet__physical(self, frame_can_id, frame_data, decoded_ai): + self.mock_can_ai_class.decode_packet_ai.return_value = decoded_ai + mock_eq_physical = Mock(return_value=True) + self.mock_can_segmenter.rx_packets_physical_ai = MagicMock(__eq__=mock_eq_physical) + self.mock_can_segmenter.rx_packets_functional_ai = MagicMock(__eq__=Mock(return_value=False)) + assert CanSegmenter.is_input_packet(self.mock_can_segmenter, can_id=frame_can_id, data=frame_data) \ + == AddressingType.PHYSICAL + mock_eq_physical.assert_called_once() + + @pytest.mark.parametrize("frame_can_id, frame_data, decoded_ai", [ + ("some ID", "some data", {"a": 1, "b": 2}), + (Mock(), MagicMock(), MagicMock()), + ]) + def test_is_input_packet__functional(self, frame_can_id, frame_data, decoded_ai): + self.mock_can_ai_class.decode_packet_ai.return_value = decoded_ai + mock_eq_functional = Mock(return_value=True) + self.mock_can_segmenter.rx_packets_physical_ai = MagicMock(__eq__=Mock(return_value=False)) + self.mock_can_segmenter.rx_packets_functional_ai = MagicMock(__eq__=mock_eq_functional) + assert CanSegmenter.is_input_packet(self.mock_can_segmenter, can_id=frame_can_id, data=frame_data) \ + == AddressingType.FUNCTIONAL + mock_eq_functional.assert_called_once() + + @pytest.mark.parametrize("frame_can_id, frame_data, decoded_ai", [ + ("some ID", "some data", {"a": 1, "b": 2}), + (Mock(), MagicMock(), MagicMock()), + ]) + def test_is_input_packet__no_match(self, frame_can_id, frame_data, decoded_ai): + self.mock_can_ai_class.decode_packet_ai.return_value = decoded_ai + mock_eq_physical = Mock(return_value=False) + mock_eq_functional = Mock(return_value=False) + self.mock_can_segmenter.rx_packets_physical_ai = MagicMock(__eq__=mock_eq_physical) + self.mock_can_segmenter.rx_packets_functional_ai = MagicMock(__eq__=mock_eq_functional) + assert CanSegmenter.is_input_packet(self.mock_can_segmenter, can_id=frame_can_id, data=frame_data) is None + mock_eq_physical.assert_called_once() + mock_eq_functional.assert_called_once() + + # is_desegmented_message + + @pytest.mark.parametrize("packets", ["some packets", range(4)]) + def test_is_desegmented_message__value_error(self, packets): + self.mock_can_segmenter.is_supported_packets_sequence_type.return_value = False + with pytest.raises(ValueError): + CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) + self.mock_can_segmenter.is_supported_packets_sequence_type.assert_called_once_with(packets) + + @pytest.mark.parametrize("packets", [ + [Mock()], + (Mock(), Mock(), Mock()), + ]) + def test_is_desegmented_message__not_implemented_error(self, packets): + self.mock_can_segmenter.is_supported_packets_sequence_type.return_value = True + self.mock_is_initial_packet_type.return_value = True + with pytest.raises(NotImplementedError): + CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) + self.mock_can_segmenter.is_supported_packets_sequence_type.assert_called_once_with(packets) + self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) + + @pytest.mark.parametrize("packets", [ + [Mock()], + (Mock(), Mock(), Mock()), + ]) + def test_is_desegmented_message__false__not_initial_packet(self, packets): + self.mock_can_segmenter.is_supported_packets_sequence_type.return_value = True + self.mock_is_initial_packet_type.return_value = False + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False + self.mock_can_segmenter.is_supported_packets_sequence_type.assert_called_once_with(packets) + self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) + + @pytest.mark.parametrize("packets", [ + [Mock(packet_type=CanPacketType.SINGLE_FRAME)], + [Mock(packet_type=CanPacketType.SINGLE_FRAME), Mock()], + (Mock(packet_type=CanPacketType.SINGLE_FRAME), Mock(), Mock()), + ]) + def test_is_desegmented_message__single_frame(self, packets): + self.mock_can_segmenter.is_supported_packets_sequence_type.return_value = True + self.mock_is_initial_packet_type.return_value = True + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) \ + is (len(packets) == 1) + self.mock_can_segmenter.is_supported_packets_sequence_type.assert_called_once_with(packets) + self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) + + @pytest.mark.parametrize("packets", [ + [Mock(packet_type=CanPacketType.FIRST_FRAME, payload=[]), Mock()], + (Mock(packet_type=CanPacketType.FIRST_FRAME, payload=range(5)), Mock(), Mock()), + ]) + def test_is_desegmented_message__first_frame__multiple_initial_packets(self, packets): + self.mock_can_segmenter.is_supported_packets_sequence_type.return_value = True + self.mock_is_initial_packet_type.return_value = True + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False + self.mock_can_segmenter.is_supported_packets_sequence_type.assert_called_once_with(packets) + self.mock_is_initial_packet_type.assert_has_calls( + [call(packets[0].packet_type), call(packets[1].packet_type)]) + + @pytest.mark.parametrize("packets", [ + [Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=11, payload=range(4)), Mock(payload=range(6))], + (Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=143, payload=[0xFF] * 10), Mock(payload=None), + Mock(payload=range(50, 100)), Mock(payload=range(150, 200))), + ]) + def test_is_desegmented_message__first_frame__too_little_packets(self, packets): + self.mock_can_segmenter.is_supported_packets_sequence_type.return_value = True + self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False + self.mock_can_segmenter.is_supported_packets_sequence_type.assert_called_once_with(packets) + self.mock_is_initial_packet_type.assert_has_calls( + [call(packet.packet_type) for packet in packets]) + + @pytest.mark.parametrize("packets", [ + [Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=10, payload=range(4)), Mock(payload=range(6)), + Mock(payload=None)], + (Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=54, payload=[0xFF] * 10), Mock(payload=None), + Mock(payload=range(50, 100)), Mock(payload=range(150, 200))), + ]) + def test_is_desegmented_message__first_frame__too_many_packets(self, packets): + self.mock_can_segmenter.is_supported_packets_sequence_type.return_value = True + self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False + self.mock_can_segmenter.is_supported_packets_sequence_type.assert_called_once_with(packets) + self.mock_is_initial_packet_type.assert_has_calls( + [call(packet.packet_type) for packet in packets]) + + @pytest.mark.parametrize("packets", [ + [Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=11, payload=range(4)), Mock(payload=range(7))], + (Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=100, payload=[0xFF] * 10), Mock(payload=None), + Mock(payload=range(50, 100)), Mock(payload=range(150, 200))), + ]) + def test_is_desegmented_message__first_frame__true(self, packets): + self.mock_can_segmenter.is_supported_packets_sequence_type.return_value = True + self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] + assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is True + self.mock_can_segmenter.is_supported_packets_sequence_type.assert_called_once_with(packets) + self.mock_is_initial_packet_type.assert_has_calls( + [call(packet.packet_type) for packet in packets]) + # desegmentation @pytest.mark.parametrize("packets", [ @@ -200,10 +339,10 @@ def test_filler_byte__set(self, value): (Mock(), Mock()), ]) def test_desegmentation__segmentation_error(self, packets): - self.mock_can_segmenter.is_complete_packets_sequence.return_value = False + self.mock_can_segmenter.is_desegmented_message.return_value = False with pytest.raises(SegmentationError): CanSegmenter.desegmentation(self=self.mock_can_segmenter, packets=packets) - self.mock_can_segmenter.is_complete_packets_sequence.assert_called_once_with(packets) + self.mock_can_segmenter.is_desegmented_message.assert_called_once_with(packets) @pytest.mark.parametrize("packets", [ [Mock()], @@ -211,11 +350,11 @@ def test_desegmentation__segmentation_error(self, packets): ]) @patch(f"{SCRIPT_LOCATION}.isinstance") def test_desegmentation__not_implemented(self, mock_isinstance, packets): - self.mock_can_segmenter.is_complete_packets_sequence.return_value = True + self.mock_can_segmenter.is_desegmented_message.return_value = True mock_isinstance.return_value = False with pytest.raises(NotImplementedError): CanSegmenter.desegmentation(self=self.mock_can_segmenter, packets=packets) - self.mock_can_segmenter.is_complete_packets_sequence.assert_called_once_with(packets) + self.mock_can_segmenter.is_desegmented_message.assert_called_once_with(packets) mock_isinstance.assert_called() @pytest.mark.parametrize("packets", [ @@ -223,10 +362,10 @@ def test_desegmentation__not_implemented(self, mock_isinstance, packets): (Mock(spec=CanPacketRecord), Mock(spec=CanPacketRecord)), ]) def test_desegmentation__records(self, packets): - self.mock_can_segmenter.is_complete_packets_sequence.return_value = True + self.mock_can_segmenter.is_desegmented_message.return_value = True assert CanSegmenter.desegmentation(self=self.mock_can_segmenter, packets=packets) \ == self.mock_uds_message_record_class.return_value - self.mock_can_segmenter.is_complete_packets_sequence.assert_called_once_with(packets) + self.mock_can_segmenter.is_desegmented_message.assert_called_once_with(packets) self.mock_uds_message_record_class.assert_called_once_with(packets) @pytest.mark.parametrize("packets", [ @@ -235,12 +374,11 @@ def test_desegmentation__records(self, packets): ]) @patch(f"{SCRIPT_LOCATION}.isinstance") def test_desegmentation__definition__unsegmented_message(self, mock_isinstance, packets): - self.mock_can_segmenter.is_complete_packets_sequence.return_value = True - mock_isinstance.side_effect = lambda value, types: types == self.mock_can_packet_class and isinstance(value, - CanPacket) + self.mock_can_segmenter.is_desegmented_message.return_value = True + mock_isinstance.side_effect = lambda value, types: types == self.mock_can_packet_class and isinstance(value, CanPacket) assert CanSegmenter.desegmentation(self=self.mock_can_segmenter, packets=packets) \ == self.mock_uds_message_class.return_value - self.mock_can_segmenter.is_complete_packets_sequence.assert_called_once_with(packets) + self.mock_can_segmenter.is_desegmented_message.assert_called_once_with(packets) self.mock_uds_message_class.assert_called_once_with(payload=packets[0].payload, addressing_type=packets[0].addressing_type) @@ -255,12 +393,11 @@ def test_desegmentation__definition__unsegmented_message(self, mock_isinstance, ]) @patch(f"{SCRIPT_LOCATION}.isinstance") def test_desegmentation__definitions__segmented_message(self, mock_isinstance, packets): - self.mock_can_segmenter.is_complete_packets_sequence.return_value = True - mock_isinstance.side_effect = lambda value, types: types == self.mock_can_packet_class and isinstance(value, - CanPacket) + self.mock_can_segmenter.is_desegmented_message.return_value = True + mock_isinstance.side_effect = lambda value, types: types == self.mock_can_packet_class and isinstance(value, CanPacket) assert CanSegmenter.desegmentation(self=self.mock_can_segmenter, packets=packets) \ == self.mock_uds_message_class.return_value - self.mock_can_segmenter.is_complete_packets_sequence.assert_called_once_with(packets) + self.mock_can_segmenter.is_desegmented_message.assert_called_once_with(packets) total_payload = [] for packet in packets: if packet.payload is not None: @@ -274,12 +411,11 @@ def test_desegmentation__definitions__segmented_message(self, mock_isinstance, p ]) @patch(f"{SCRIPT_LOCATION}.isinstance") def test_desegmentation__definitions__segmentation_error(self, mock_isinstance, packets): - self.mock_can_segmenter.is_complete_packets_sequence.return_value = True - mock_isinstance.side_effect = lambda value, types: types == self.mock_can_packet_class and isinstance(value, - CanPacket) + self.mock_can_segmenter.is_desegmented_message.return_value = True + mock_isinstance.side_effect = lambda value, types: types == self.mock_can_packet_class and isinstance(value, CanPacket) with pytest.raises(SegmentationError): CanSegmenter.desegmentation(self=self.mock_can_segmenter, packets=packets) - self.mock_can_segmenter.is_complete_packets_sequence.assert_called_once_with(packets) + self.mock_can_segmenter.is_desegmented_message.assert_called_once_with(packets) # segmentation @@ -318,103 +454,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) - # is_complete_packets_sequence - - @pytest.mark.parametrize("packets", ["some packets", range(4)]) - def test_is_complete_packets_sequence__value_error(self, packets): - self.mock_can_segmenter.is_supported_packets_sequence.return_value = False - with pytest.raises(ValueError): - CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) - self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) - - @pytest.mark.parametrize("packets", [ - [Mock()], - (Mock(), Mock(), Mock()), - ]) - def test_is_complete_packets_sequence__not_implemented_error(self, packets): - self.mock_can_segmenter.is_supported_packets_sequence.return_value = True - self.mock_is_initial_packet_type.return_value = True - with pytest.raises(NotImplementedError): - CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) - self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) - self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) - - @pytest.mark.parametrize("packets", [ - [Mock()], - (Mock(), Mock(), Mock()), - ]) - def test_is_complete_packets_sequence__false__not_initial_packet(self, packets): - self.mock_can_segmenter.is_supported_packets_sequence.return_value = True - self.mock_is_initial_packet_type.return_value = False - assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False - self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) - self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) - - @pytest.mark.parametrize("packets", [ - [Mock(packet_type=CanPacketType.SINGLE_FRAME)], - [Mock(packet_type=CanPacketType.SINGLE_FRAME), Mock()], - (Mock(packet_type=CanPacketType.SINGLE_FRAME), Mock(), Mock()), - ]) - def test_is_complete_packets_sequence__single_frame(self, packets): - self.mock_can_segmenter.is_supported_packets_sequence.return_value = True - self.mock_is_initial_packet_type.return_value = True - assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) \ - is (len(packets) == 1) - self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) - self.mock_is_initial_packet_type.assert_called_once_with(packets[0].packet_type) - - @pytest.mark.parametrize("packets", [ - [Mock(packet_type=CanPacketType.FIRST_FRAME, payload=[]), Mock()], - (Mock(packet_type=CanPacketType.FIRST_FRAME, payload=range(5)), Mock(), Mock()), - ]) - def test_is_complete_packets_sequence__first_frame__multiple_initial_packets(self, packets): - self.mock_can_segmenter.is_supported_packets_sequence.return_value = True - self.mock_is_initial_packet_type.return_value = True - assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False - self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) - self.mock_is_initial_packet_type.assert_has_calls( - [call(packets[0].packet_type), call(packets[1].packet_type)]) - - @pytest.mark.parametrize("packets", [ - [Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=11, payload=range(4)), Mock(payload=range(6))], - (Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=143, payload=[0xFF] * 10), Mock(payload=None), - Mock(payload=range(50, 100)), Mock(payload=range(150, 200))), - ]) - def test_is_complete_packets_sequence__first_frame__too_little_packets(self, packets): - self.mock_can_segmenter.is_supported_packets_sequence.return_value = True - self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] - assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False - self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) - self.mock_is_initial_packet_type.assert_has_calls( - [call(packet.packet_type) for packet in packets]) - - @pytest.mark.parametrize("packets", [ - [Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=10, payload=range(4)), Mock(payload=range(6)), - Mock(payload=None)], - (Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=54, payload=[0xFF] * 10), Mock(payload=None), - Mock(payload=range(50, 100)), Mock(payload=range(150, 200))), - ]) - def test_is_complete_packets_sequence__first_frame__too_many_packets(self, packets): - self.mock_can_segmenter.is_supported_packets_sequence.return_value = True - self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] - assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is False - self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) - self.mock_is_initial_packet_type.assert_has_calls( - [call(packet.packet_type) for packet in packets]) - - @pytest.mark.parametrize("packets", [ - [Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=11, payload=range(4)), Mock(payload=range(7))], - (Mock(packet_type=CanPacketType.FIRST_FRAME, data_length=100, payload=[0xFF] * 10), Mock(payload=None), - Mock(payload=range(50, 100)), Mock(payload=range(150, 200))), - ]) - def test_is_complete_packets_sequence__first_frame__true(self, packets): - self.mock_can_segmenter.is_supported_packets_sequence.return_value = True - self.mock_is_initial_packet_type.side_effect = [True] + (len(packets) - 1) * [False] - assert CanSegmenter.is_desegmented_message(self=self.mock_can_segmenter, packets=packets) is True - self.mock_can_segmenter.is_supported_packets_sequence.assert_called_once_with(packets) - self.mock_is_initial_packet_type.assert_has_calls( - [call(packet.packet_type) for packet in packets]) - # __physical_segmentation @pytest.mark.parametrize("message_payload_size", [CanFirstFrameHandler.MAX_LONG_FF_DL_VALUE + 1, @@ -438,7 +477,7 @@ def test_physical_segmentation__sf_with_data_optimization(self, mock_len, 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.physical_ai = physical_ai + 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) @@ -466,7 +505,7 @@ def test_physical_segmentation__sf_without_data_optimization(self, mock_len, 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.physical_ai = physical_ai + 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) @@ -496,7 +535,7 @@ def test_physical_segmentation__ff_cf_with_data_optimization(self, mock_len, phy 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.physical_ai = physical_ai + 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, @@ -562,7 +601,7 @@ def test_functional_segmentation__with_data_optimization(self, mock_len, message 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.functional_ai = functional_ai + 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) @@ -589,7 +628,7 @@ def test_functional_segmentation__without_data_optimization(self, mock_len, mess 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.functional_ai = functional_ai + 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) @@ -610,6 +649,84 @@ def test_functional_segmentation__without_data_optimization(self, mock_len, mess class TestCanSegmenterIntegration: """Integration tests for `CanSegmenter` class.""" + @pytest.mark.parametrize("addressing_information, frame_can_id, frame_data", [ + (Normal11BitCanAddressingInformation(rx_physical={"can_id": 0x611}, + tx_physical={"can_id": 0x612}, + rx_functional={"can_id": 0x6FE}, + tx_functional={"can_id": 0x6FF}), 0x611, [0x10, 0x01]), + (NormalFixedCanAddressingInformation(rx_physical={"can_id": 0x18DA1234, "target_address": 0x12, "source_address": 0x34}, + tx_physical={"can_id": 0x18DA3412, "target_address": 0x34, "source_address": 0x12}, + rx_functional={"can_id": 0x18DBF0E1, "target_address": 0xF0, "source_address": 0xE1}, + tx_functional={"can_id": 0x18DBE1F0}), 0x18DA1234, [0xAB, 0xCD, 0x12, 0xF5]), + (ExtendedCanAddressingInformation(rx_physical={"can_id": 0x1234, "target_address": 0xF0}, + tx_physical={"can_id": 0x1234, "target_address": 0xE2,}, + rx_functional={"can_id": 0xFEDCBA, "target_address": 0xBB}, + tx_functional={"can_id": 0xFEDCBA, "target_address": 0x12}), 0x1234, [0xF0, *range(63)]), + (Mixed11BitCanAddressingInformation(rx_physical={"can_id": 0x645, "address_extension": 0x01}, + tx_physical={"can_id": 0x646, "address_extension": 0xFE}, + rx_functional={"can_id": 0x6DE, "address_extension": 0x05}, + tx_functional={"can_id": 0x6DF, "address_extension": 0xFF}), 0x645, [0x01, 0x3E]), + (Mixed29BitCanAddressingInformation(rx_physical={"can_id": 0x18CEC2D0, "address_extension": 0x52, "target_address": 0xC2, "source_address": 0xD0}, + tx_physical={"can_id": 0x18CED0C2, "address_extension": 0xD8, "target_address": 0xD0, "source_address": 0xC2}, + rx_functional={"can_id": 0x18CD7186, "address_extension": 0x05, "target_address": 0x71, "source_address": 0x86}, + tx_functional={"can_id": 0x18CD8671, "address_extension": 0xFF}), 0x18CEC2D0, [0x52, 0x3E]), + ]) + def test_is_input_packet__physical(self, addressing_information, frame_can_id, frame_data): + can_segmenter = CanSegmenter(addressing_information=addressing_information) + assert can_segmenter.is_input_packet(can_id=frame_can_id, data=frame_data) is AddressingType.PHYSICAL + + @pytest.mark.parametrize("addressing_information, frame_can_id, frame_data", [ + (Normal11BitCanAddressingInformation(rx_physical={"can_id": 0x611}, + tx_physical={"can_id": 0x612}, + rx_functional={"can_id": 0x6FE}, + tx_functional={"can_id": 0x6FF}), 0x6FE, [0x10, 0x01]), + (NormalFixedCanAddressingInformation(rx_physical={"can_id": 0x18DA1234, "target_address": 0x12, "source_address": 0x34}, + tx_physical={"can_id": 0x18DA3412, "target_address": 0x34, "source_address": 0x12}, + rx_functional={"can_id": 0x18DBF0E1, "target_address": 0xF0, "source_address": 0xE1}, + tx_functional={"can_id": 0x18DBE1F0}), 0x18DBF0E1, [0xAB, 0xCD, 0x12, 0xF5]), + (ExtendedCanAddressingInformation(rx_physical={"can_id": 0x1234, "target_address": 0xF0}, + tx_physical={"can_id": 0x1234, "target_address": 0xE2,}, + rx_functional={"can_id": 0xFEDCBA, "target_address": 0xBB}, + tx_functional={"can_id": 0xFEDCBA, "target_address": 0x12}), 0xFEDCBA, [0xBB, *range(63)]), + (Mixed11BitCanAddressingInformation(rx_physical={"can_id": 0x645, "address_extension": 0x01}, + tx_physical={"can_id": 0x646, "address_extension": 0xFE}, + rx_functional={"can_id": 0x6DE, "address_extension": 0x05}, + tx_functional={"can_id": 0x6DF, "address_extension": 0xFF}), 0x6DE, [0x05, 0x3E]), + (Mixed29BitCanAddressingInformation(rx_physical={"can_id": 0x18CEC2D0, "address_extension": 0x52, "target_address": 0xC2, "source_address": 0xD0}, + tx_physical={"can_id": 0x18CED0C2, "address_extension": 0xD8, "target_address": 0xD0, "source_address": 0xC2}, + rx_functional={"can_id": 0x18CD7186, "address_extension": 0x05, "target_address": 0x71, "source_address": 0x86}, + tx_functional={"can_id": 0x18CD8671, "address_extension": 0xFF}), 0x18CD7186, [0x05, 0x3E]), + ]) + def test_is_input_packet__functional(self, addressing_information, frame_can_id, frame_data): + can_segmenter = CanSegmenter(addressing_information=addressing_information) + assert can_segmenter.is_input_packet(can_id=frame_can_id, data=frame_data) is AddressingType.FUNCTIONAL + + @pytest.mark.parametrize("addressing_information, frame_can_id, frame_data", [ + (Normal11BitCanAddressingInformation(rx_physical={"can_id": 0x611}, + tx_physical={"can_id": 0x612}, + rx_functional={"can_id": 0x6FE}, + tx_functional={"can_id": 0x6FF}), 0x6FF, [0x10, 0x01]), + (NormalFixedCanAddressingInformation(rx_physical={"can_id": 0x18DA1234, "target_address": 0x12, "source_address": 0x34}, + tx_physical={"can_id": 0x18DA3412, "target_address": 0x34, "source_address": 0x12}, + rx_functional={"can_id": 0x18DBF0E1, "target_address": 0xF0, "source_address": 0xE1}, + tx_functional={"can_id": 0x18DBE1F0}), 0x18DA3412, [0xAB, 0xCD, 0x12, 0xF5]), + (ExtendedCanAddressingInformation(rx_physical={"can_id": 0x1234, "target_address": 0xF0}, + tx_physical={"can_id": 0x1234, "target_address": 0xE2,}, + rx_functional={"can_id": 0xFEDCBA, "target_address": 0xBB}, + tx_functional={"can_id": 0xFEDCBA, "target_address": 0x12}), 0xFEDCBA, [0xF0, *range(63)]), + (Mixed11BitCanAddressingInformation(rx_physical={"can_id": 0x645, "address_extension": 0x01}, + tx_physical={"can_id": 0x646, "address_extension": 0xFE}, + rx_functional={"can_id": 0x6DE, "address_extension": 0x05}, + tx_functional={"can_id": 0x6DF, "address_extension": 0xFF}), 0x646, [0xFE, 0x3E]), + (Mixed29BitCanAddressingInformation(rx_physical={"can_id": 0x18CEC2D0, "address_extension": 0x52, "target_address": 0xC2, "source_address": 0xD0}, + tx_physical={"can_id": 0x18CED0C2, "address_extension": 0xD8, "target_address": 0xD0, "source_address": 0xC2}, + rx_functional={"can_id": 0x18CD7186, "address_extension": 0x05, "target_address": 0x71, "source_address": 0x86}, + tx_functional={"can_id": 0x18CD8671, "address_extension": 0xFF}), 0x18CD7186, [0x52, 0x3E]), + ]) + def test_is_input_packet__false(self, addressing_information, frame_can_id, frame_data): + can_segmenter = CanSegmenter(addressing_information=addressing_information) + assert can_segmenter.is_input_packet(can_id=frame_can_id, data=frame_data) is None + @pytest.mark.parametrize("uds_message", [ UdsMessage(payload=bytearray([0x54]), addressing_type=AddressingType.PHYSICAL), UdsMessage(payload=(0x3E, 0x00), addressing_type=AddressingType.FUNCTIONAL), diff --git a/tests/software_tests/transport_interface/test_abstract_transport_interface.py b/tests/software_tests/transport_interface/test_abstract_transport_interface.py index 208b46da..e05d7d54 100644 --- a/tests/software_tests/transport_interface/test_abstract_transport_interface.py +++ b/tests/software_tests/transport_interface/test_abstract_transport_interface.py @@ -10,7 +10,7 @@ class TestAbstractTransportInterface: """Unit tests for `AbstractTransportInterface` class.""" - def setup(self): + def setup_method(self): self.mock_transport_interface = Mock(spec=AbstractTransportInterface) # __init__ 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 45ccfd00..48a63b1a 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -12,7 +12,7 @@ class TestAbstractCanTransportInterface: """Unit tests for `AbstractCanTransportInterface` class.""" - def setup(self): + def setup_method(self): self.mock_can_transport_interface = Mock(spec=AbstractCanTransportInterface) # patching self._patcher_abstract_transport_interface_init \ @@ -23,7 +23,7 @@ def setup(self): self._patcher_warn = patch(f"{SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() - def teardown(self): + def teardown_method(self): self._patcher_abstract_transport_interface_init.stop() self._patcher_can_segmenter_class.stop() self._patcher_warn.stop() @@ -496,13 +496,13 @@ def test_filler_byte__set(self, value): class TestPyCanTransportInterface: """Unit tests for `PyCanTransportInterface` class.""" - def setup(self): + def setup_method(self): self.mock_can_transport_interface = Mock(spec=PyCanTransportInterface) # patching self._patcher_abstract_can_ti_init = patch(f"{SCRIPT_LOCATION}.AbstractCanTransportInterface.__init__") self.mock_abstract_can_ti_init = self._patcher_abstract_can_ti_init.start() - def teardown(self): + def teardown_method(self): self._patcher_abstract_can_ti_init.stop() # __init__ diff --git a/tests/software_tests/utilities/test_bytes_operations.py b/tests/software_tests/utilities/test_bytes_operations.py index af9f3a18..c1e6060a 100644 --- a/tests/software_tests/utilities/test_bytes_operations.py +++ b/tests/software_tests/utilities/test_bytes_operations.py @@ -18,13 +18,13 @@ class TestFunctions: SCRIPT_LOCATION = "uds.utilities.bytes_operations" - def setup(self): + def setup_method(self): self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() self._patcher_validate_endianness = patch(f"{self.SCRIPT_LOCATION}.Endianness.validate_member") self.mock_validate_endianness = self._patcher_validate_endianness.start() - def teardown(self): + def teardown_method(self): self._patcher_validate_raw_bytes.stop() self._patcher_validate_endianness.stop() diff --git a/uds/message/nrc.py b/uds/message/nrc.py index deaf136c..f23a85b1 100644 --- a/uds/message/nrc.py +++ b/uds/message/nrc.py @@ -228,5 +228,3 @@ class NRC(ByteEnum, ValidatedEnum, ExtendableEnum): information is temporality not available. This NRC is in general supported by each diagnostic service, as not otherwise stated in the data link specific implementation document, therefore it is not listed in the list of applicable response codes of the diagnostic services.""" - -# TODO: add alias diff --git a/uds/message/service_identifiers.py b/uds/message/service_identifiers.py index 1ec6d19e..f76a0873 100644 --- a/uds/message/service_identifiers.py +++ b/uds/message/service_identifiers.py @@ -58,11 +58,10 @@ def is_request_sid(cls, value: int) -> bool: :return: True if value is valid SID, else False. """ - if cls.is_member(value): - return True if value in POSSIBLE_REQUEST_SIDS: - warn(message=f"SID 0x{value:X} is not recognized by this version of the package.", - category=UnrecognizedSIDWarning) + if not cls.is_member(value): + warn(message=f"SID 0x{value:X} is not recognized by this version of the package.", + category=UnrecognizedSIDWarning) return True return False @@ -121,11 +120,10 @@ def is_response_sid(cls, value: int) -> bool: :return: True if value is valid RSID, else False. """ - if cls.is_member(value): - return True if value in POSSIBLE_RESPONSE_SIDS: - warn(message=f"RSID 0x{value:X} is not recognized by this version of the package", - category=UnrecognizedSIDWarning) + if not cls.is_member(value): + warn(message=f"RSID 0x{value:X} is not recognized by this version of the package", + category=UnrecognizedSIDWarning) return True return False @@ -135,5 +133,3 @@ def is_response_sid(cls, value: int) -> bool: # extend 'ResponseSID' with members that were defined in RequestSID for request_sid_member in RequestSID: # type: ignore ResponseSID.add_member(request_sid_member.name, request_sid_member.value + 0x40) - -# TODO: add aliases diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py index a5e44acb..18f17246 100644 --- a/uds/segmentation/can_segmenter.py +++ b/uds/segmentation/can_segmenter.py @@ -2,16 +2,15 @@ __all__ = ["CanSegmenter"] -from typing import Optional, Union, Tuple, Type -from copy import copy +from typing import Optional, Union, Type -from uds.utilities import RawBytesList, validate_raw_byte +from uds.utilities import RawBytesList, RawBytes, validate_raw_byte from uds.transmission_attributes import AddressingType -from uds.can import AbstractCanAddressingInformation, CanAddressingInformation, \ - CanAddressingFormat, CanAddressingFormatAlias, \ +from uds.can import AbstractCanAddressingInformation, CanAddressingInformation, PacketAIParamsAlias, \ + CanAddressingFormatAlias, \ CanDlcHandler, CanSingleFrameHandler, CanFirstFrameHandler, CanConsecutiveFrameHandler, DEFAULT_FILLER_BYTE from uds.packet import CanPacket, CanPacketRecord, CanPacketType, \ - AbstractUdsPacket, AbstractUdsPacketRecord, AbstractUdsPacketContainer, PacketsContainersSequence, PacketsTuple + AbstractUdsPacket, AbstractUdsPacketRecord, PacketsContainersSequence, PacketsTuple from uds.message import UdsMessage, UdsMessageRecord from .abstract_segmenter import AbstractSegmenter, SegmentationError @@ -55,22 +54,22 @@ def addressing_format(self) -> CanAddressingFormatAlias: return self.addressing_information.addressing_format @property - def rx_packets_physical_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + def rx_packets_physical_ai(self) -> PacketAIParamsAlias: """Addressing Information parameters of incoming physically addressed CAN packets.""" return self.addressing_information.rx_packets_physical_ai @property - def tx_packets_physical_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + def tx_packets_physical_ai(self) -> PacketAIParamsAlias: """Addressing Information parameters of outgoing physically addressed CAN packets.""" return self.addressing_information.tx_packets_physical_ai @property - def rx_packets_functional_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + def rx_packets_functional_ai(self) -> PacketAIParamsAlias: """Addressing Information parameters of incoming functionally addressed CAN packets.""" return self.addressing_information.rx_packets_functional_ai @property - def tx_packets_functional_ai(self) -> AbstractCanAddressingInformation.PacketAIParamsAlias: + def tx_packets_functional_ai(self) -> PacketAIParamsAlias: """Addressing Information parameters of outgoing functionally addressed CAN packets.""" return self.addressing_information.tx_packets_functional_ai @@ -147,11 +146,12 @@ def filler_byte(self, value: int): validate_raw_byte(value) self.__filler_byte: int = value - def is_input_packet(self, **kwargs) -> Optional[AddressingType]: + def is_input_packet(self, can_id: int, data: RawBytes) -> Optional[AddressingType]: # type: ignore # noqa """ Check if provided frame attributes belong to a UDS CAN packet which is an input for this CAN Segmenter. - :param kwargs: Attributes of a CAN frame to check. + :param can_id: Identifier of CAN frame to check. + :param data: Data field of CAN frame to check. :return: Addressing Type used for transmission of this UDS CAN packet according to the configuration of this CAN Segmenter (if provided attributes belongs to an input UDS CAN packet), otherwise None. @@ -159,18 +159,53 @@ def is_input_packet(self, **kwargs) -> Optional[AddressingType]: try: decoded_frame_ai = CanAddressingInformation.decode_packet_ai( addressing_format=self.addressing_format, - can_id=kwargs["can_id"], - ai_data_bytes=kwargs["data"][:self.addressing_information.AI_DATA_BYTES_NUMBER]) - decoded_frame_ai.update(can_id=kwargs["can_id"], addressing_format=self.addressing_format) + can_id=can_id, + ai_data_bytes=data[:self.addressing_information.AI_DATA_BYTES_NUMBER]) + decoded_frame_ai.update(can_id=can_id, addressing_format=self.addressing_format) # type: ignore except ValueError: return None - if decoded_frame_ai == self.rx_packets_physical_ai: + decoded_frame_ai[AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME] = AddressingType.PHYSICAL # type: ignore # noqa + if self.rx_packets_physical_ai == decoded_frame_ai: return AddressingType.PHYSICAL - if decoded_frame_ai == self.rx_packets_functional_ai: + decoded_frame_ai[AbstractCanAddressingInformation.ADDRESSING_TYPE_NAME] = AddressingType.FUNCTIONAL # type: ignore # noqa + if self.rx_packets_functional_ai == decoded_frame_ai: return AddressingType.FUNCTIONAL - # TODO: make sure it works + return None - def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage, UdsMessageRecord]: # TODO: review + def is_desegmented_message(self, packets: PacketsContainersSequence) -> bool: + """ + Check whether provided packets are full sequence of packets that form exactly one diagnostic message. + + :param packets: Packets sequence to check. + + :raise ValueError: Provided value is not CAN packets sequence. + :raise NotImplementedError: There is missing implementation for the provided initial packet type. + Please create an issue in our `Issues Tracking System `_ + with detailed description if you face this error. + + :return: True if the packets form exactly one diagnostic message. + False if there are missing, additional or inconsistent (e.g. two packets that initiate a message) packets. + """ + if not self.is_supported_packets_sequence_type(packets): + raise ValueError("Provided packets are not consistent CAN Packets sequence.") + if not CanPacketType.is_initial_packet_type(packets[0].packet_type): + return False + if packets[0].packet_type == CanPacketType.SINGLE_FRAME: + return len(packets) == 1 + if packets[0].packet_type == CanPacketType.FIRST_FRAME: + total_payload_size = packets[0].data_length + payload_bytes_found = len(packets[0].payload) # type: ignore + for following_packet in packets[1:]: + if CanPacketType.is_initial_packet_type(following_packet.packet_type): + return False + if payload_bytes_found >= total_payload_size: # type: ignore + return False + if following_packet.payload is not None: + payload_bytes_found += len(following_packet.payload) + return payload_bytes_found >= total_payload_size # type: ignore + raise NotImplementedError(f"Unknown packet type received: {packets[0].packet_type}") + + def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage, UdsMessageRecord]: """ Perform desegmentation of CAN packets. @@ -222,39 +257,6 @@ def segmentation(self, message: UdsMessage) -> PacketsTuple: return self.__functional_segmentation(message) raise NotImplementedError(f"Unknown addressing type received: {message.addressing_type}") - def is_desegmented_message(self, packets: PacketsContainersSequence) -> bool: # TODO: review - """ - Check whether provided packets are full sequence of packets that form exactly one diagnostic message. - - :param packets: Packets sequence to check. - - :raise ValueError: Provided value is not CAN packets sequence. - :raise NotImplementedError: There is missing implementation for the provided initial packet type. - Please create an issue in our `Issues Tracking System `_ - with detailed description if you face this error. - - :return: True if the packets form exactly one diagnostic message. - False if there are missing, additional or inconsistent (e.g. two packets that initiate a message) packets. - """ - if not self.is_supported_packets_sequence_type(packets): - raise ValueError("Provided packets are not consistent CAN Packets sequence.") - if not CanPacketType.is_initial_packet_type(packets[0].packet_type): - return False - if packets[0].packet_type == CanPacketType.SINGLE_FRAME: - return len(packets) == 1 - if packets[0].packet_type == CanPacketType.FIRST_FRAME: - total_payload_size = packets[0].data_length - payload_bytes_found = len(packets[0].payload) # type: ignore - for following_packet in packets[1:]: - if CanPacketType.is_initial_packet_type(following_packet.packet_type): - return False - if payload_bytes_found >= total_payload_size: # type: ignore - return False - if following_packet.payload is not None: - payload_bytes_found += len(following_packet.payload) - return payload_bytes_found >= total_payload_size # type: ignore - raise NotImplementedError(f"Unknown packet type received: {packets[0].packet_type}") - def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: rework """ Segment physically addressed diagnostic message. @@ -275,7 +277,7 @@ def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: payload=message.payload, filler_byte=self.filler_byte, dlc=None if self.use_data_optimization else self.dlc, - **self.physical_ai) + **self.tx_packets_physical_ai) return (single_frame,) ff_payload_size = CanFirstFrameHandler.get_payload_size( addressing_format=self.addressing_format, @@ -285,7 +287,7 @@ def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: payload=message.payload[:ff_payload_size], dlc=self.dlc, data_length=message_payload_size, - **self.physical_ai) + **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 @@ -300,11 +302,11 @@ def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: else self.dlc, sequence_number=sequence_number, filler_byte=self.filler_byte, - **self.physical_ai) + **self.tx_packets_physical_ai) consecutive_frames.append(consecutive_frame) return (first_frame, *consecutive_frames) - def __functional_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: rework + def __functional_segmentation(self, message: UdsMessage) -> PacketsTuple: """ Segment functionally addressed diagnostic message. @@ -324,5 +326,5 @@ def __functional_segmentation(self, message: UdsMessage) -> PacketsTuple: # TOD payload=message.payload, filler_byte=self.filler_byte, dlc=None if self.use_data_optimization else self.dlc, - **self.functional_ai) + **self.tx_packets_functional_ai) return (single_frame,) diff --git a/uds/transmission_attributes/addressing.py b/uds/transmission_attributes/addressing.py index 73f2d3e7..df15fef2 100644 --- a/uds/transmission_attributes/addressing.py +++ b/uds/transmission_attributes/addressing.py @@ -21,9 +21,9 @@ class AddressingType(StrEnum, ValidatedEnum): :ref:`Addressing ` describes a communication model that is used during UDS communication. """ - PHYSICAL = "Physical" + PHYSICAL: "AddressingType" = "Physical" # type: ignore """:ref:`Physical addressing ` - 1 (client) to 1 (server) communication.""" - FUNCTIONAL = "Functional" + FUNCTIONAL: "AddressingType" = "Functional" # type: ignore """:ref:`Functional addressing ` - 1 (client) to many (servers) communication.""" From 4179cc9b165158f5dd88d67977b4186af6424e47 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 21 Sep 2023 11:42:00 +0200 Subject: [PATCH 16/40] temporary version - sync send_packet Implemented sync send_packet method in transport_interface, but that causes problems with async_implementation (both Async and Sync Queue don't like each other). I need to either switch to only async methods or find a way to have both queues types. --- .../can/kvaser with python-can/config.wcc | 221 ++++++++++++++++++ .../listeners_check (remove).py} | 27 ++- .../send_and_receive_packets.py | 42 ++++ uds/segmentation/can_segmenter.py | 2 +- .../can_transport_interface.py | 32 ++- 5 files changed, 301 insertions(+), 23 deletions(-) create mode 100644 examples/can/kvaser with python-can/config.wcc rename examples/{kvaser.py => can/kvaser with python-can/listeners_check (remove).py} (52%) create mode 100644 examples/can/kvaser with python-can/send_and_receive_packets.py diff --git a/examples/can/kvaser with python-can/config.wcc b/examples/can/kvaser with python-can/config.wcc new file mode 100644 index 00000000..0f58375a --- /dev/null +++ b/examples/can/kvaser with python-can/config.wcc @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + +]> + + + + + 96 + 515 + 215 + 435 + 300 + 0 + 0 + + + 96 + 6 + 21 + 663 + 101 + 0 + 1 + Kvaser CanKing + + + ^TCANKingDialog\.,$,,0,TCanCtrlWin,,0 + ^TCANKingDialog\.,$,,0,THistoryWindow,,0 + TTrafficGenerator$,$,,0,TCanCtrlWin,,0 + TTimedTransmission$,$,,0,TCanCtrlWin,,0 + + + + TCanCtrlWin + 6 + 127 + 340 + 650 + 0 + 1 + CAN 1 + 96 + 1000000 + 5 + 2 + 2 + 500000 + 63 + 16 + 16 + 4000000 + 7 + 2 + 2 + 1 + 0 + 1 + 0 + + + TFormatter + 351 + 127 + 319 + 316 + 0 + 1 + Select Formatters + 96 + + StandardText + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + TOutputWindow + 676 + 21 + 1724 + 1061 + 0 + 1 + Output Window + 96 + 1 + 0 + + + THistoryWindow + 389 + 411 + 453 + 275 + 0 + 0 + History list + 96 + + + TTimedTransmission + 389 + 411 + 0 + 0 + Timed transmission + 96 + 10 + 0 + 0 + + + TLogToFile + 530 + 448 + 0 + 0 + Log To Text File + 96 + + + + TTextWindow + 389 + 411 + 600 + 233 + 0 + 0 + System Messages + 96 + + + TLogFilePlayer + 356 + 455 + 0 + 0 + Log File Player + 96 + + 1 + + 1,0,2 + + + 0,1,0,0 + 1,2,0,0 + 3,4,1,0 + 1,2,1,1 + 4,0,0,0 + 1,5,0,0 + 1,5,1,1 + 7,4,0,0 + + + + + $MAIN + 0 + View1 + *,&Messages,MsgMenu + + + Universal,C,U,TCanKingDialog.TUtilPage.TUniversalMessage + Traffic Generator,C,G,TTrafficGenerator + *,Misc,MiscMenu + + + 1-4 Ints,C,I,TCanKingDialog.TUtilPage.TInt4Page + 1-2 Longs,C,L,TCanKingDialog.TUtilPage.TLongintPage + 1-2 Floats,C,F,TCanKingDialog.TUtilPage.TFloatPage + String,C,S,TCanKingDialog.TUtilPage.TStrPage + Error Frame,C,E,TCanKingDialog.TUtilPage.TErrorFlagPage + + + $MAIN + 0 + _MsgMenu + *,&Options,OptionsMenu + + + &Global...,,,TGlobalOptionsDlg + + + + + 0 + 0 + 0 + + + + 0 + + + 0 + + + diff --git a/examples/kvaser.py b/examples/can/kvaser with python-can/listeners_check (remove).py similarity index 52% rename from examples/kvaser.py rename to examples/can/kvaser with python-can/listeners_check (remove).py index cac87c38..1386320b 100644 --- a/examples/kvaser.py +++ b/examples/can/kvaser with python-can/listeners_check (remove).py @@ -1,11 +1,12 @@ import can bus1 = can.Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) -bus2 = can.Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) +# bus2 = can.Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) listener1 = can.BufferedReader() -listener2 = can.BufferedReader() -notifier1 = can.Notifier(bus=bus1, listeners=[listener1]) -notifier2 = can.Notifier(bus=bus2, listeners=[listener2]) +async_listener1 = can.AsyncBufferedReader() +# listener2 = can.BufferedReader() +notifier1 = can.Notifier(bus=bus1, listeners=[listener1, async_listener1]) +# notifier2 = can.Notifier(bus=bus2, listeners=[listener2]) msg1 = can.Message(arbitration_id=0x123456, @@ -18,19 +19,23 @@ print("#MSG 1 sent to bus1") bus1.send(msg=msg1) +print(listener1.buffer.qsize()) print("#MSG 2 sent to bus1") -bus1.send(msg=msg2) +# bus1.send(msg=msg2) +print(listener1.buffer.qsize()) print("#MSG 2 sent to bus2") -bus2.send(msg=msg1) +# bus2.send(msg=msg1) +x = listener1.get_message() +print(x) +print(type(x)) +# print(listener2.get_message()) print(listener1.get_message()) -print(listener2.get_message()) +# print(listener2.get_message()) print(listener1.get_message()) -print(listener2.get_message()) +# print(listener2.get_message()) print(listener1.get_message()) -print(listener2.get_message()) -print(listener1.get_message()) -print(listener2.get_message()) +# print(listener2.get_message()) diff --git a/examples/can/kvaser with python-can/send_and_receive_packets.py b/examples/can/kvaser with python-can/send_and_receive_packets.py new file mode 100644 index 00000000..34a91041 --- /dev/null +++ b/examples/can/kvaser with python-can/send_and_receive_packets.py @@ -0,0 +1,42 @@ +from pprint import pprint +import asyncio + +from can import Bus, Message +from uds.transport_interface import PyCanTransportInterface +from uds.can import CanAddressingInformation, CanAddressingFormat +from uds.message import UdsMessage +from uds.transmission_attributes import AddressingType + +kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +example_addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}, +) + +can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, + addressing_information=example_addressing_information) + +message_1 = UdsMessage(addressing_type=AddressingType.PHYSICAL, payload=[0x10, 0x03]) +message_2 = UdsMessage(addressing_type=AddressingType.FUNCTIONAL, payload=[0x3E]) + +packet_1 = can_ti.segmenter.segmentation(message_1)[0] +packet_2 = can_ti.segmenter.segmentation(message_2)[0] + +kvaser_bus.send(Message(arbitration_id=0x611, data=list(range(8)))) +sent_record_1 = can_ti.send_packet(packet=packet_1) +kvaser_bus.send(Message(arbitration_id=0x6FF, data=[0x01, 0x3E])) +sent_record_2 = can_ti.send_packet(packet=packet_2) + +pprint(sent_record_1.__dict__) +pprint(sent_record_2.__dict__) + +# kvaser_bus.send(Message(arbitration_id=0x612, data=list(range(8)))) +# kvaser_bus.send(Message(arbitration_id=0x6FE, data=[0x01, 0x3E])) +# received_record_1 = asyncio.run(can_ti.receive_packet()) +# received_record_2 = asyncio.run(can_ti.receive_packet()) +# +# pprint(received_record_1.__dict__) +# pprint(received_record_2.__dict__) diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py index 18f17246..b854cc2b 100644 --- a/uds/segmentation/can_segmenter.py +++ b/uds/segmentation/can_segmenter.py @@ -257,7 +257,7 @@ def segmentation(self, message: UdsMessage) -> PacketsTuple: return self.__functional_segmentation(message) raise NotImplementedError(f"Unknown addressing type received: {message.addressing_type}") - def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: # TODO: rework + def __physical_segmentation(self, message: UdsMessage) -> PacketsTuple: """ Segment physically addressed diagnostic message. diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index debd7f7a..5fc465e8 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -5,7 +5,7 @@ from typing import Optional, Any from abc import abstractmethod from warnings import warn -from asyncio import wait_for +from asyncio import wait_for, get_event_loop from datetime import datetime from can import BusABC, AsyncBufferedReader, BufferedReader, Notifier, Message @@ -382,10 +382,13 @@ def __init__(self, super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, **kwargs) - self.__async_listener = AsyncBufferedReader() self.__sync_listener = BufferedReader() - self.__can_notifier = Notifier(bus=self.bus_manager, - listeners=[self.__async_listener, self.__sync_listener]) + self.__sync_can_notifier = Notifier(bus=self.bus_manager, + listeners=[self.__sync_listener]) + self.__async_listener = AsyncBufferedReader() # TODO: make it work + # self.__async_can_notifier = Notifier(bus=self.bus_manager, + # listeners=[self.__async_listener], + # loop=get_event_loop()) @property def n_as_measured(self) -> Optional[TimeMilliseconds]: @@ -434,7 +437,7 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: """ return isinstance(bus_manager, BusABC) # TODO: check that receive_own_messages is set (if possible) - def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore + def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore # TODO: test """ Transmit CAN packet. @@ -451,10 +454,18 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore data=packet.raw_frame_data, is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) self.bus_manager.send(can_message) - # TODO: get sent packet from self.__sync_listener - NOTE: we have to see sent messages - # TODO: create CanPacketRecord and return it - - async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: + observed_frame = self.__sync_listener.get_message() + while observed_frame.arbitration_id != packet.can_id \ + or tuple(observed_frame.data) != packet.raw_frame_data \ + or not observed_frame.is_rx: + observed_frame = self.__sync_listener.get_message() + 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)) + + async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: # TODO: test """ Receive CAN packet. @@ -479,9 +490,8 @@ async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> Ca packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) packet_addressing_format = self.segmenter.addressing_format - received_datetime = datetime.now() # TODO: get better accuracy: use received_frame.timestamp return CanPacketRecord(frame=received_frame, direction=TransmissionDirection.RECEIVED, addressing_type=packet_addressing_type, addressing_format=packet_addressing_format, - transmission_time=received_datetime) + transmission_time=datetime.fromtimestamp(received_frame.timestamp)) From 153f3e225f78d7aa37adf20b459c8e8c67afebb7 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Fri, 29 Sep 2023 15:53:11 +0200 Subject: [PATCH 17/40] provide synchronous and asynchronous implementation for sending/receiving packets - examples - remade Abstract Transport Interface - raw code with TODOs for python-can Transport Interface --- .../kvaser with python-can/receive_packets.py | 36 +++++ .../receive_packets_asyncio.py | 38 +++++ .../kvaser with python-can/send_packets.py | 36 +++++ ...ive_packets.py => send_packets_asyncio.py} | 34 +++-- uds/packet/__init__.py | 2 +- uds/packet/can_packet.py | 10 +- uds/packet/can_packet_record.py | 2 +- .../abstract_transport_interface.py | 31 ++++- .../can_transport_interface.py | 131 +++++++++++++++--- 9 files changed, 267 insertions(+), 53 deletions(-) create mode 100644 examples/can/kvaser with python-can/receive_packets.py create mode 100644 examples/can/kvaser with python-can/receive_packets_asyncio.py create mode 100644 examples/can/kvaser with python-can/send_packets.py rename examples/can/kvaser with python-can/{send_and_receive_packets.py => send_packets_asyncio.py} (61%) diff --git a/examples/can/kvaser with python-can/receive_packets.py b/examples/can/kvaser with python-can/receive_packets.py new file mode 100644 index 00000000..d381a0b2 --- /dev/null +++ b/examples/can/kvaser with python-can/receive_packets.py @@ -0,0 +1,36 @@ +from pprint import pprint +from threading import Timer + +from can import Bus, Message +from uds.transport_interface import PyCanTransportInterface +from uds.can import CanAddressingInformation, CanAddressingFormat + +kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +example_addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}, +) +can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, + addressing_information=example_addressing_information) + +frame_1 = Message(arbitration_id=0x6FE, data=[0x10, 0x03]) +frame_2 = Message(arbitration_id=0x611, data=[0x10, 0x03]) # shall be ignored, as it is not observed CAN ID +frame_3 = Message(arbitration_id=0x612, data=[0x3E, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]) + + +def main(): + Timer(interval=1, function=kvaser_bus.send, args=(frame_1, )).run() + record_1 = can_ti.receive_packet() + pprint(record_1.__dict__) + + Timer(interval=1, function=kvaser_bus.send, args=(frame_2, )).run() + Timer(interval=2, function=kvaser_bus.send, args=(frame_3, )).run() + record_2 = can_ti.receive_packet() + pprint(record_2.__dict__) + + +if __name__ == "__main__": + main() diff --git a/examples/can/kvaser with python-can/receive_packets_asyncio.py b/examples/can/kvaser with python-can/receive_packets_asyncio.py new file mode 100644 index 00000000..45d3b87f --- /dev/null +++ b/examples/can/kvaser with python-can/receive_packets_asyncio.py @@ -0,0 +1,38 @@ +import asyncio +from pprint import pprint + +from can import Bus, Message +from uds.transport_interface import PyCanTransportInterface +from uds.can import CanAddressingInformation, CanAddressingFormat + +kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +example_addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}, +) +can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, + addressing_information=example_addressing_information) + +frame_1 = Message(arbitration_id=0x6FE, data=[0x10, 0x03]) +frame_2 = Message(arbitration_id=0x611, data=[0x10, 0x03]) # shall be ignored, as it is not observed CAN ID +frame_3 = Message(arbitration_id=0x612, data=[0x3E, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]) + + +async def main(): + r1 = can_ti.async_receive_packet() + kvaser_bus.send(frame_1) + record_1 = await r1 + pprint(record_1.__dict__) + + r2 = can_ti.async_receive_packet() + kvaser_bus.send(frame_2) + kvaser_bus.send(frame_3) + record_2 = await r2 + pprint(record_2.__dict__) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/can/kvaser with python-can/send_packets.py b/examples/can/kvaser with python-can/send_packets.py new file mode 100644 index 00000000..def294c4 --- /dev/null +++ b/examples/can/kvaser with python-can/send_packets.py @@ -0,0 +1,36 @@ +from pprint import pprint + +from can import Bus +from uds.transport_interface import PyCanTransportInterface +from uds.can import CanAddressingInformation, CanAddressingFormat +from uds.message import UdsMessage +from uds.transmission_attributes import AddressingType + +kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +example_addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}, +) +can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, + addressing_information=example_addressing_information) + +message_1 = UdsMessage(addressing_type=AddressingType.PHYSICAL, payload=[0x10, 0x03]) +message_2 = UdsMessage(addressing_type=AddressingType.FUNCTIONAL, payload=[0x3E]) + +packet_1 = can_ti.segmenter.segmentation(message_1)[0] +packet_2 = can_ti.segmenter.segmentation(message_2)[0] + + +def main(): + record_1 = can_ti.send_packet(packet_1) + record_2 = can_ti.send_packet(packet_2) + + pprint(record_1.__dict__) + pprint(record_2.__dict__) + + +if __name__ == "__main__": + main() diff --git a/examples/can/kvaser with python-can/send_and_receive_packets.py b/examples/can/kvaser with python-can/send_packets_asyncio.py similarity index 61% rename from examples/can/kvaser with python-can/send_and_receive_packets.py rename to examples/can/kvaser with python-can/send_packets_asyncio.py index 34a91041..254ed51b 100644 --- a/examples/can/kvaser with python-can/send_and_receive_packets.py +++ b/examples/can/kvaser with python-can/send_packets_asyncio.py @@ -1,7 +1,7 @@ -from pprint import pprint import asyncio +from pprint import pprint -from can import Bus, Message +from can import Bus from uds.transport_interface import PyCanTransportInterface from uds.can import CanAddressingInformation, CanAddressingFormat from uds.message import UdsMessage @@ -15,7 +15,6 @@ tx_functional={"can_id": 0x6FF}, rx_functional={"can_id": 0x6FE}, ) - can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, addressing_information=example_addressing_information) @@ -25,18 +24,17 @@ packet_1 = can_ti.segmenter.segmentation(message_1)[0] packet_2 = can_ti.segmenter.segmentation(message_2)[0] -kvaser_bus.send(Message(arbitration_id=0x611, data=list(range(8)))) -sent_record_1 = can_ti.send_packet(packet=packet_1) -kvaser_bus.send(Message(arbitration_id=0x6FF, data=[0x01, 0x3E])) -sent_record_2 = can_ti.send_packet(packet=packet_2) - -pprint(sent_record_1.__dict__) -pprint(sent_record_2.__dict__) - -# kvaser_bus.send(Message(arbitration_id=0x612, data=list(range(8)))) -# kvaser_bus.send(Message(arbitration_id=0x6FE, data=[0x01, 0x3E])) -# received_record_1 = asyncio.run(can_ti.receive_packet()) -# received_record_2 = asyncio.run(can_ti.receive_packet()) -# -# pprint(received_record_1.__dict__) -# pprint(received_record_2.__dict__) + +async def main(): + r1 = can_ti.async_send_packet(packet_1) + r2 = can_ti.async_send_packet(packet_2) + + record_1 = await r1 + record_2 = await r2 + + pprint(record_1.__dict__) + pprint(record_2.__dict__) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/uds/packet/__init__.py b/uds/packet/__init__.py index 8b0c064d..f6053d29 100644 --- a/uds/packet/__init__.py +++ b/uds/packet/__init__.py @@ -15,5 +15,5 @@ PacketsSequence, PacketsTuple, PacketsList, \ PacketsRecordsSequence, PacketsRecordsTuple, PacketsRecordsList from .abstract_can_packet_container import AbstractCanPacketContainer -from .can_packet import CanPacket +from .can_packet import CanPacket, AnyCanPacket from .can_packet_record import CanPacketRecord diff --git a/uds/packet/can_packet.py b/uds/packet/can_packet.py index ad56bcc9..909f417f 100644 --- a/uds/packet/can_packet.py +++ b/uds/packet/can_packet.py @@ -577,14 +577,6 @@ class AnyCanPacket(AbstractCanPacketContainer, AbstractUdsPacket): other features that this class is missing. """ - class DecodedAIParamsAlias(TypedDict, total=True): - """Alias of :ref:`Addressing Information ` parameters encoded in any CAN Packet.""" - - addressing_type: Optional[AddressingTypeAlias] - target_address: Optional[int] - source_address: Optional[int] - address_extension: Optional[int] - def __init__(self, *, raw_frame_data: RawBytes, addressing_format: CanAddressingFormatAlias, @@ -688,7 +680,7 @@ def packet_type(self) -> Optional[int]: # type: ignore return None return self.raw_frame_data[ai_data_bytes_number] >> 4 - def get_addressing_information(self) -> DecodedAIParamsAlias: + def get_addressing_information(self) -> CanAddressingInformation.DecodedAIParamsAlias: """ Get Addressing Information carried by this packet. diff --git a/uds/packet/can_packet_record.py b/uds/packet/can_packet_record.py index 6906a390..56b4bc17 100644 --- a/uds/packet/can_packet_record.py +++ b/uds/packet/can_packet_record.py @@ -136,7 +136,7 @@ def _validate_frame(value: Any) -> None: :raise ValueError: Some attribute of the frame argument is missing or its value is unexpected. """ if isinstance(value, PythonCanMessage): - CanIdHandler.validate_can_id(value.arbitration_id, extended_can_id=value.is_extended_id) + CanIdHandler.validate_can_id(value.arbitration_id) CanDlcHandler.validate_data_bytes_number(len(value.data)) return None raise TypeError(f"Unsupported CAN Frame type was provided. Actual type: {type(value)}") diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index 6f188a66..82081fa9 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -4,6 +4,7 @@ from typing import Optional, Any from abc import ABC, abstractmethod +from asyncio import AbstractEventLoop from uds.utilities import TimeMilliseconds from uds.packet import AbstractUdsPacket, AbstractUdsPacketRecord @@ -71,7 +72,7 @@ def send_packet(self, packet: AbstractUdsPacket) -> AbstractUdsPacketRecord: """ @abstractmethod - async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> AbstractUdsPacketRecord: + def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> AbstractUdsPacketRecord: """ Receive UDS packet. @@ -81,3 +82,31 @@ async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> Ab :return: Record with historic information about received UDS packet. """ + + @abstractmethod + async def async_send_packet(self, + packet: AbstractUdsPacket, + loop: Optional[AbstractEventLoop] = None) -> AbstractUdsPacketRecord: + """ + Transmit UDS packet asynchronously. + + :param packet: A packet to send. + :param loop: An asyncio event loop used for observing messages. + + :return: Record with historic information about transmitted UDS packet. + """ + + @abstractmethod + async def async_receive_packet(self, + timeout: Optional[TimeMilliseconds] = None, + loop: Optional[AbstractEventLoop] = None) -> AbstractUdsPacketRecord: + """ + Receive UDS packet asynchronously. + + :param timeout: Maximal time (in milliseconds) to wait. + :param loop: An asyncio event loop used for observing messages. + + :raise TimeoutError: Timeout was reached. + + :return: Record with historic information about received UDS packet. + """ diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 5fc465e8..7dc75336 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -2,17 +2,18 @@ __all__ = ["AbstractCanTransportInterface", "PyCanTransportInterface"] -from typing import Optional, Any +from typing import Any, Optional, Union from abc import abstractmethod from warnings import warn -from asyncio import wait_for, get_event_loop +from asyncio import AbstractEventLoop, wait_for, get_running_loop from datetime import datetime +from time import time from can import BusABC, AsyncBufferedReader, BufferedReader, Notifier, Message from uds.utilities import TimeMilliseconds, ValueWarning from uds.can import AbstractCanAddressingInformation, CanIdHandler, CanDlcHandler -from uds.packet import CanPacket, CanPacketRecord +from uds.packet import CanPacket, AnyCanPacket, CanPacketRecord from uds.transmission_attributes import TransmissionDirection from uds.segmentation import CanSegmenter from .abstract_transport_interface import AbstractTransportInterface @@ -382,13 +383,6 @@ def __init__(self, super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, **kwargs) - self.__sync_listener = BufferedReader() - self.__sync_can_notifier = Notifier(bus=self.bus_manager, - listeners=[self.__sync_listener]) - self.__async_listener = AsyncBufferedReader() # TODO: make it work - # self.__async_can_notifier = Notifier(bus=self.bus_manager, - # listeners=[self.__async_listener], - # loop=get_event_loop()) @property def n_as_measured(self) -> Optional[TimeMilliseconds]: @@ -437,39 +431,122 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: """ return isinstance(bus_manager, BusABC) # TODO: check that receive_own_messages is set (if possible) - def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore # TODO: test + def send_packet(self, packet: Union[CanPacket, AnyCanPacket]) -> CanPacketRecord: + """ + Transmit CAN packet. + + .. warning:: Must not be called within an asynchronous function. + + :param packet: CAN packet to send. + + :return: Record with historic information about transmitted CAN packet. + """ + if not isinstance(packet, (CanPacket, AnyCanPacket)): + raise TypeError("Provided packet value does not contain a CAN Packet.") + can_message = Message(arbitration_id=packet.can_id, + 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)) + message_listener = BufferedReader() + notifier = Notifier(bus=self.bus_manager, + listeners=[message_listener]) + self.bus_manager.send(can_message) + observed_frame = None + while observed_frame is None \ + or observed_frame.arbitration_id != packet.can_id \ + or tuple(observed_frame.data) != packet.raw_frame_data \ + or not observed_frame.is_rx: + observed_frame = message_listener.get_message() # TODO: add wait_for and proper timeout (N_AS ?) + notifier.stop() + 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[TimeMilliseconds] = None) -> CanPacketRecord: # TODO: make it possible to receive old packets (received in a past) + """ + 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.") + message_listener = BufferedReader() + notifier = Notifier(bus=self.bus_manager, + listeners=[message_listener]) + packet_addressing_type = None + while packet_addressing_type is None: + time_now = time() + _timeout_left = None if timeout is None else timeout - (time_now - time_start)*1000 + received_frame = message_listener.get_message(timeout=_timeout_left) + packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, + data=received_frame.data) + notifier.stop() + 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: Union[CanPacket, AnyCanPacket], + loop: Optional[AbstractEventLoop] = None) -> CanPacketRecord: # type: ignore """ Transmit CAN packet. :param packet: CAN packet to send. + :param loop: An asyncio event loop used for observing messages. :raise TypeError: Provided packet is not CAN Packet. :return: Record with historic information about transmitted CAN packet. """ - if not isinstance(packet, CanPacket): - raise TypeError("Provided packet value does not contain CAN Packet.") + loop = get_running_loop() if loop is None else loop + if not isinstance(packet, (CanPacket, AnyCanPacket)): + raise TypeError("Provided packet value does not contain a CAN Packet.") + async_message_listener = AsyncBufferedReader() + async_notifier = Notifier(bus=self.bus_manager, + listeners=[async_message_listener], + loop=loop) can_message = Message(arbitration_id=packet.can_id, 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)) self.bus_manager.send(can_message) - observed_frame = self.__sync_listener.get_message() - while observed_frame.arbitration_id != packet.can_id \ + observed_frame = None + while observed_frame is None \ + or observed_frame.arbitration_id != packet.can_id \ or tuple(observed_frame.data) != packet.raw_frame_data \ or not observed_frame.is_rx: - observed_frame = self.__sync_listener.get_message() + observed_frame = await async_message_listener.get_message() # TODO: add wait_for and proper timeout (N_AS ?) + async_notifier.stop() 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)) - async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: # TODO: test + async def async_receive_packet(self, + timeout: Optional[TimeMilliseconds] = None, + loop: Optional[AbstractEventLoop] = None) -> CanPacketRecord: """ Receive CAN packet. :param timeout: Maximal time (in milliseconds) to wait. + :param loop: An asyncio event loop used for observing messages. :raise TypeError: Provided timeout value is not None neither int nor float type. :raise ValueError: Provided timeout value is less or equal 0. @@ -477,21 +554,29 @@ async def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> Ca :return: Record with historic information about received CAN packet. """ + loop = get_running_loop() if loop is None else loop + 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.") - received_frame = await wait_for(self.__async_listener.get_message(), timeout) - packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, - data=received_frame.data) + async_message_listener = AsyncBufferedReader() + async_notifier = Notifier(bus=self.bus_manager, + listeners=[async_message_listener], + loop=loop) + packet_addressing_type = None while packet_addressing_type is None: - received_frame = await wait_for(self.__async_listener.get_message(), timeout) + time_now = time() + _timeout_left = None if timeout is None else timeout - (time_now - time_start)*1000 + received_frame = await wait_for(async_message_listener.get_message(), + timeout=_timeout_left, + loop=loop) packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) - packet_addressing_format = self.segmenter.addressing_format + async_notifier.stop() return CanPacketRecord(frame=received_frame, direction=TransmissionDirection.RECEIVED, addressing_type=packet_addressing_type, - addressing_format=packet_addressing_format, + addressing_format=self.segmenter.addressing_format, transmission_time=datetime.fromtimestamp(received_frame.timestamp)) From 880de97ba7f560b7552e55a0ce567927da6caa08 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Fri, 29 Sep 2023 18:00:39 +0200 Subject: [PATCH 18/40] Update can_transport_interface.py Make n_ar and n_as measurements --- .../can_transport_interface.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 7dc75336..d4bce60c 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -13,7 +13,7 @@ from uds.utilities import TimeMilliseconds, ValueWarning from uds.can import AbstractCanAddressingInformation, CanIdHandler, CanDlcHandler -from uds.packet import CanPacket, AnyCanPacket, CanPacketRecord +from uds.packet import CanPacket, AnyCanPacket, CanPacketRecord, CanPacketType from uds.transmission_attributes import TransmissionDirection from uds.segmentation import CanSegmenter from .abstract_transport_interface import AbstractTransportInterface @@ -441,8 +441,11 @@ def send_packet(self, packet: Union[CanPacket, AnyCanPacket]) -> CanPacketRecord :return: Record with historic information about transmitted CAN packet. """ + time_start = time() if not isinstance(packet, (CanPacket, AnyCanPacket)): raise TypeError("Provided packet value does not contain a CAN Packet.") + is_flow_control_packet = packet.packet_type == CanPacketType.FLOW_CONTROL + timeout = self.n_ar_timeout if is_flow_control_packet else self.n_as_timeout can_message = Message(arbitration_id=packet.can_id, is_extended_id=CanIdHandler.is_extended_can_id(packet.can_id), data=packet.raw_frame_data, @@ -456,8 +459,14 @@ def send_packet(self, packet: Union[CanPacket, AnyCanPacket]) -> CanPacketRecord or observed_frame.arbitration_id != packet.can_id \ or tuple(observed_frame.data) != packet.raw_frame_data \ or not observed_frame.is_rx: - observed_frame = message_listener.get_message() # TODO: add wait_for and proper timeout (N_AS ?) + time_now = time() + timeout_left = timeout - (time_now - time_start) * 1000. + observed_frame = message_listener.get_message(timeout=timeout_left) notifier.stop() + if is_flow_control_packet: + self.__n_ar_measured = (observed_frame.timestamp - time_start) * 1000. + else: + self.__n_as_measured = (observed_frame.timestamp - time_start) * 1000. return CanPacketRecord(frame=observed_frame, direction=TransmissionDirection.TRANSMITTED, addressing_type=packet.addressing_type, @@ -490,8 +499,8 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacke packet_addressing_type = None while packet_addressing_type is None: time_now = time() - _timeout_left = None if timeout is None else timeout - (time_now - time_start)*1000 - received_frame = message_listener.get_message(timeout=_timeout_left) + timeout_left = None if timeout is None else timeout - (time_now - time_start)*1000 + received_frame = message_listener.get_message(timeout=timeout_left) packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) notifier.stop() @@ -514,9 +523,12 @@ async def async_send_packet(self, :return: Record with historic information about transmitted CAN packet. """ - loop = get_running_loop() if loop is None else loop + time_start = time() if not isinstance(packet, (CanPacket, AnyCanPacket)): raise TypeError("Provided packet value does not contain a CAN Packet.") + loop = get_running_loop() if loop is None else loop + is_flow_control_packet = packet.packet_type == CanPacketType.FLOW_CONTROL + timeout = self.n_ar_timeout if is_flow_control_packet else self.n_as_timeout async_message_listener = AsyncBufferedReader() async_notifier = Notifier(bus=self.bus_manager, listeners=[async_message_listener], @@ -531,8 +543,14 @@ async def async_send_packet(self, or observed_frame.arbitration_id != packet.can_id \ or tuple(observed_frame.data) != packet.raw_frame_data \ or not observed_frame.is_rx: - observed_frame = await async_message_listener.get_message() # TODO: add wait_for and proper timeout (N_AS ?) + time_now = time() + timeout_left = timeout - (time_now - time_start) * 1000. + observed_frame = await wait_for(async_message_listener.get_message(), timeout=timeout_left) async_notifier.stop() + if is_flow_control_packet: + self.__n_ar_measured = (observed_frame.timestamp - time_start) * 1000. + else: + self.__n_as_measured = (observed_frame.timestamp - time_start) * 1000. return CanPacketRecord(frame=observed_frame, direction=TransmissionDirection.TRANSMITTED, addressing_type=packet.addressing_type, From 88084e3ac712afb989922e1616f68e03a5621125 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 4 Oct 2023 18:25:48 +0200 Subject: [PATCH 19/40] add system tests Add tests for python-can transport interface with Kvaser. --- .../listeners_check (remove).py | 41 ---- requirements.txt | 2 +- .../packet/test_can_packet_record.py | 2 +- .../test_can_transport_interface.py | 214 +++++++++++++++++- tests/system_tests/__init__.py | 0 .../transport_interface/__init__.py | 0 .../can_transport_interface/__init__.py | 0 .../can_transport_interface/python_can.py | 192 ++++++++++++++++ uds/packet/can_packet.py | 2 +- .../can_transport_interface.py | 58 ++--- 10 files changed, 430 insertions(+), 81 deletions(-) delete mode 100644 examples/can/kvaser with python-can/listeners_check (remove).py create mode 100644 tests/system_tests/__init__.py create mode 100644 tests/system_tests/transport_interface/__init__.py create mode 100644 tests/system_tests/transport_interface/can_transport_interface/__init__.py create mode 100644 tests/system_tests/transport_interface/can_transport_interface/python_can.py diff --git a/examples/can/kvaser with python-can/listeners_check (remove).py b/examples/can/kvaser with python-can/listeners_check (remove).py deleted file mode 100644 index 1386320b..00000000 --- a/examples/can/kvaser with python-can/listeners_check (remove).py +++ /dev/null @@ -1,41 +0,0 @@ -import can - -bus1 = can.Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) -# bus2 = can.Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) -listener1 = can.BufferedReader() -async_listener1 = can.AsyncBufferedReader() -# listener2 = can.BufferedReader() -notifier1 = can.Notifier(bus=bus1, listeners=[listener1, async_listener1]) -# notifier2 = can.Notifier(bus=bus2, listeners=[listener2]) - - -msg1 = can.Message(arbitration_id=0x123456, - dlc=64, - is_fd=True, - data=8*[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]) -msg2 = can.Message(arbitration_id=0x7FF, - dlc=8, - data=[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF][::-1]) - -print("#MSG 1 sent to bus1") -bus1.send(msg=msg1) -print(listener1.buffer.qsize()) - -print("#MSG 2 sent to bus1") -# bus1.send(msg=msg2) -print(listener1.buffer.qsize()) - -print("#MSG 2 sent to bus2") -# bus2.send(msg=msg1) - -x = listener1.get_message() -print(x) -print(type(x)) -# print(listener2.get_message()) -print(listener1.get_message()) -# print(listener2.get_message()) -print(listener1.get_message()) -# print(listener2.get_message()) -print(listener1.get_message()) -# print(listener2.get_message()) - diff --git a/requirements.txt b/requirements.txt index 185a99fa..c0813513 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ aenum>=3.0.0 -python-can>=3.0.0 \ No newline at end of file +python-can>=4.0.0 \ No newline at end of file diff --git a/tests/software_tests/packet/test_can_packet_record.py b/tests/software_tests/packet/test_can_packet_record.py index 99a7e59a..44edd209 100644 --- a/tests/software_tests/packet/test_can_packet_record.py +++ b/tests/software_tests/packet/test_can_packet_record.py @@ -147,7 +147,7 @@ def test_validate_frame__invalid_type(self, frame): def test_validate_frame__valid_python_can(self, example_python_can_message): assert CanPacketRecord._validate_frame(example_python_can_message) is None self.mock_can_id_handler_class.validate_can_id.assert_called_once_with( - example_python_can_message.arbitration_id, extended_can_id=example_python_can_message.is_extended_id) + example_python_can_message.arbitration_id) self.mock_can_dlc_handler_class.validate_data_bytes_number.assert_called_once_with( len(example_python_can_message.data)) 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 48a63b1a..d5c2ba39 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -1,8 +1,8 @@ import pytest -from mock import MagicMock, Mock, patch +from mock import MagicMock, Mock, AsyncMock, patch from uds.transport_interface.can_transport_interface import AbstractCanTransportInterface, PyCanTransportInterface, \ - AbstractCanAddressingInformation, BusABC + AbstractCanAddressingInformation, BusABC, CanPacket, CanPacketType, TransmissionDirection from uds.can import CanAddressingInformation, CanAddressingFormat @@ -224,7 +224,7 @@ def test_n_ar_timeout__set__valid_without_warn(self, mock_isinstance): mock_ne.assert_called_once_with(self.mock_can_transport_interface.N_AR_TIMEOUT) self.mock_warn.assert_not_called() assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_ar_timeout == mock_value - + # n_bs @pytest.mark.parametrize("value", ["something", Mock()]) @@ -275,7 +275,7 @@ def test_n_bs_timeout__set__valid_without_warn(self, mock_isinstance): mock_ne.assert_called_once_with(self.mock_can_transport_interface.N_BS_TIMEOUT) self.mock_warn.assert_not_called() assert self.mock_can_transport_interface._AbstractCanTransportInterface__n_bs_timeout == mock_value - + # n_br @pytest.mark.parametrize("value", ["something", Mock()]) @@ -399,7 +399,7 @@ def test_n_cs_max__n_as_not_measured(self, n_cr_timeout): self.mock_can_transport_interface.n_as_measured = None assert AbstractCanTransportInterface.n_cs_max.fget(self.mock_can_transport_interface) \ == 0.9 * n_cr_timeout - + # n_cr @pytest.mark.parametrize("value", ["something", Mock()]) @@ -469,7 +469,7 @@ def test_dlc__get(self): def test_dlc__set(self, value): AbstractCanTransportInterface.dlc.fset(self.mock_can_transport_interface, value) assert self.mock_can_transport_interface.segmenter.dlc == value - + # use_data_optimization def test_use_data_optimization__get(self): @@ -480,7 +480,7 @@ def test_use_data_optimization__get(self): def test_use_data_optimization__set(self, value): AbstractCanTransportInterface.use_data_optimization.fset(self.mock_can_transport_interface, value) assert self.mock_can_transport_interface.segmenter.use_data_optimization == value - + # filler_byte def test_filler_byte__get(self): @@ -497,13 +497,43 @@ class TestPyCanTransportInterface: """Unit tests for `PyCanTransportInterface` class.""" def setup_method(self): - self.mock_can_transport_interface = Mock(spec=PyCanTransportInterface) + self.mock_can_transport_interface = MagicMock(spec=PyCanTransportInterface) # patching + self._patcher_wait_for = patch(f"{SCRIPT_LOCATION}.wait_for", AsyncMock(side_effect=lambda *args, **kwargs: args[0])) + self.mock_wait_for = self._patcher_wait_for.start() + self._patcher_time = patch(f"{SCRIPT_LOCATION}.time") + self.mock_time = self._patcher_time.start() + self._patcher_datetime = patch(f"{SCRIPT_LOCATION}.datetime") + self.mock_datetime = self._patcher_datetime.start() self._patcher_abstract_can_ti_init = patch(f"{SCRIPT_LOCATION}.AbstractCanTransportInterface.__init__") self.mock_abstract_can_ti_init = self._patcher_abstract_can_ti_init.start() + self._patcher_can_id_handler = patch(f"{SCRIPT_LOCATION}.CanIdHandler") + 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_packet_record = patch(f"{SCRIPT_LOCATION}.CanPacketRecord") + self.mock_can_packet_record = self._patcher_can_packet_record.start() + self._patcher_buffered_reader = patch(f"{SCRIPT_LOCATION}.BufferedReader") + self.mock_buffered_reader = self._patcher_buffered_reader.start() + self._patcher_async_buffered_reader = patch(f"{SCRIPT_LOCATION}.AsyncBufferedReader") + self.mock_async_buffered_reader = self._patcher_async_buffered_reader.start() + self._patcher_notifier = patch(f"{SCRIPT_LOCATION}.Notifier") + self.mock_notifier = self._patcher_notifier.start() + self._patcher_message = patch(f"{SCRIPT_LOCATION}.Message") + self.mock_message = self._patcher_message.start() def teardown_method(self): + self._patcher_wait_for.stop() + self._patcher_time.stop() + self._patcher_datetime.stop() self._patcher_abstract_can_ti_init.stop() + self._patcher_can_id_handler.stop() + self._patcher_can_dlc_handler.stop() + self._patcher_can_packet_record.stop() + self._patcher_buffered_reader.stop() + self._patcher_async_buffered_reader.stop() + self._patcher_notifier.stop() + self._patcher_message.stop() # __init__ @@ -580,6 +610,174 @@ def test_is_supported_bus_manager(self, mock_isinstance, value): assert PyCanTransportInterface.is_supported_bus_manager(value) == mock_isinstance.return_value mock_isinstance.assert_called_once_with(value, BusABC) + # send_packet + + @pytest.mark.parametrize("packet", ["something", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + def test_send_packet__type_error(self, mock_isinstance, packet): + mock_isinstance.return_value = False + with pytest.raises(TypeError): + PyCanTransportInterface.send_packet(self.mock_can_transport_interface, packet) + mock_isinstance.assert_called_once_with(packet, CanPacket) + + @pytest.mark.parametrize("packet", [ + Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME, raw_frame_data=(0x12, 0x34)), + Mock(spec=CanPacket, packet_type=CanPacketType.FLOW_CONTROL, raw_frame_data=tuple(range(8))), + Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME, raw_frame_data=tuple(range(64, 128))), + ]) + def test_send_packet(self, packet): + mock_get_message = Mock(return_value=MagicMock(arbitration_id=packet.can_id, + data=packet.raw_frame_data, + is_rx=True)) + self.mock_buffered_reader.return_value.get_message = mock_get_message + self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured = None + self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured = None + assert PyCanTransportInterface.send_packet(self.mock_can_transport_interface, packet) \ + == self.mock_can_packet_record.return_value + self.mock_can_id_handler.is_extended_can_id.assert_called_once_with(packet.can_id) + self.mock_can_dlc_handler.is_can_fd_specific_dlc.assert_called_once_with(packet.dlc) + self.mock_message.assert_called_once_with(arbitration_id=packet.can_id, + is_extended_id=self.mock_can_id_handler.is_extended_can_id.return_value, + data=packet.raw_frame_data, + is_fd=self.mock_can_dlc_handler.is_can_fd_specific_dlc.return_value) + self.mock_can_transport_interface.bus_manager.send.assert_called_once_with(self.mock_message.return_value) + self.mock_datetime.fromtimestamp.assert_called_once_with(mock_get_message.return_value.timestamp) + self.mock_can_packet_record.assert_called_once_with(frame=mock_get_message.return_value, + direction=TransmissionDirection.TRANSMITTED, + addressing_type=packet.addressing_type, + addressing_format=packet.addressing_format, + 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) + 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_ar_measured is None + mock_get_message.return_value.timestamp.__sub__.assert_called_once_with(self.mock_time.return_value) + + # receive_packet + + @pytest.mark.parametrize("timeout", ["something", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + def test_receive_packet__type_error(self, mock_isinstance, timeout): + mock_isinstance.return_value = False + with pytest.raises(TypeError): + PyCanTransportInterface.receive_packet(self.mock_can_transport_interface, timeout) + mock_isinstance.assert_called_once_with(timeout, (int, float)) + + @pytest.mark.parametrize("timeout", [0, -654]) + def test_receive_packet__value_error(self, timeout): + with pytest.raises(ValueError): + PyCanTransportInterface.receive_packet(self.mock_can_transport_interface, timeout) + + @pytest.mark.parametrize("timeout", [0.001, 123.456]) + def test_receive_packet__timeout_error(self, timeout): + mock_get_message = Mock(return_value=None) + self.mock_buffered_reader.return_value.get_message = mock_get_message + with pytest.raises(TimeoutError): + PyCanTransportInterface.receive_packet(self.mock_can_transport_interface, timeout) + + @pytest.mark.parametrize("timeout", [None, 0.001, 123.456]) + def test_receive_packet(self, timeout): + assert PyCanTransportInterface.receive_packet(self.mock_can_transport_interface, timeout) \ + == self.mock_can_packet_record.return_value + self.mock_datetime.fromtimestamp.assert_called_once_with(self.mock_buffered_reader.return_value.get_message.return_value.timestamp) + self.mock_can_transport_interface.segmenter.is_input_packet.assert_called_once_with( + can_id=self.mock_buffered_reader.return_value.get_message.return_value.arbitration_id, + data=self.mock_buffered_reader.return_value.get_message.return_value.data) + self.mock_can_packet_record.assert_called_once_with( + frame=self.mock_buffered_reader.return_value.get_message.return_value, + direction=TransmissionDirection.RECEIVED, + addressing_type=self.mock_can_transport_interface.segmenter.is_input_packet.return_value, + addressing_format=self.mock_can_transport_interface.segmenter.addressing_format, + transmission_time=self.mock_datetime.fromtimestamp.return_value) + + # async_send_packet + + @pytest.mark.parametrize("packet", ["something", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + @pytest.mark.asyncio + async def test_async_send_packet__type_error(self, mock_isinstance, packet): + mock_isinstance.return_value = False + with pytest.raises(TypeError): + await PyCanTransportInterface.async_send_packet(self.mock_can_transport_interface, packet) + mock_isinstance.assert_called_once_with(packet, CanPacket) + + @pytest.mark.parametrize("packet", [ + Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME, raw_frame_data=(0x12, 0x34)), + Mock(spec=CanPacket, packet_type=CanPacketType.FLOW_CONTROL, raw_frame_data=tuple(range(8))), + Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME, raw_frame_data=tuple(range(64, 128))), + ]) + @pytest.mark.asyncio + async def test_async_send_packet(self, packet): + mock_get_message = Mock(return_value=MagicMock(arbitration_id=packet.can_id, + data=packet.raw_frame_data, + is_rx=True)) + self.mock_async_buffered_reader.return_value.get_message = mock_get_message + self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured = None + self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured = None + assert await PyCanTransportInterface.async_send_packet(self.mock_can_transport_interface, packet) \ + == self.mock_can_packet_record.return_value + self.mock_can_id_handler.is_extended_can_id.assert_called_once_with(packet.can_id) + self.mock_can_dlc_handler.is_can_fd_specific_dlc.assert_called_once_with(packet.dlc) + self.mock_message.assert_called_once_with(arbitration_id=packet.can_id, + is_extended_id=self.mock_can_id_handler.is_extended_can_id.return_value, + data=packet.raw_frame_data, + is_fd=self.mock_can_dlc_handler.is_can_fd_specific_dlc.return_value) + self.mock_can_transport_interface.bus_manager.send.assert_called_once_with(self.mock_message.return_value) + self.mock_datetime.fromtimestamp.assert_called_once_with(mock_get_message.return_value.timestamp) + self.mock_can_packet_record.assert_called_once_with(frame=mock_get_message.return_value, + direction=TransmissionDirection.TRANSMITTED, + addressing_type=packet.addressing_type, + addressing_format=packet.addressing_format, + 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) + 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_ar_measured is None + mock_get_message.return_value.timestamp.__sub__.assert_called_once_with(self.mock_time.return_value) + + # async_receive_packet + + @pytest.mark.parametrize("timeout", ["something", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + @pytest.mark.asyncio + async def test_async_receive_packet__type_error(self, mock_isinstance, timeout): + mock_isinstance.return_value = False + with pytest.raises(TypeError): + await PyCanTransportInterface.async_receive_packet(self.mock_can_transport_interface, timeout) + mock_isinstance.assert_called_once_with(timeout, (int, float)) + + @pytest.mark.parametrize("timeout", [0, -654]) + @pytest.mark.asyncio + async def test_async_receive_packet__value_error(self, timeout): + with pytest.raises(ValueError): + await PyCanTransportInterface.async_receive_packet(self.mock_can_transport_interface, timeout) + + @pytest.mark.parametrize("timeout", [None, 0.001, 123.456]) + @pytest.mark.asyncio + async def test_async_receive_packet(self, timeout): + assert await PyCanTransportInterface.async_receive_packet(self.mock_can_transport_interface, timeout) \ + == self.mock_can_packet_record.return_value + self.mock_datetime.fromtimestamp.assert_called_once_with(self.mock_async_buffered_reader.return_value.get_message.return_value.timestamp) + self.mock_can_transport_interface.segmenter.is_input_packet.assert_called_once_with( + can_id=self.mock_async_buffered_reader.return_value.get_message.return_value.arbitration_id, + data=self.mock_async_buffered_reader.return_value.get_message.return_value.data) + self.mock_can_packet_record.assert_called_once_with( + frame=self.mock_async_buffered_reader.return_value.get_message.return_value, + direction=TransmissionDirection.RECEIVED, + addressing_type=self.mock_can_transport_interface.segmenter.is_input_packet.return_value, + addressing_format=self.mock_can_transport_interface.segmenter.addressing_format, + transmission_time=self.mock_datetime.fromtimestamp.return_value) + class TestPyCanTransportInterfaceIntegration: """Integration tests for `PyCanTransportInterface` class.""" diff --git a/tests/system_tests/__init__.py b/tests/system_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/system_tests/transport_interface/__init__.py b/tests/system_tests/transport_interface/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/system_tests/transport_interface/can_transport_interface/__init__.py b/tests/system_tests/transport_interface/can_transport_interface/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/system_tests/transport_interface/can_transport_interface/python_can.py b/tests/system_tests/transport_interface/can_transport_interface/python_can.py new file mode 100644 index 00000000..3fff2300 --- /dev/null +++ b/tests/system_tests/transport_interface/can_transport_interface/python_can.py @@ -0,0 +1,192 @@ +import pytest +from abc import ABC, abstractmethod +from threading import Timer +from can import Bus, Message +from datetime import datetime, timedelta + +from uds.can import CanAddressingInformation, CanAddressingFormat, CanFlowStatus +from uds.transport_interface import PyCanTransportInterface +from uds.transmission_attributes import AddressingType, TransmissionDirection +from uds.packet import CanPacket, AnyCanPacket, CanPacketType, CanPacketRecord + + +class AbstractTestPyCan(ABC): + + @abstractmethod + def setup_class(self): + """Configure `self.bus1` and `self.bus2`. They are supposed to be connected through a termination.""" + self.bus1: Bus + self.bus2: Bus + + # send_packet + + @pytest.mark.parametrize("packet_type, addressing_type, packet_type_specific_kwargs", [ + (CanPacketType.SINGLE_FRAME, AddressingType.FUNCTIONAL, {"filler_byte": 0x1E, "payload": [0x10, 0x04]}), + (CanPacketType.FIRST_FRAME, AddressingType.PHYSICAL, {"dlc": 8, + "payload": [0x22, 0x10, 0x00, 0x10, 0x01, 0x10], + "data_length": 0x13}), + (CanPacketType.CONSECUTIVE_FRAME, AddressingType.PHYSICAL, {"payload": [0x32, 0xFF], "sequence_number": 0xF}), + (CanPacketType.FLOW_CONTROL, AddressingType.PHYSICAL, {"dlc": 8, + "flow_status": CanFlowStatus.ContinueToSend, + "block_size": 0x15, + "st_min": 0xFE}), + ]) + def test_send_packet__11bit_addressing(self, packet_type, addressing_type, packet_type_specific_kwargs): + addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}, + ) + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=addressing_information) + packet = CanPacket(packet_type=packet_type, + addressing_format=addressing_information.addressing_format, + addressing_type=addressing_type, + can_id=addressing_information.tx_packets_physical_ai["can_id"] + if addressing_type == AddressingType.PHYSICAL else + addressing_information.tx_packets_functional_ai["can_id"], + **packet_type_specific_kwargs) + datetime_before_send = datetime.now() + packet_record = can_transport_interface.send_packet(packet) + datetime_after_send = datetime.now() + assert isinstance(packet_record, CanPacketRecord) + assert datetime_before_send < packet_record.transmission_time < datetime_after_send + assert packet_record.direction == TransmissionDirection.TRANSMITTED + assert packet_record.raw_frame_data == packet.raw_frame_data + assert packet_record.can_id == packet.can_id + assert packet_record.packet_type == packet.packet_type + assert packet_record.addressing_format == packet.addressing_format == addressing_information.addressing_format + assert packet_record.addressing_type == packet.addressing_type == addressing_type + assert packet_record.target_address is packet.target_address is None + assert packet_record.source_address is packet.source_address is None + assert packet_record.address_extension is packet.address_extension is None + + # TODO: more addressings + + # receive_packet + + @pytest.mark.parametrize("addressing_information, frame", [ + (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + Message(data=[0x02, 0x10, 0x03])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + Message(data=[0x2C] + list(range(100, 163)), is_fd=True)), + (CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF},), + Message(data=[0xFE, 0x30, 0xAB, 0x7F])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}, ), + Message(data=[0xFE, 0x11, 0x23, 0x62, 0x92, 0xD0, 0xB1, 0x00])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}, ), + Message(data=[0x87, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), + ]) + def test_receive_packet__physical_receive(self, addressing_information, frame): + frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] + # data parameter of `frame` object must be set manually and according to `addressing_format` + # and `addressing_information` + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=addressing_information) + Timer(interval=0.1, function=self.bus2.send, args=(frame, )).run() + datetime_before_receive = datetime.now() + packet_record = can_transport_interface.receive_packet(timeout=1000) + datetime_after_receive = datetime.now() + assert isinstance(packet_record, CanPacketRecord) + assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + assert packet_record.direction == TransmissionDirection.RECEIVED + assert packet_record.raw_frame_data == tuple(frame.data) + assert packet_record.can_id == frame.arbitration_id == addressing_information.rx_packets_physical_ai["can_id"] + assert packet_record.addressing_format == addressing_information.addressing_format + assert packet_record.addressing_type == AddressingType.PHYSICAL + assert packet_record.target_address == addressing_information.rx_packets_physical_ai["target_address"] + assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] + assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] + + @pytest.mark.parametrize("addressing_information, frame", [ + (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + Message(data=[0x02, 0x10, 0x03])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + Message(data=[0x2C] + list(range(100, 163)), is_fd=True)), + (CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF},), + Message(data=[0xFF, 0x30, 0xAB, 0x7F])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}, ), + Message(data=[0xFF, 0x11, 0x23, 0x62, 0x92, 0xD0, 0xB1, 0x00])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}, ), + Message(data=[0xFF, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), + ]) + def test_receive_packet__functional_receive(self, addressing_information, frame): + frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] + # data parameter of `frame` object must be set manually and according to `addressing_format` + # and `addressing_information` + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=addressing_information) + Timer(interval=0.1, function=self.bus2.send, args=(frame, )).run() + datetime_before_receive = datetime.now() + packet_record = can_transport_interface.receive_packet(timeout=1000) + datetime_after_receive = datetime.now() + assert isinstance(packet_record, CanPacketRecord) + assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + assert packet_record.direction == TransmissionDirection.RECEIVED + assert packet_record.raw_frame_data == tuple(frame.data) + assert packet_record.can_id == frame.arbitration_id == addressing_information.rx_packets_functional_ai["can_id"] + assert packet_record.addressing_format == addressing_information.addressing_format + assert packet_record.addressing_type == AddressingType.FUNCTIONAL + assert packet_record.target_address == addressing_information.rx_packets_functional_ai["target_address"] + assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] + assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] + + # TODO: timeout test + + # async_receive_packet + + # TODO: test + + # async_send_packet + + # TODO: test + + +class TestPythonCanKvaser(AbstractTestPyCan): + """System Tests for `PyCanTransportInterface` with Kvaser as bus manager.""" + + def setup_class(self): + self.bus1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) + self.bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) diff --git a/uds/packet/can_packet.py b/uds/packet/can_packet.py index 909f417f..cb056d45 100644 --- a/uds/packet/can_packet.py +++ b/uds/packet/can_packet.py @@ -2,7 +2,7 @@ __all__ = ["CanPacket", "AnyCanPacket"] -from typing import Optional, Any, TypedDict +from typing import Optional, Any from warnings import warn from uds.utilities import AmbiguityError, UnusedArgumentWarning, RawBytes, RawBytesTuple, validate_raw_bytes diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index d4bce60c..65293e0a 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -2,7 +2,7 @@ __all__ = ["AbstractCanTransportInterface", "PyCanTransportInterface"] -from typing import Any, Optional, Union +from typing import Any, Optional from abc import abstractmethod from warnings import warn from asyncio import AbstractEventLoop, wait_for, get_running_loop @@ -13,7 +13,7 @@ from uds.utilities import TimeMilliseconds, ValueWarning from uds.can import AbstractCanAddressingInformation, CanIdHandler, CanDlcHandler -from uds.packet import CanPacket, AnyCanPacket, CanPacketRecord, CanPacketType +from uds.packet import CanPacket, CanPacketRecord, CanPacketType from uds.transmission_attributes import TransmissionDirection from uds.segmentation import CanSegmenter from .abstract_transport_interface import AbstractTransportInterface @@ -356,13 +356,16 @@ class PyCanTransportInterface(AbstractCanTransportInterface): """ def __init__(self, - can_bus_manager: Any, + can_bus_manager: BusABC, addressing_information: AbstractCanAddressingInformation, **kwargs: Any) -> None: """ Create python-can Transport Interface. - :param can_bus_manager: An object that handles CAN bus (Physical and Data layers of OSI Model). + :param can_bus_manager: Python-can bus object for handling CAN. + + .. warning:: Bus must have capability of receiving transmitted frame (``receive_own_messages=True`` set). + :param addressing_information: Addressing Information of CAN Transport Interface. :param kwargs: Optional arguments that are specific for CAN bus. @@ -429,9 +432,9 @@ def is_supported_bus_manager(bus_manager: Any) -> bool: :return: True if provided bus object is compatible with this Transport Interface, False otherwise. """ - return isinstance(bus_manager, BusABC) # TODO: check that receive_own_messages is set (if possible) + return isinstance(bus_manager, BusABC) - def send_packet(self, packet: Union[CanPacket, AnyCanPacket]) -> CanPacketRecord: + def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore """ Transmit CAN packet. @@ -442,7 +445,7 @@ def send_packet(self, packet: Union[CanPacket, AnyCanPacket]) -> CanPacketRecord :return: Record with historic information about transmitted CAN packet. """ time_start = time() - if not isinstance(packet, (CanPacket, AnyCanPacket)): + 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 timeout = self.n_ar_timeout if is_flow_control_packet else self.n_as_timeout @@ -451,29 +454,28 @@ def send_packet(self, packet: Union[CanPacket, AnyCanPacket]) -> CanPacketRecord data=packet.raw_frame_data, is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) message_listener = BufferedReader() - notifier = Notifier(bus=self.bus_manager, - listeners=[message_listener]) + notifier = Notifier(bus=self.bus_manager, listeners=[message_listener]) + notifier.add_listener(message_listener) self.bus_manager.send(can_message) observed_frame = None while observed_frame is None \ or observed_frame.arbitration_id != packet.can_id \ or tuple(observed_frame.data) != packet.raw_frame_data \ or not observed_frame.is_rx: - time_now = time() - timeout_left = timeout - (time_now - time_start) * 1000. + timeout_left = timeout / 1000. - (time() - time_start) observed_frame = message_listener.get_message(timeout=timeout_left) notifier.stop() if is_flow_control_packet: - self.__n_ar_measured = (observed_frame.timestamp - time_start) * 1000. + self.__n_ar_measured = observed_frame.timestamp - time_start else: - self.__n_as_measured = (observed_frame.timestamp - time_start) * 1000. + self.__n_as_measured = observed_frame.timestamp - 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[TimeMilliseconds] = None) -> CanPacketRecord: # TODO: make it possible to receive old packets (received in a past) + def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacketRecord: """ Receive CAN packet. @@ -494,13 +496,14 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacke if timeout <= 0: raise ValueError("Provided timeout value is less or equal 0.") message_listener = BufferedReader() - notifier = Notifier(bus=self.bus_manager, - listeners=[message_listener]) + notifier = Notifier(bus=self.bus_manager, listeners=[message_listener]) packet_addressing_type = None while packet_addressing_type is None: time_now = time() - timeout_left = None if timeout is None else timeout - (time_now - time_start)*1000 + timeout_left = float("inf") if timeout is None else timeout / 1000. - (time_now - time_start) received_frame = message_listener.get_message(timeout=timeout_left) + if received_frame is None: + raise TimeoutError("Timeout was reached before receiving a CAN Packet.") packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) notifier.stop() @@ -511,8 +514,8 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacke transmission_time=datetime.fromtimestamp(received_frame.timestamp)) async def async_send_packet(self, - packet: Union[CanPacket, AnyCanPacket], - loop: Optional[AbstractEventLoop] = None) -> CanPacketRecord: # type: ignore + packet: CanPacket, # type: ignore + loop: Optional[AbstractEventLoop] = None) -> CanPacketRecord: """ Transmit CAN packet. @@ -524,7 +527,7 @@ async def async_send_packet(self, :return: Record with historic information about transmitted CAN packet. """ time_start = time() - if not isinstance(packet, (CanPacket, AnyCanPacket)): + 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 is_flow_control_packet = packet.packet_type == CanPacketType.FLOW_CONTROL @@ -543,14 +546,13 @@ async def async_send_packet(self, or observed_frame.arbitration_id != packet.can_id \ or tuple(observed_frame.data) != packet.raw_frame_data \ or not observed_frame.is_rx: - time_now = time() - timeout_left = timeout - (time_now - time_start) * 1000. + timeout_left = timeout / 1000. - (time() - time_start) observed_frame = await wait_for(async_message_listener.get_message(), timeout=timeout_left) async_notifier.stop() if is_flow_control_packet: - self.__n_ar_measured = (observed_frame.timestamp - time_start) * 1000. + self.__n_ar_measured = observed_frame.timestamp - time_start else: - self.__n_as_measured = (observed_frame.timestamp - time_start) * 1000. + self.__n_as_measured = observed_frame.timestamp - time_start return CanPacketRecord(frame=observed_frame, direction=TransmissionDirection.TRANSMITTED, addressing_type=packet.addressing_type, @@ -572,24 +574,22 @@ async def async_receive_packet(self, :return: Record with historic information about received CAN packet. """ - loop = get_running_loop() if loop is None else loop 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.") + loop = get_running_loop() if loop is None else loop async_message_listener = AsyncBufferedReader() async_notifier = Notifier(bus=self.bus_manager, listeners=[async_message_listener], loop=loop) packet_addressing_type = None while packet_addressing_type is None: - time_now = time() - _timeout_left = None if timeout is None else timeout - (time_now - time_start)*1000 + _timeout_left = None if timeout is None else timeout / 1000. - (time() - time_start) received_frame = await wait_for(async_message_listener.get_message(), - timeout=_timeout_left, - loop=loop) + timeout=_timeout_left) packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) async_notifier.stop() From 323698b8cc70fee7d490cc110ddafb96d5278a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20D=C4=85browski?= Date: Thu, 5 Oct 2023 14:24:29 +0200 Subject: [PATCH 20/40] system tests update - add timeout tests (sort of performance test on receive_packet method) - update CI file so only software tests are automatically executed (no system tests in cloud testing) --- .github/workflows/ci.yml | 12 +-- .../{python_can.py => test_python_can.py} | 78 +++++++++++++++++-- 2 files changed, 77 insertions(+), 13 deletions(-) rename tests/system_tests/transport_interface/can_transport_interface/{python_can.py => test_python_can.py} (73%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85faa9ec..6e0f9f62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,16 +32,16 @@ jobs: - name: Execute unit tests with coverage report [pytest] run: | - pytest --cov-report=term-missing --cov=uds -m "not integration and not performance" + pytest tests/software_tests --cov-report=term-missing --cov=uds -m "not integration and not performance" - name: Execute integration tests with coverage report [pytest] run: | - pytest --cov-report=term-missing --cov=uds -m "integration" + pytest tests/software_tests --cov-report=term-missing --cov=uds -m "integration" # TODO: uncomment when performance tests are added # - name: Execute performance tests with coverage report [pytest] # run: | -# pytest --cov-report=term-missing --cov=uds -m "performance" +# pytest tests/software_tests --cov-report=term-missing --cov=uds -m "performance" code_coverage: @@ -72,7 +72,7 @@ jobs: - name: Execute unit tests [pytest] run: | - pytest --cov-report=xml --cov=uds -m "not integration and not performance" + pytest tests/software_tests --cov-report=xml --cov=uds -m "not integration and not performance" - name: Upload unit tests report [CodeCov] uses: codecov/codecov-action@v2 @@ -83,7 +83,7 @@ jobs: - name: Execute integration tests [pytest] run: | - pytest --cov-report=xml --cov=uds -m "integration" + pytest tests/software_tests --cov-report=xml --cov=uds -m "integration" - name: Upload integration tests report [CodeCov] uses: codecov/codecov-action@v2 @@ -95,7 +95,7 @@ jobs: # TODO: uncomment when performance tests are added # - name: Execute performance tests [pytest] # run: | -# pytest --cov-report=xml --cov=uds -m "performance" +# pytest tests/software_tests --cov-report=xml --cov=uds -m "performance" # # - name: Upload performance tests report [CodeCov] # uses: codecov/codecov-action@v2 diff --git a/tests/system_tests/transport_interface/can_transport_interface/python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py similarity index 73% rename from tests/system_tests/transport_interface/can_transport_interface/python_can.py rename to tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index 3fff2300..bddcb01c 100644 --- a/tests/system_tests/transport_interface/can_transport_interface/python_can.py +++ b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py @@ -99,15 +99,19 @@ def test_send_packet__11bit_addressing(self, packet_type, addressing_type, packe rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}, ), Message(data=[0x87, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), ]) - def test_receive_packet__physical_receive(self, addressing_information, frame): + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 950), # ms + (30, 1), + ]) + def test_receive_packet__physical_receive(self, addressing_information, frame, timeout, send_after): frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, addressing_information=addressing_information) - Timer(interval=0.1, function=self.bus2.send, args=(frame, )).run() + Timer(interval=send_after/1000., function=self.bus2.send, args=(frame, )).start() datetime_before_receive = datetime.now() - packet_record = can_transport_interface.receive_packet(timeout=1000) + packet_record = can_transport_interface.receive_packet(timeout=timeout) datetime_after_receive = datetime.now() assert isinstance(packet_record, CanPacketRecord) assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive @@ -152,15 +156,19 @@ def test_receive_packet__physical_receive(self, addressing_information, frame): rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}, ), Message(data=[0xFF, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), ]) - def test_receive_packet__functional_receive(self, addressing_information, frame): + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 950), # ms + (30, 1), + ]) + def test_receive_packet__functional_receive(self, addressing_information, frame, timeout, send_after): frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, addressing_information=addressing_information) - Timer(interval=0.1, function=self.bus2.send, args=(frame, )).run() + Timer(interval=send_after/1000., function=self.bus2.send, args=(frame, )).start() datetime_before_receive = datetime.now() - packet_record = can_transport_interface.receive_packet(timeout=1000) + packet_record = can_transport_interface.receive_packet(timeout=timeout) datetime_after_receive = datetime.now() assert isinstance(packet_record, CanPacketRecord) assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive @@ -173,7 +181,63 @@ def test_receive_packet__functional_receive(self, addressing_information, frame) assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] - # TODO: timeout test + @pytest.mark.parametrize("addressing_type, addressing_information, frame", [ + (AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + Message(data=[0x02, 0x10, 0x03])), + (AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + Message(data=[0x2C] + list(range(100, 163)), is_fd=True)), + (AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF}, ), + Message(data=[0xFF, 0x30, 0xAB, 0x7F])), + (AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}, ), + Message(data=[0xFF, 0x11, 0x23, 0x62, 0x92, 0xD0, 0xB1, 0x00])), + (AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, + "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, + "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, + "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, + "address_extension": 0xFF}, ), + Message(data=[0xFF, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), + ]) + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 1001), # ms + (10, 15), + ]) + def test_receive_packet__timeout(self, addressing_information, addressing_type, frame, timeout, send_after): + if addressing_type == AddressingType.PHYSICAL: + frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] + else: + frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] + # data parameter of `frame` object must be set manually and according to `addressing_format` + # and `addressing_information` + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=addressing_information) + Timer(interval=send_after/1000., function=self.bus2.send, args=(frame,)).start() + with pytest.raises(TimeoutError): + can_transport_interface.receive_packet(timeout=timeout) # async_receive_packet From 97df93113bb4e35aabceb65b9cd3e60689e927ef Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Fri, 6 Oct 2023 16:46:32 +0200 Subject: [PATCH 21/40] system tests for transport interface add system tests for transport interface + figure out notifiers problem --- .../test_python_can.py | 179 ++++++++++++++---- .../transport_interface/conftest.py | 12 ++ .../can_transport_interface.py | 25 ++- 3 files changed, 170 insertions(+), 46 deletions(-) create mode 100644 tests/system_tests/transport_interface/conftest.py 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 bddcb01c..8ebd81f8 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 @@ -1,13 +1,15 @@ import pytest +import asyncio from abc import ABC, abstractmethod from threading import Timer +from time import time +from datetime import datetime from can import Bus, Message -from datetime import datetime, timedelta from uds.can import CanAddressingInformation, CanAddressingFormat, CanFlowStatus from uds.transport_interface import PyCanTransportInterface from uds.transmission_attributes import AddressingType, TransmissionDirection -from uds.packet import CanPacket, AnyCanPacket, CanPacketType, CanPacketRecord +from uds.packet import CanPacket, CanPacketType, CanPacketRecord class AbstractTestPyCan(ABC): @@ -20,33 +22,69 @@ def setup_class(self): # send_packet - @pytest.mark.parametrize("packet_type, addressing_type, packet_type_specific_kwargs", [ - (CanPacketType.SINGLE_FRAME, AddressingType.FUNCTIONAL, {"filler_byte": 0x1E, "payload": [0x10, 0x04]}), - (CanPacketType.FIRST_FRAME, AddressingType.PHYSICAL, {"dlc": 8, - "payload": [0x22, 0x10, 0x00, 0x10, 0x01, 0x10], - "data_length": 0x13}), - (CanPacketType.CONSECUTIVE_FRAME, AddressingType.PHYSICAL, {"payload": [0x32, 0xFF], "sequence_number": 0xF}), - (CanPacketType.FLOW_CONTROL, AddressingType.PHYSICAL, {"dlc": 8, - "flow_status": CanFlowStatus.ContinueToSend, - "block_size": 0x15, - "st_min": 0xFE}), + @pytest.mark.skip + @pytest.mark.parametrize("packet_type, addressing_type, addressing_information, packet_type_specific_kwargs", [ + (CanPacketType.SINGLE_FRAME, + AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + {"filler_byte": 0x1E, "payload": [0x10, 0x04]}), + (CanPacketType.FIRST_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + {"dlc": 8, "payload": [0x22, 0x10, 0x00, 0x10, 0x01, 0x10], "data_length": 0x13}), + (CanPacketType.CONSECUTIVE_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF}), + {"payload": [0x32, 0xFF], "sequence_number": 0xF}), + (CanPacketType.FLOW_CONTROL, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}), + {"dlc": 8, "flow_status": CanFlowStatus.ContinueToSend, "block_size": 0x15, "st_min": 0xFE}), + (CanPacketType.SINGLE_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}), + {"filler_byte": 0xBC, "payload": [0x22, 0x12, 0x34, 0x12, 0x56, 0x12, 0x78, 0x12, 0x9A, 0x12, 0xBC], "dlc":0xF}), ]) - def test_send_packet__11bit_addressing(self, packet_type, addressing_type, packet_type_specific_kwargs): - addressing_information = CanAddressingInformation( - addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - tx_physical={"can_id": 0x611}, - rx_physical={"can_id": 0x612}, - tx_functional={"can_id": 0x6FF}, - rx_functional={"can_id": 0x6FE}, - ) + def test_send_packet(self, packet_type, addressing_type, addressing_information, packet_type_specific_kwargs): + if addressing_type == AddressingType.PHYSICAL: + can_id = addressing_information.tx_packets_physical_ai["can_id"] + target_address = addressing_information.tx_packets_physical_ai["target_address"] + source_address = addressing_information.tx_packets_physical_ai["source_address"] + address_extension = addressing_information.tx_packets_physical_ai["address_extension"] + else: + can_id = addressing_information.tx_packets_functional_ai["can_id"] + target_address = addressing_information.tx_packets_functional_ai["target_address"] + source_address = addressing_information.tx_packets_functional_ai["source_address"] + address_extension = addressing_information.tx_packets_functional_ai["address_extension"] can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, addressing_information=addressing_information) packet = CanPacket(packet_type=packet_type, addressing_format=addressing_information.addressing_format, addressing_type=addressing_type, - can_id=addressing_information.tx_packets_physical_ai["can_id"] - if addressing_type == AddressingType.PHYSICAL else - addressing_information.tx_packets_functional_ai["can_id"], + can_id=can_id, + target_address=target_address, + source_address=source_address, + address_extension=address_extension, **packet_type_specific_kwargs) datetime_before_send = datetime.now() packet_record = can_transport_interface.send_packet(packet) @@ -55,18 +93,17 @@ def test_send_packet__11bit_addressing(self, packet_type, addressing_type, packe assert datetime_before_send < packet_record.transmission_time < datetime_after_send assert packet_record.direction == TransmissionDirection.TRANSMITTED assert packet_record.raw_frame_data == packet.raw_frame_data - assert packet_record.can_id == packet.can_id - assert packet_record.packet_type == packet.packet_type assert packet_record.addressing_format == packet.addressing_format == addressing_information.addressing_format + assert packet_record.packet_type == packet.packet_type == packet_type + assert packet_record.can_id == packet.can_id == can_id assert packet_record.addressing_type == packet.addressing_type == addressing_type - assert packet_record.target_address is packet.target_address is None - assert packet_record.source_address is packet.source_address is None - assert packet_record.address_extension is packet.address_extension is None - - # TODO: more addressings + 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 # receive_packet + @pytest.mark.skip @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, @@ -84,24 +121,24 @@ def test_send_packet__11bit_addressing(self, packet_type, addressing_type, packe tx_physical={"can_id": 0x987, "target_address": 0x90}, rx_physical={"can_id": 0x987, "target_address": 0xFE}, tx_functional={"can_id": 0x11765, "target_address": 0x5A}, - rx_functional={"can_id": 0x11765, "target_address": 0xFF},), + rx_functional={"can_id": 0x11765, "target_address": 0xFF}), Message(data=[0xFE, 0x30, 0xAB, 0x7F])), (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, tx_physical={"can_id": 0x651, "address_extension": 0x87}, rx_physical={"can_id": 0x652, "address_extension": 0xFE}, tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, - rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}, ), + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}), Message(data=[0xFE, 0x11, 0x23, 0x62, 0x92, 0xD0, 0xB1, 0x00])), (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, - rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}, ), + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}), Message(data=[0x87, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), ]) @pytest.mark.parametrize("timeout, send_after", [ (1000, 950), # ms - (30, 1), + (50, 1), ]) def test_receive_packet__physical_receive(self, addressing_information, frame, timeout, send_after): frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] @@ -115,15 +152,18 @@ def test_receive_packet__physical_receive(self, addressing_information, frame, t datetime_after_receive = datetime.now() assert isinstance(packet_record, CanPacketRecord) assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + # TODO: performance + # assert (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout assert packet_record.direction == TransmissionDirection.RECEIVED assert packet_record.raw_frame_data == tuple(frame.data) - assert packet_record.can_id == frame.arbitration_id == addressing_information.rx_packets_physical_ai["can_id"] assert packet_record.addressing_format == addressing_information.addressing_format assert packet_record.addressing_type == AddressingType.PHYSICAL + assert packet_record.can_id == frame.arbitration_id == addressing_information.rx_packets_physical_ai["can_id"] assert packet_record.target_address == addressing_information.rx_packets_physical_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] + @pytest.mark.skip @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, @@ -158,7 +198,7 @@ def test_receive_packet__physical_receive(self, addressing_information, frame, t ]) @pytest.mark.parametrize("timeout, send_after", [ (1000, 950), # ms - (30, 1), + (50, 1), ]) def test_receive_packet__functional_receive(self, addressing_information, frame, timeout, send_after): frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] @@ -172,15 +212,18 @@ def test_receive_packet__functional_receive(self, addressing_information, frame, datetime_after_receive = datetime.now() assert isinstance(packet_record, CanPacketRecord) assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + # TODO: performance + # assert (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout assert packet_record.direction == TransmissionDirection.RECEIVED assert packet_record.raw_frame_data == tuple(frame.data) - assert packet_record.can_id == frame.arbitration_id == addressing_information.rx_packets_functional_ai["can_id"] assert packet_record.addressing_format == addressing_information.addressing_format assert packet_record.addressing_type == AddressingType.FUNCTIONAL + assert packet_record.can_id == frame.arbitration_id == addressing_information.rx_packets_functional_ai["can_id"] assert packet_record.target_address == addressing_information.rx_packets_functional_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] + @pytest.mark.skip @pytest.mark.parametrize("addressing_type, addressing_information, frame", [ (AddressingType.PHYSICAL, CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -224,7 +267,7 @@ def test_receive_packet__functional_receive(self, addressing_information, frame, ]) @pytest.mark.parametrize("timeout, send_after", [ (1000, 1001), # ms - (10, 15), + (50, 55), ]) def test_receive_packet__timeout(self, addressing_information, addressing_type, frame, timeout, send_after): if addressing_type == AddressingType.PHYSICAL: @@ -241,7 +284,65 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, # async_receive_packet - # TODO: test + @pytest.mark.skip + @pytest.mark.parametrize("timeout", [1000, 50]) + @pytest.mark.asyncio + async def test_async_receive_packet__timeout(self, example_addressing_information, timeout): + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + # TODO: time_before_receive = time() + with pytest.raises((TimeoutError, asyncio.TimeoutError)): + await can_transport_interface.async_receive_packet(timeout=timeout) + # TODO: time_after_receive = time() + # TODO: performance + # assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + 30 + + @pytest.mark.parametrize("addressing_information, frame", [ + (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + Message(data=[0x02, 0x10, 0x03])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + Message(data=[0x2C] + list(range(100, 163)), is_fd=True)), + (CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF}), + Message(data=[0xFE, 0x30, 0xAB, 0x7F])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}), + Message(data=[0xFE, 0x11, 0x23, 0x62, 0x92, 0xD0, 0xB1, 0x00])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}), + Message(data=[0x87, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), + ]) + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 950), # ms + (50, 1), + ]) + @pytest.mark.asyncio + async def test_async_receive_packet(self, addressing_information, frame, timeout, send_after): + async def _send_frame(): + await asyncio.sleep(send_after/1000.) + self.bus2.send(frame) + + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=addressing_information) + done, _ = await asyncio.wait([_send_frame(), can_transport_interface.async_receive_packet(timeout=timeout)]) + # TODO: get packet from done # async_send_packet diff --git a/tests/system_tests/transport_interface/conftest.py b/tests/system_tests/transport_interface/conftest.py new file mode 100644 index 00000000..3715631a --- /dev/null +++ b/tests/system_tests/transport_interface/conftest.py @@ -0,0 +1,12 @@ +from pytest import fixture + +from uds.can import CanAddressingInformation, CanAddressingFormat + + +@fixture +def example_addressing_information(): + return CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}) diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 65293e0a..758db8ec 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -463,8 +463,10 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore or tuple(observed_frame.data) != packet.raw_frame_data \ or not observed_frame.is_rx: timeout_left = timeout / 1000. - (time() - time_start) + if timeout_left <= 0: + raise TimeoutError("Timeout was reached before observing a CAN Packet being transmitted.") observed_frame = message_listener.get_message(timeout=timeout_left) - notifier.stop() + notifier.stop(timeout=0) # TODO: do it smarter if is_flow_control_packet: self.__n_ar_measured = observed_frame.timestamp - time_start else: @@ -501,12 +503,16 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacke while packet_addressing_type is None: time_now = time() timeout_left = float("inf") 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 = message_listener.get_message(timeout=timeout_left) if received_frame is None: - raise TimeoutError("Timeout was reached before receiving a CAN Packet.") + raise TimeoutError("Timeout was reached before a CAN Packet was received.") + if timeout is not None and (received_frame.timestamp - time_start) * 1000. > timeout: + raise TimeoutError("CAN Packet was received after timeout.") packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) - notifier.stop() + notifier.stop(timeout=0) # TODO: do it smarter return CanPacketRecord(frame=received_frame, direction=TransmissionDirection.RECEIVED, addressing_type=packet_addressing_type, @@ -548,7 +554,7 @@ async def async_send_packet(self, or not observed_frame.is_rx: timeout_left = timeout / 1000. - (time() - time_start) observed_frame = await wait_for(async_message_listener.get_message(), timeout=timeout_left) - async_notifier.stop() + async_notifier.stop(timeout=0) # TODO: do it smarter if is_flow_control_packet: self.__n_ar_measured = observed_frame.timestamp - time_start else: @@ -587,12 +593,17 @@ async def async_receive_packet(self, loop=loop) packet_addressing_type = None while packet_addressing_type is None: - _timeout_left = None if timeout is None else timeout / 1000. - (time() - time_start) + if timeout is None: + timeout_left = None + else: + timeout_left = timeout / 1000. - (time() - time_start) + if timeout_left <= 0: + raise TimeoutError("Timeout was reached before a CAN Packet was received.") received_frame = await wait_for(async_message_listener.get_message(), - timeout=_timeout_left) + timeout=timeout_left) packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) - async_notifier.stop() + async_notifier.stop(timeout=0) # TODO: do it smarter return CanPacketRecord(frame=received_frame, direction=TransmissionDirection.RECEIVED, addressing_type=packet_addressing_type, From 13e4972d5418c2834cd52198af5767771de47a6a Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 11 Oct 2023 17:01:10 +0200 Subject: [PATCH 22/40] update system tests --- .../python_can_timing_issue.py | 23 +++ .../kvaser with python-can/receive_packets.py | 12 +- .../receive_packets_asyncio.py | 15 +- .../kvaser with python-can/send_packets.py | 6 +- .../send_packets_asyncio.py | 2 + .../test_python_can.py | 157 ++++++++++++++---- .../can_transport_interface.py | 27 +-- 7 files changed, 187 insertions(+), 55 deletions(-) create mode 100644 examples/can/kvaser with python-can/python_can_timing_issue.py diff --git a/examples/can/kvaser with python-can/python_can_timing_issue.py b/examples/can/kvaser with python-can/python_can_timing_issue.py new file mode 100644 index 00000000..6499e85a --- /dev/null +++ b/examples/can/kvaser with python-can/python_can_timing_issue.py @@ -0,0 +1,23 @@ +from time import time +from threading import Timer +from can import Notifier, BufferedReader, Bus, Message + +if __name__ == "__main__": + kvaser_bus_1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) + kvaser_bus_2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) # connected with bus 1 + + buffered_reader = BufferedReader() + notifier = Notifier(bus=kvaser_bus_2, listeners=[buffered_reader]) + + message = Message(data=[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], arbitration_id=0x100) + + for _ in range(10): + Timer(interval=0.1, function=kvaser_bus_1.send, args=(message, )).run() + + sent_message = buffered_reader.get_message(timeout=1) + timestamp_after_send = time() + + print([sent_message.timestamp, timestamp_after_send, sent_message.timestamp <= timestamp_after_send]) + + kvaser_bus_1.shutdown() + kvaser_bus_2.shutdown() diff --git a/examples/can/kvaser with python-can/receive_packets.py b/examples/can/kvaser with python-can/receive_packets.py index d381a0b2..fdbe658d 100644 --- a/examples/can/kvaser with python-can/receive_packets.py +++ b/examples/can/kvaser with python-can/receive_packets.py @@ -6,6 +6,8 @@ from uds.can import CanAddressingInformation, CanAddressingFormat kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +kvaser_bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + example_addressing_information = CanAddressingInformation( addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, @@ -22,13 +24,13 @@ def main(): - Timer(interval=1, function=kvaser_bus.send, args=(frame_1, )).run() - record_1 = can_ti.receive_packet() + Timer(interval=0.1, function=kvaser_bus.send, args=(frame_1, )).run() + record_1 = can_ti.receive_packet(timeout=1000) pprint(record_1.__dict__) - Timer(interval=1, function=kvaser_bus.send, args=(frame_2, )).run() - Timer(interval=2, function=kvaser_bus.send, args=(frame_3, )).run() - record_2 = can_ti.receive_packet() + Timer(interval=0.3, function=kvaser_bus.send, args=(frame_2, )).run() + Timer(interval=0.8, function=kvaser_bus.send, args=(frame_3, )).run() + record_2 = can_ti.receive_packet(timeout=1000) pprint(record_2.__dict__) diff --git a/examples/can/kvaser with python-can/receive_packets_asyncio.py b/examples/can/kvaser with python-can/receive_packets_asyncio.py index 45d3b87f..c9bfaccf 100644 --- a/examples/can/kvaser with python-can/receive_packets_asyncio.py +++ b/examples/can/kvaser with python-can/receive_packets_asyncio.py @@ -6,6 +6,8 @@ from uds.can import CanAddressingInformation, CanAddressingFormat kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +kvaser_bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + example_addressing_information = CanAddressingInformation( addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, @@ -22,17 +24,20 @@ async def main(): - r1 = can_ti.async_receive_packet() - kvaser_bus.send(frame_1) + r1 = can_ti.async_receive_packet(timeout=1000) + kvaser_bus2.send(frame_1) record_1 = await r1 pprint(record_1.__dict__) - r2 = can_ti.async_receive_packet() - kvaser_bus.send(frame_2) - kvaser_bus.send(frame_3) + r2 = can_ti.async_receive_packet(timeout=1000) + kvaser_bus2.send(frame_2) + kvaser_bus2.send(frame_3) record_2 = await r2 pprint(record_2.__dict__) + kvaser_bus.shutdown() + kvaser_bus2.shutdown() + if __name__ == "__main__": asyncio.run(main()) diff --git a/examples/can/kvaser with python-can/send_packets.py b/examples/can/kvaser with python-can/send_packets.py index def294c4..af76b651 100644 --- a/examples/can/kvaser with python-can/send_packets.py +++ b/examples/can/kvaser with python-can/send_packets.py @@ -7,6 +7,8 @@ from uds.transmission_attributes import AddressingType kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +kvaser_bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + example_addressing_information = CanAddressingInformation( addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, @@ -26,9 +28,9 @@ def main(): record_1 = can_ti.send_packet(packet_1) - record_2 = can_ti.send_packet(packet_2) - pprint(record_1.__dict__) + + record_2 = can_ti.send_packet(packet_2) pprint(record_2.__dict__) diff --git a/examples/can/kvaser with python-can/send_packets_asyncio.py b/examples/can/kvaser with python-can/send_packets_asyncio.py index 254ed51b..5757cb6e 100644 --- a/examples/can/kvaser with python-can/send_packets_asyncio.py +++ b/examples/can/kvaser with python-can/send_packets_asyncio.py @@ -8,6 +8,8 @@ from uds.transmission_attributes import AddressingType kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) +kvaser_bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + example_addressing_information = CanAddressingInformation( addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, 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 8ebd81f8..9c010b06 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 @@ -1,6 +1,5 @@ import pytest import asyncio -from abc import ABC, abstractmethod from threading import Timer from time import time from datetime import datetime @@ -12,17 +11,20 @@ from uds.packet import CanPacket, CanPacketType, CanPacketRecord -class AbstractTestPyCan(ABC): +class TestPythonCanKvaser: + """System Tests for `PyCanTransportInterface` with Kvaser as bus manager.""" - @abstractmethod def setup_class(self): - """Configure `self.bus1` and `self.bus2`. They are supposed to be connected through a termination.""" - self.bus1: Bus - self.bus2: Bus + self.bus1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) + self.bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + + def teardown_class(self): + """Safely close bus objects.""" + self.bus1.shutdown() + self.bus2.shutdown() # send_packet - @pytest.mark.skip @pytest.mark.parametrize("packet_type, addressing_type, addressing_information, packet_type_specific_kwargs", [ (CanPacketType.SINGLE_FRAME, AddressingType.FUNCTIONAL, @@ -90,7 +92,6 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, packet_record = can_transport_interface.send_packet(packet) datetime_after_send = datetime.now() assert isinstance(packet_record, CanPacketRecord) - assert datetime_before_send < packet_record.transmission_time < datetime_after_send assert packet_record.direction == TransmissionDirection.TRANSMITTED assert packet_record.raw_frame_data == packet.raw_frame_data assert packet_record.addressing_format == packet.addressing_format == addressing_information.addressing_format @@ -100,10 +101,12 @@ 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 + # performance checks + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 is resolved + assert datetime_before_send < packet_record.transmission_time < datetime_after_send # receive_packet - @pytest.mark.skip @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, @@ -151,9 +154,6 @@ def test_receive_packet__physical_receive(self, addressing_information, frame, t packet_record = can_transport_interface.receive_packet(timeout=timeout) datetime_after_receive = datetime.now() assert isinstance(packet_record, CanPacketRecord) - assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive - # TODO: performance - # assert (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout assert packet_record.direction == TransmissionDirection.RECEIVED assert packet_record.raw_frame_data == tuple(frame.data) assert packet_record.addressing_format == addressing_information.addressing_format @@ -162,8 +162,11 @@ def test_receive_packet__physical_receive(self, addressing_information, frame, t assert packet_record.target_address == addressing_information.rx_packets_physical_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] + # performance checks + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 is resolved + assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + assert (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout # performance check - @pytest.mark.skip @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, @@ -211,9 +214,6 @@ def test_receive_packet__functional_receive(self, addressing_information, frame, packet_record = can_transport_interface.receive_packet(timeout=timeout) datetime_after_receive = datetime.now() assert isinstance(packet_record, CanPacketRecord) - assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive - # TODO: performance - # assert (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout assert packet_record.direction == TransmissionDirection.RECEIVED assert packet_record.raw_frame_data == tuple(frame.data) assert packet_record.addressing_format == addressing_information.addressing_format @@ -222,8 +222,11 @@ def test_receive_packet__functional_receive(self, addressing_information, frame, assert packet_record.target_address == addressing_information.rx_packets_functional_ai["target_address"] assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] + # performance checks + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 is resolved + assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + assert (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout - @pytest.mark.skip @pytest.mark.parametrize("addressing_type, addressing_information, frame", [ (AddressingType.PHYSICAL, CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -282,20 +285,31 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, with pytest.raises(TimeoutError): can_transport_interface.receive_packet(timeout=timeout) + +class TestAsyncPythonCanKvaser: + """System Tests for asynchronous functions of `PyCanTransportInterface` with Kvaser as bus manager.""" + + def setup_class(self): + self.bus1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) + self.bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + + def teardown_class(self): + """Safely close bus objects.""" + self.bus1.shutdown() + self.bus2.shutdown() + # async_receive_packet - @pytest.mark.skip @pytest.mark.parametrize("timeout", [1000, 50]) @pytest.mark.asyncio async def test_async_receive_packet__timeout(self, example_addressing_information, timeout): can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, addressing_information=example_addressing_information) - # TODO: time_before_receive = time() + time_before_receive = time() with pytest.raises((TimeoutError, asyncio.TimeoutError)): await can_transport_interface.async_receive_packet(timeout=timeout) - # TODO: time_after_receive = time() - # TODO: performance - # assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + 30 + time_after_receive = time() + assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + 30. @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -334,24 +348,101 @@ async def test_async_receive_packet__timeout(self, example_addressing_informatio (50, 1), ]) @pytest.mark.asyncio - async def test_async_receive_packet(self, addressing_information, frame, timeout, send_after): + async def test_async_receive_packet__physical(self, addressing_information, frame, timeout, send_after): async def _send_frame(): await asyncio.sleep(send_after/1000.) self.bus2.send(frame) + frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] + # data parameter of `frame` object must be set manually and according to `addressing_format` + # and `addressing_information` can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, addressing_information=addressing_information) + future_record = can_transport_interface.async_receive_packet(timeout=timeout) + # self.bus2.send(frame) + # record = await future_record done, _ = await asyncio.wait([_send_frame(), can_transport_interface.async_receive_packet(timeout=timeout)]) - # TODO: get packet from done + # TODO: extract packet_record from done # async_send_packet - # TODO: test - - -class TestPythonCanKvaser(AbstractTestPyCan): - """System Tests for `PyCanTransportInterface` with Kvaser as bus manager.""" - - def setup_class(self): - self.bus1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) - self.bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + @pytest.mark.parametrize("packet_type, addressing_type, addressing_information, packet_type_specific_kwargs", [ + (CanPacketType.SINGLE_FRAME, + AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + {"filler_byte": 0x1E, "payload": [0x10, 0x04]}), + (CanPacketType.FIRST_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + {"dlc": 8, "payload": [0x22, 0x10, 0x00, 0x10, 0x01, 0x10], "data_length": 0x13}), + (CanPacketType.CONSECUTIVE_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF}), + {"payload": [0x32, 0xFF], "sequence_number": 0xF}), + (CanPacketType.FLOW_CONTROL, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}), + {"dlc": 8, "flow_status": CanFlowStatus.ContinueToSend, "block_size": 0x15, "st_min": 0xFE}), + (CanPacketType.SINGLE_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}), + {"filler_byte": 0xBC, "payload": [0x22, 0x12, 0x34, 0x12, 0x56, 0x12, 0x78, 0x12, 0x9A, 0x12, 0xBC], "dlc":0xF}), + ]) + @pytest.mark.asyncio + async def test_async_send_packet(self, packet_type, addressing_type, addressing_information, packet_type_specific_kwargs): # TODO: why is it failing when run with other test cases? + if addressing_type == AddressingType.PHYSICAL: + can_id = addressing_information.tx_packets_physical_ai["can_id"] + target_address = addressing_information.tx_packets_physical_ai["target_address"] + source_address = addressing_information.tx_packets_physical_ai["source_address"] + address_extension = addressing_information.tx_packets_physical_ai["address_extension"] + else: + can_id = addressing_information.tx_packets_functional_ai["can_id"] + target_address = addressing_information.tx_packets_functional_ai["target_address"] + source_address = addressing_information.tx_packets_functional_ai["source_address"] + address_extension = addressing_information.tx_packets_functional_ai["address_extension"] + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=addressing_information) + packet = CanPacket(packet_type=packet_type, + addressing_format=addressing_information.addressing_format, + addressing_type=addressing_type, + can_id=can_id, + target_address=target_address, + source_address=source_address, + address_extension=address_extension, + **packet_type_specific_kwargs) + datetime_before_send = datetime.now() + packet_record = await can_transport_interface.async_send_packet(packet) + datetime_after_send = datetime.now() + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.TRANSMITTED + assert packet_record.raw_frame_data == packet.raw_frame_data + assert packet_record.addressing_format == packet.addressing_format == addressing_information.addressing_format + assert packet_record.packet_type == packet.packet_type == packet_type + assert packet_record.can_id == packet.can_id == can_id + assert packet_record.addressing_type == packet.addressing_type == addressing_type + 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 + # performance checks + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 is resolved + assert datetime_before_send < packet_record.transmission_time < datetime_after_send \ No newline at end of file diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 758db8ec..aafd7969 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -355,6 +355,11 @@ class PyCanTransportInterface(AbstractCanTransportInterface): .. note:: Documentation for python-can package: https://python-can.readthedocs.io/ """ + _MAX_LISTENER_TIMEOUT: float = 4280000. # ms + """Maximal timeout value accepted by python-can listeners.""" + _MIN_NOTIFIER_TIMEOUT: float = 0.0000001 # s + """Minimal timeout for notifiers that does not cause malfunctioning of listeners.""" + def __init__(self, can_bus_manager: BusABC, addressing_information: AbstractCanAddressingInformation, @@ -454,8 +459,7 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore data=packet.raw_frame_data, is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) message_listener = BufferedReader() - notifier = Notifier(bus=self.bus_manager, listeners=[message_listener]) - notifier.add_listener(message_listener) + notifier = Notifier(bus=self.bus_manager, listeners=[message_listener], timeout=self._MIN_NOTIFIER_TIMEOUT) self.bus_manager.send(can_message) observed_frame = None while observed_frame is None \ @@ -466,11 +470,12 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore if timeout_left <= 0: raise TimeoutError("Timeout was reached before observing a CAN Packet being transmitted.") observed_frame = message_listener.get_message(timeout=timeout_left) - notifier.stop(timeout=0) # TODO: do it smarter + notifier.stop(timeout=self._MIN_NOTIFIER_TIMEOUT) if is_flow_control_packet: self.__n_ar_measured = observed_frame.timestamp - time_start else: self.__n_as_measured = observed_frame.timestamp - time_start + print([observed_frame.timestamp, time()]) return CanPacketRecord(frame=observed_frame, direction=TransmissionDirection.TRANSMITTED, addressing_type=packet.addressing_type, @@ -498,11 +503,11 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacke if timeout <= 0: raise ValueError("Provided timeout value is less or equal 0.") message_listener = BufferedReader() - notifier = Notifier(bus=self.bus_manager, listeners=[message_listener]) + notifier = Notifier(bus=self.bus_manager, listeners=[message_listener], timeout=self._MIN_NOTIFIER_TIMEOUT) packet_addressing_type = None while packet_addressing_type is None: time_now = time() - timeout_left = float("inf") if timeout is None else timeout / 1000. - (time_now - time_start) + 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 = message_listener.get_message(timeout=timeout_left) @@ -512,7 +517,7 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacke raise TimeoutError("CAN Packet was received after timeout.") packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) - notifier.stop(timeout=0) # TODO: do it smarter + notifier.stop(timeout=self._MIN_NOTIFIER_TIMEOUT) return CanPacketRecord(frame=received_frame, direction=TransmissionDirection.RECEIVED, addressing_type=packet_addressing_type, @@ -541,7 +546,8 @@ async def async_send_packet(self, async_message_listener = AsyncBufferedReader() async_notifier = Notifier(bus=self.bus_manager, listeners=[async_message_listener], - loop=loop) + loop=loop, + timeout=self._MIN_NOTIFIER_TIMEOUT) can_message = Message(arbitration_id=packet.can_id, is_extended_id=CanIdHandler.is_extended_can_id(packet.can_id), data=packet.raw_frame_data, @@ -554,7 +560,7 @@ async def async_send_packet(self, or not observed_frame.is_rx: timeout_left = timeout / 1000. - (time() - time_start) observed_frame = await wait_for(async_message_listener.get_message(), timeout=timeout_left) - async_notifier.stop(timeout=0) # TODO: do it smarter + async_notifier.stop(timeout=self._MIN_NOTIFIER_TIMEOUT) if is_flow_control_packet: self.__n_ar_measured = observed_frame.timestamp - time_start else: @@ -590,7 +596,8 @@ async def async_receive_packet(self, async_message_listener = AsyncBufferedReader() async_notifier = Notifier(bus=self.bus_manager, listeners=[async_message_listener], - loop=loop) + loop=loop, + timeout=self._MIN_NOTIFIER_TIMEOUT) packet_addressing_type = None while packet_addressing_type is None: if timeout is None: @@ -603,7 +610,7 @@ async def async_receive_packet(self, timeout=timeout_left) packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) - async_notifier.stop(timeout=0) # TODO: do it smarter + async_notifier.stop(timeout=self._MIN_NOTIFIER_TIMEOUT) return CanPacketRecord(frame=received_frame, direction=TransmissionDirection.RECEIVED, addressing_type=packet_addressing_type, From 2f1523b57d33cba5c34e10532907350a14aaf296 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Fri, 13 Oct 2023 14:53:34 +0200 Subject: [PATCH 23/40] update system tests Figured out that timeout tests causes problems, still trying to figure out the root cause. --- .../python_can_timing_issue.py | 4 +- .../test_python_can.py | 246 ++++++++++++------ .../can_transport_interface.py | 1 - 3 files changed, 170 insertions(+), 81 deletions(-) diff --git a/examples/can/kvaser with python-can/python_can_timing_issue.py b/examples/can/kvaser with python-can/python_can_timing_issue.py index 6499e85a..a7a81bd2 100644 --- a/examples/can/kvaser with python-can/python_can_timing_issue.py +++ b/examples/can/kvaser with python-can/python_can_timing_issue.py @@ -7,12 +7,12 @@ kvaser_bus_2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) # connected with bus 1 buffered_reader = BufferedReader() - notifier = Notifier(bus=kvaser_bus_2, listeners=[buffered_reader]) + notifier = Notifier(bus=kvaser_bus_1, listeners=[buffered_reader]) message = Message(data=[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], arbitration_id=0x100) for _ in range(10): - Timer(interval=0.1, function=kvaser_bus_1.send, args=(message, )).run() + Timer(interval=0.1, function=kvaser_bus_1.send, args=(message, )).start() sent_message = buffered_reader.get_message(timeout=1) timestamp_after_send = time() 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 9c010b06..812c5787 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 @@ -102,11 +102,73 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, assert packet_record.source_address == packet.source_address == source_address assert packet_record.address_extension == packet.address_extension == address_extension # performance checks - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 is resolved - assert datetime_before_send < packet_record.transmission_time < datetime_after_send + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # assert datetime_before_send < packet_record.transmission_time < datetime_after_send # receive_packet + # TODO: @pytest.mark.skip("Causes malfunctioning of following tests") + @pytest.mark.parametrize("addressing_type, addressing_information, frame", [ + (AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + Message(data=[0x02, 0x10, 0x03])), + (AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + Message(data=[0x2C] + list(range(100, 163)), is_fd=True)), + (AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF}, ), + Message(data=[0xFF, 0x30, 0xAB, 0x7F])), + (AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}, ), + Message(data=[0xFF, 0x11, 0x23, 0x62, 0x92, 0xD0, 0xB1, 0x00])), + (AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, + "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, + "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, + "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, + "address_extension": 0xFF}, ), + Message(data=[0xFF, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), + ]) + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 1001), # ms + (50, 55), + ]) + def test_receive_packet__timeout(self, addressing_information, addressing_type, frame, timeout, send_after): + if addressing_type == AddressingType.PHYSICAL: + frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] + else: + frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] + # data parameter of `frame` object must be set manually and according to `addressing_format` + # and `addressing_information` + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=addressing_information) + Timer(interval=send_after/1000., function=self.bus2.send, args=(frame,)).start() + time_before_receive = time() + with pytest.raises(TimeoutError): + can_transport_interface.receive_packet(timeout=timeout) + time_after_receive = time() + assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + 25. + @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, @@ -141,9 +203,9 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, ]) @pytest.mark.parametrize("timeout, send_after", [ (1000, 950), # ms - (50, 1), + (50, 20), ]) - def test_receive_packet__physical_receive(self, addressing_information, frame, timeout, send_after): + def test_receive_packet__physical(self, addressing_information, frame, timeout, send_after): frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` @@ -163,9 +225,9 @@ def test_receive_packet__physical_receive(self, addressing_information, frame, t assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] # performance checks - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 is resolved - assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive - assert (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout # performance check + assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -201,9 +263,9 @@ def test_receive_packet__physical_receive(self, addressing_information, frame, t ]) @pytest.mark.parametrize("timeout, send_after", [ (1000, 950), # ms - (50, 1), + (50, 20), ]) - def test_receive_packet__functional_receive(self, addressing_information, frame, timeout, send_after): + def test_receive_packet__functional(self, addressing_information, frame, timeout, send_after): frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` @@ -223,67 +285,9 @@ def test_receive_packet__functional_receive(self, addressing_information, frame, assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] # performance checks - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 is resolved - assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive - assert (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout - - @pytest.mark.parametrize("addressing_type, addressing_information, frame", [ - (AddressingType.PHYSICAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - tx_physical={"can_id": 0x611}, - rx_physical={"can_id": 0x612}, - tx_functional={"can_id": 0x6FF}, - rx_functional={"can_id": 0x6FE}), - Message(data=[0x02, 0x10, 0x03])), - (AddressingType.PHYSICAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, - tx_physical={"target_address": 0x1B, "source_address": 0xFF}, - rx_physical={"target_address": 0xFF, "source_address": 0x1B}, - tx_functional={"target_address": 0xAC, "source_address": 0xFE}, - rx_functional={"target_address": 0xFE, "source_address": 0xAC}), - Message(data=[0x2C] + list(range(100, 163)), is_fd=True)), - (AddressingType.FUNCTIONAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, - tx_physical={"can_id": 0x987, "target_address": 0x90}, - rx_physical={"can_id": 0x987, "target_address": 0xFE}, - tx_functional={"can_id": 0x11765, "target_address": 0x5A}, - rx_functional={"can_id": 0x11765, "target_address": 0xFF}, ), - Message(data=[0xFF, 0x30, 0xAB, 0x7F])), - (AddressingType.FUNCTIONAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, - tx_physical={"can_id": 0x651, "address_extension": 0x87}, - rx_physical={"can_id": 0x652, "address_extension": 0xFE}, - tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, - rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}, ), - Message(data=[0xFF, 0x11, 0x23, 0x62, 0x92, 0xD0, 0xB1, 0x00])), - (AddressingType.FUNCTIONAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, - tx_physical={"target_address": 0x1B, "source_address": 0xFF, - "address_extension": 0x87}, - rx_physical={"target_address": 0xFF, "source_address": 0x1B, - "address_extension": 0x87}, - tx_functional={"target_address": 0xAC, "source_address": 0xFE, - "address_extension": 0xFF}, - rx_functional={"target_address": 0xFE, "source_address": 0xAC, - "address_extension": 0xFF}, ), - Message(data=[0xFF, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), - ]) - @pytest.mark.parametrize("timeout, send_after", [ - (1000, 1001), # ms - (50, 55), - ]) - def test_receive_packet__timeout(self, addressing_information, addressing_type, frame, timeout, send_after): - if addressing_type == AddressingType.PHYSICAL: - frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] - else: - frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] - # data parameter of `frame` object must be set manually and according to `addressing_format` - # and `addressing_information` - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, - addressing_information=addressing_information) - Timer(interval=send_after/1000., function=self.bus2.send, args=(frame,)).start() - with pytest.raises(TimeoutError): - can_transport_interface.receive_packet(timeout=timeout) + assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive class TestAsyncPythonCanKvaser: @@ -300,6 +304,7 @@ def teardown_class(self): # async_receive_packet + # TODO: @pytest.mark.skip("Causes malfunctioning of following tests") @pytest.mark.parametrize("timeout", [1000, 50]) @pytest.mark.asyncio async def test_async_receive_packet__timeout(self, example_addressing_information, timeout): @@ -309,7 +314,7 @@ async def test_async_receive_packet__timeout(self, example_addressing_informatio with pytest.raises((TimeoutError, asyncio.TimeoutError)): await can_transport_interface.async_receive_packet(timeout=timeout) time_after_receive = time() - assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + 30. + assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + 25. @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -345,7 +350,7 @@ async def test_async_receive_packet__timeout(self, example_addressing_informatio ]) @pytest.mark.parametrize("timeout, send_after", [ (1000, 950), # ms - (50, 1), + (50, 20), ]) @pytest.mark.asyncio async def test_async_receive_packet__physical(self, addressing_information, frame, timeout, send_after): @@ -359,10 +364,95 @@ async def _send_frame(): can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, addressing_information=addressing_information) future_record = can_transport_interface.async_receive_packet(timeout=timeout) - # self.bus2.send(frame) - # record = await future_record - done, _ = await asyncio.wait([_send_frame(), can_transport_interface.async_receive_packet(timeout=timeout)]) - # TODO: extract packet_record from done + datetime_before_receive = datetime.now() + done_tasks, _ = await asyncio.wait([_send_frame(), future_record]) + 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] + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.RECEIVED + assert packet_record.raw_frame_data == tuple(frame.data) + assert packet_record.addressing_format == addressing_information.addressing_format + assert packet_record.addressing_type == AddressingType.PHYSICAL + assert packet_record.can_id == frame.arbitration_id == addressing_information.rx_packets_physical_ai["can_id"] + assert packet_record.target_address == addressing_information.rx_packets_physical_ai["target_address"] + assert packet_record.source_address == addressing_information.rx_packets_physical_ai["source_address"] + assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] + # performance checks + assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + + @pytest.mark.parametrize("addressing_information, frame", [ + (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + Message(data=[0x02, 0x10, 0x03])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + Message(data=[0x2C] + list(range(100, 163)), is_fd=True)), + (CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF},), + Message(data=[0xFF, 0x30, 0xAB, 0x7F])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}, ), + Message(data=[0xFF, 0x11, 0x23, 0x62, 0x92, 0xD0, 0xB1, 0x00])), + (CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}, ), + Message(data=[0xFF, 0x02, 0x3E, 0x80, 0xAA, 0xAA, 0xAA, 0xAA])), + ]) + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 950), # ms + (50, 20), + ]) + @pytest.mark.asyncio + async def test_async_receive_packet__functional(self, addressing_information, frame, timeout, send_after): + async def _send_frame(): + await asyncio.sleep(send_after/1000.) + self.bus2.send(frame) + + frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] + # data parameter of `frame` object must be set manually and according to `addressing_format` + # and `addressing_information` + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=addressing_information) + future_record = can_transport_interface.async_receive_packet(timeout=timeout) + datetime_before_receive = datetime.now() + done_tasks, _ = await asyncio.wait([_send_frame(), future_record]) + 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] + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.RECEIVED + assert packet_record.raw_frame_data == tuple(frame.data) + assert packet_record.addressing_format == addressing_information.addressing_format + assert packet_record.addressing_type == AddressingType.FUNCTIONAL + assert packet_record.can_id == frame.arbitration_id == addressing_information.rx_packets_functional_ai["can_id"] + assert packet_record.target_address == addressing_information.rx_packets_functional_ai["target_address"] + assert packet_record.source_address == addressing_information.rx_packets_functional_ai["source_address"] + assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] + # performance checks + assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive # async_send_packet @@ -444,5 +534,5 @@ async def test_async_send_packet(self, packet_type, addressing_type, addressing_ assert packet_record.source_address == packet.source_address == source_address assert packet_record.address_extension == packet.address_extension == address_extension # performance checks - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 is resolved - assert datetime_before_send < packet_record.transmission_time < datetime_after_send \ No newline at end of file + # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # assert datetime_before_send < packet_record.transmission_time < datetime_after_send diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index aafd7969..66f4e7f1 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -475,7 +475,6 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore self.__n_ar_measured = observed_frame.timestamp - time_start else: self.__n_as_measured = observed_frame.timestamp - time_start - print([observed_frame.timestamp, time()]) return CanPacketRecord(frame=observed_frame, direction=TransmissionDirection.TRANSMITTED, addressing_type=packet.addressing_type, From 7f3ac08fcbb867ce2f9f1a7c4afeca1349053555 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Tue, 17 Oct 2023 17:03:17 +0200 Subject: [PATCH 24/40] resolve notifiers problem Update system tests. TODO: refactor code and add some test. Seems like we have got it and soon task would be clsoed. --- .../test_python_can.py | 60 ++++++++++++++- .../transport_interface/conftest.py | 39 ++++++++++ .../abstract_transport_interface.py | 3 +- .../can_transport_interface.py | 76 +++++++++++++------ 4 files changed, 150 insertions(+), 28 deletions(-) diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index 812c5787..0811f59b 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 @@ -1,7 +1,7 @@ import pytest import asyncio from threading import Timer -from time import time +from time import time, sleep from datetime import datetime from can import Bus, Message @@ -9,6 +9,7 @@ from uds.transport_interface import PyCanTransportInterface from uds.transmission_attributes import AddressingType, TransmissionDirection from uds.packet import CanPacket, CanPacketType, CanPacketRecord +from uds.message import UdsMessage class TestPythonCanKvaser: @@ -68,6 +69,7 @@ def teardown_class(self): {"filler_byte": 0xBC, "payload": [0x22, 0x12, 0x34, 0x12, 0x56, 0x12, 0x78, 0x12, 0x9A, 0x12, 0xBC], "dlc":0xF}), ]) def test_send_packet(self, packet_type, addressing_type, addressing_information, packet_type_specific_kwargs): + # TODO: docstring if addressing_type == AddressingType.PHYSICAL: can_id = addressing_information.tx_packets_physical_ai["can_id"] target_address = addressing_information.tx_packets_physical_ai["target_address"] @@ -107,7 +109,6 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, # receive_packet - # TODO: @pytest.mark.skip("Causes malfunctioning of following tests") @pytest.mark.parametrize("addressing_type, addressing_information, frame", [ (AddressingType.PHYSICAL, CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -154,6 +155,7 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, (50, 55), ]) def test_receive_packet__timeout(self, addressing_information, addressing_type, frame, timeout, send_after): + # TODO: docstring if addressing_type == AddressingType.PHYSICAL: frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] else: @@ -206,6 +208,7 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, (50, 20), ]) def test_receive_packet__physical(self, addressing_information, frame, timeout, send_after): + # TODO: docstring frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` @@ -266,6 +269,7 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, (50, 20), ]) def test_receive_packet__functional(self, addressing_information, frame, timeout, send_after): + # TODO: docstring frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` @@ -289,6 +293,44 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + # use cases + + def test_timeout_then_send(self, example_addressing_information): + # TODO: docstring + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + uds_message = UdsMessage(payload=[0x3E, 0x00], addressing_type=AddressingType.PHYSICAL) + packet = can_transport_interface.segmenter.segmentation(uds_message)[0] + with pytest.raises(TimeoutError): + can_transport_interface.receive_packet(timeout=100) + packet_record = can_transport_interface.send_packet(packet) + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.TRANSMITTED + + def test_timeout_then_receive(self, example_addressing_information, example_rx_frame): + # TODO: docstring + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + with pytest.raises(TimeoutError): + can_transport_interface.receive_packet(timeout=100) + self.bus2.send(example_rx_frame) + packet_record = can_transport_interface.receive_packet(timeout=100) + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.RECEIVED + + def test_observe_tx_packet(self, example_addressing_information, example_tx_frame, example_tx_uds_message): + # TODO: docstring + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + packet = can_transport_interface.segmenter.segmentation(example_tx_uds_message)[0] + self.bus1.send(example_tx_frame) + sleep(0.1) + datetime_before_send = datetime.now() + packet_record = can_transport_interface.send_packet(packet) + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.TRANSMITTED + assert packet_record.transmission_time > datetime_before_send + class TestAsyncPythonCanKvaser: """System Tests for asynchronous functions of `PyCanTransportInterface` with Kvaser as bus manager.""" @@ -304,10 +346,10 @@ def teardown_class(self): # async_receive_packet - # TODO: @pytest.mark.skip("Causes malfunctioning of following tests") @pytest.mark.parametrize("timeout", [1000, 50]) @pytest.mark.asyncio async def test_async_receive_packet__timeout(self, example_addressing_information, timeout): + # TODO: docstring can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, addressing_information=example_addressing_information) time_before_receive = time() @@ -354,6 +396,7 @@ async def test_async_receive_packet__timeout(self, example_addressing_informatio ]) @pytest.mark.asyncio async def test_async_receive_packet__physical(self, addressing_information, frame, timeout, send_after): + # TODO: docstring async def _send_frame(): await asyncio.sleep(send_after/1000.) self.bus2.send(frame) @@ -423,6 +466,7 @@ async def _send_frame(): ]) @pytest.mark.asyncio async def test_async_receive_packet__functional(self, addressing_information, frame, timeout, send_after): + # TODO: docstring async def _send_frame(): await asyncio.sleep(send_after/1000.) self.bus2.send(frame) @@ -499,7 +543,8 @@ async def _send_frame(): {"filler_byte": 0xBC, "payload": [0x22, 0x12, 0x34, 0x12, 0x56, 0x12, 0x78, 0x12, 0x9A, 0x12, 0xBC], "dlc":0xF}), ]) @pytest.mark.asyncio - async def test_async_send_packet(self, packet_type, addressing_type, addressing_information, packet_type_specific_kwargs): # TODO: why is it failing when run with other test cases? + async def test_async_send_packet(self, packet_type, addressing_type, addressing_information, packet_type_specific_kwargs): + # TODO: docstring if addressing_type == AddressingType.PHYSICAL: can_id = addressing_information.tx_packets_physical_ai["can_id"] target_address = addressing_information.tx_packets_physical_ai["target_address"] @@ -536,3 +581,10 @@ async def test_async_send_packet(self, packet_type, addressing_type, addressing_ # performance checks # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved # assert datetime_before_send < packet_record.transmission_time < datetime_after_send + + # use cases + + # TODO: multiple packets tests + # Procedure 1 - timeout at receive then receive another packet + # Procedure 2 - skip receiving packet, then receive exactly the same one later, make sure timing is good + # Procedure 3 - send and receive in the same task, make sure both are executed. diff --git a/tests/system_tests/transport_interface/conftest.py b/tests/system_tests/transport_interface/conftest.py index 3715631a..23959355 100644 --- a/tests/system_tests/transport_interface/conftest.py +++ b/tests/system_tests/transport_interface/conftest.py @@ -1,12 +1,51 @@ from pytest import fixture +from can import Message + from uds.can import CanAddressingInformation, CanAddressingFormat +from uds.message import UdsMessage +from uds.transmission_attributes import AddressingType @fixture def example_addressing_information(): + """Example Addressing Information that can be used in test cases.""" return CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, rx_physical={"can_id": 0x612}, tx_functional={"can_id": 0x6FF}, rx_functional={"can_id": 0x6FE}) + + +@fixture +def example_rx_frame(): + """ + Example CAN Frame containing a CAN Packet with received addressing information. + + .. note:: + Compatible with example_addressing_information. + """ + return Message(arbitration_id=0x612, data=[0x02, 0x10, 0x03]) + + +@fixture +def example_tx_frame(): + """ + Example CAN Frame containing a CAN Packet with transmitted addressing information. + + .. note:: + Compatible with example_addressing_information. + """ + return Message(arbitration_id=0x611, data=[0x02, 0x50, 0x03, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]) + + +@fixture +def example_tx_uds_message(): + """ + Example CAN Frame containing a CAN Packet with transmitted addressing information. + + .. note:: + The same as example_tx_frame. + Compatible with example_addressing_information. + """ + return UdsMessage(payload=[0x50, 0x03], addressing_type=AddressingType.PHYSICAL) diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index 82081fa9..488465a1 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -18,8 +18,7 @@ class AbstractTransportInterface(ABC): Transport Interfaces are meant to handle middle layers (Transport and Network) of UDS OSI Model. """ - def __init__(self, - bus_manager: Any) -> None: + def __init__(self, bus_manager: Any) -> None: """ Create Transport Interface (an object for handling UDS Transport and Network layers). diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 66f4e7f1..2683f152 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -391,6 +391,15 @@ def __init__(self, super().__init__(can_bus_manager=can_bus_manager, addressing_information=addressing_information, **kwargs) + self.__notifier: Optional[Notifier] = None + self.__frames_buffer = BufferedReader() + self.__async_notifier: Optional[Notifier] = None + self.__async_frames_buffer = AsyncBufferedReader() + + def __del__(self): + # TODO: docstring and unit tests + self._teardown_notifier() + self._teardown_async_notifier() @property def n_as_measured(self) -> Optional[TimeMilliseconds]: @@ -428,6 +437,42 @@ def n_cr_measured(self) -> Optional[TimeMilliseconds]: """ return self.__n_cr_measured + def _teardown_notifier(self) -> None: + # TODO: docstring and unit tests + if self.__notifier is not None: + self.__notifier.stop(self._MIN_NOTIFIER_TIMEOUT) + self.__notifier = None + + def _teardown_async_notifier(self): + # TODO: docstring and unit tests + if self.__async_notifier is not None: + self.__async_notifier.stop(self._MIN_NOTIFIER_TIMEOUT) + self.__async_notifier = None + + def _setup_notifier(self) -> None: + # TODO: docstring and unit tests + self._teardown_async_notifier() + if self.__notifier is None: + self.__notifier = Notifier(bus=self.bus_manager, + listeners=[self.__frames_buffer], + timeout=self._MIN_NOTIFIER_TIMEOUT) + + def _setup_async_notifier(self, loop) -> None: + # TODO: docstring and unit tests + if self.__notifier is not None: + # TODO: warn + self._teardown_notifier() + if self.__async_notifier is None: + self.__async_notifier = Notifier(bus=self.bus_manager, + listeners=[self.__async_frames_buffer], + timeout=self._MIN_NOTIFIER_TIMEOUT, + loop=loop) + + def _clear_frames_buffer(self) -> None: + # TODO: docstring and unit tests + for _ in range(self.__frames_buffer.buffer.qsize()): + self.__frames_buffer.buffer.get_nowait() + @staticmethod def is_supported_bus_manager(bus_manager: Any) -> bool: """ @@ -458,8 +503,8 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore 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)) - message_listener = BufferedReader() - notifier = Notifier(bus=self.bus_manager, listeners=[message_listener], timeout=self._MIN_NOTIFIER_TIMEOUT) + self._setup_notifier() + self._clear_frames_buffer() self.bus_manager.send(can_message) observed_frame = None while observed_frame is None \ @@ -469,8 +514,7 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore timeout_left = timeout / 1000. - (time() - time_start) if timeout_left <= 0: raise TimeoutError("Timeout was reached before observing a CAN Packet being transmitted.") - observed_frame = message_listener.get_message(timeout=timeout_left) - notifier.stop(timeout=self._MIN_NOTIFIER_TIMEOUT) + observed_frame = self.__frames_buffer.get_message(timeout=timeout_left) if is_flow_control_packet: self.__n_ar_measured = observed_frame.timestamp - time_start else: @@ -501,22 +545,20 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacke 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.") - message_listener = BufferedReader() - notifier = Notifier(bus=self.bus_manager, listeners=[message_listener], timeout=self._MIN_NOTIFIER_TIMEOUT) + 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 = message_listener.get_message(timeout=timeout_left) + 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.") if timeout is not None and (received_frame.timestamp - time_start) * 1000. > timeout: raise TimeoutError("CAN Packet was received after timeout.") packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) - notifier.stop(timeout=self._MIN_NOTIFIER_TIMEOUT) return CanPacketRecord(frame=received_frame, direction=TransmissionDirection.RECEIVED, addressing_type=packet_addressing_type, @@ -542,11 +584,7 @@ async def async_send_packet(self, loop = get_running_loop() if loop is None else loop is_flow_control_packet = packet.packet_type == CanPacketType.FLOW_CONTROL timeout = self.n_ar_timeout if is_flow_control_packet else self.n_as_timeout - async_message_listener = AsyncBufferedReader() - async_notifier = Notifier(bus=self.bus_manager, - listeners=[async_message_listener], - loop=loop, - timeout=self._MIN_NOTIFIER_TIMEOUT) + self._setup_async_notifier(loop) can_message = Message(arbitration_id=packet.can_id, is_extended_id=CanIdHandler.is_extended_can_id(packet.can_id), data=packet.raw_frame_data, @@ -558,8 +596,7 @@ async def async_send_packet(self, or tuple(observed_frame.data) != packet.raw_frame_data \ or not observed_frame.is_rx: timeout_left = timeout / 1000. - (time() - time_start) - observed_frame = await wait_for(async_message_listener.get_message(), timeout=timeout_left) - async_notifier.stop(timeout=self._MIN_NOTIFIER_TIMEOUT) + 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 else: @@ -592,11 +629,7 @@ async def async_receive_packet(self, if timeout <= 0: raise ValueError("Provided timeout value is less or equal 0.") loop = get_running_loop() if loop is None else loop - async_message_listener = AsyncBufferedReader() - async_notifier = Notifier(bus=self.bus_manager, - listeners=[async_message_listener], - loop=loop, - timeout=self._MIN_NOTIFIER_TIMEOUT) + self._setup_async_notifier(loop) packet_addressing_type = None while packet_addressing_type is None: if timeout is None: @@ -605,11 +638,10 @@ async def async_receive_packet(self, timeout_left = timeout / 1000. - (time() - time_start) if timeout_left <= 0: raise TimeoutError("Timeout was reached before a CAN Packet was received.") - received_frame = await wait_for(async_message_listener.get_message(), + received_frame = await wait_for(self.__async_frames_buffer.get_message(), timeout=timeout_left) packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) - async_notifier.stop(timeout=self._MIN_NOTIFIER_TIMEOUT) return CanPacketRecord(frame=received_frame, direction=TransmissionDirection.RECEIVED, addressing_type=packet_addressing_type, From fb27b11eeb53cd3e94825e922abba968aa6b70b5 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Wed, 18 Oct 2023 18:28:57 +0200 Subject: [PATCH 25/40] system tests ready Final version of system tests: - testing scope cover - procedures provided --- .../test_python_can.py | 601 ++++++++++++++---- .../transport_interface/conftest.py | 10 + .../can_transport_interface.py | 40 +- 3 files changed, 502 insertions(+), 149 deletions(-) diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index 0811f59b..15ee98fd 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 @@ -15,10 +15,16 @@ class TestPythonCanKvaser: """System Tests for `PyCanTransportInterface` with Kvaser as bus manager.""" + TASK_TIMING_TOLERANCE = 30. # ms + def setup_class(self): self.bus1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) self.bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + def setup_method(self): + self.bus1.flush_tx_buffer() + self.bus2.flush_tx_buffer() + def teardown_class(self): """Safely close bus objects.""" self.bus1.shutdown() @@ -69,7 +75,20 @@ def teardown_class(self): {"filler_byte": 0xBC, "payload": [0x22, 0x12, 0x34, 0x12, 0x56, 0x12, 0x78, 0x12, 0x9A, 0x12, 0xBC], "dlc":0xF}), ]) def test_send_packet(self, packet_type, addressing_type, addressing_information, packet_type_specific_kwargs): - # TODO: docstring + """ + Check for simple sending of a CAN packet. + + Procedure: + 1. Send CAN packet via Transport Interface. + Expected: CAN packet record returned. + 2. Validate transmitted CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the transmitted CAN packet. + + :param packet_type: Type of CAN Packet to send. + :param addressing_type: Addressing type to use for transmitting a CAN packet. + :param addressing_information: Example Addressing Information of a CAN Node. + :param packet_type_specific_kwargs: Parameters specific for this CAN Packet type. + """ if addressing_type == AddressingType.PHYSICAL: can_id = addressing_information.tx_packets_physical_ai["can_id"] target_address = addressing_information.tx_packets_physical_ai["target_address"] @@ -104,7 +123,7 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, assert packet_record.source_address == packet.source_address == source_address assert packet_record.address_extension == packet.address_extension == address_extension # performance checks - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_send < packet_record.transmission_time < datetime_after_send # receive_packet @@ -155,7 +174,20 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, (50, 55), ]) def test_receive_packet__timeout(self, addressing_information, addressing_type, frame, timeout, send_after): - # TODO: docstring + """ + Check for a timeout during packet receiving. + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received CAN packet. + 2. Call method to receive packet via Transport Interface with timeout set just before CAN packet reaches CAN bus. + Expected: Timeout exception is raised. + + :param addressing_type: Addressing type to use for transmitting a CAN packet. + :param addressing_information: Example Addressing Information of CAN Node. + :param frame: CAN frame to send (must be decoded as UDS CAN Packet). + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ if addressing_type == AddressingType.PHYSICAL: frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] else: @@ -169,7 +201,7 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, with pytest.raises(TimeoutError): can_transport_interface.receive_packet(timeout=timeout) time_after_receive = time() - assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + 25. + assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -208,7 +240,21 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, (50, 20), ]) def test_receive_packet__physical(self, addressing_information, frame, timeout, send_after): - # TODO: docstring + """ + Check for a simple CAN packet (physically addressed) receiving. + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received CAN packet. + 2. Call method to receive packet via Transport Interface with timeout set just after CAN packet reaches CAN bus. + Expected: CAN packet is received. + 3. Validate received CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the received CAN packet. + + :param addressing_information: Example Addressing Information of CAN Node. + :param frame: CAN frame to send (must be decoded as UDS CAN Packet). + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` @@ -229,7 +275,7 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] # performance checks assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive @pytest.mark.parametrize("addressing_information, frame", [ @@ -269,7 +315,21 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, (50, 20), ]) def test_receive_packet__functional(self, addressing_information, frame, timeout, send_after): - # TODO: docstring + """ + Check for a simple CAN packet (functionally addressed) receiving. + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received CAN packet. + 2. Call method to receive packet via Transport Interface with timeout set just after CAN packet reaches CAN bus. + Expected: CAN packet is received. + 3. Validate received CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the received CAN packet. + + :param addressing_information: Example Addressing Information of CAN Node. + :param frame: CAN frame to send (must be decoded as UDS CAN Packet). + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` @@ -290,73 +350,128 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] # performance checks assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive - # use cases + # async_send_packet - def test_timeout_then_send(self, example_addressing_information): - # TODO: docstring - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, - addressing_information=example_addressing_information) - uds_message = UdsMessage(payload=[0x3E, 0x00], addressing_type=AddressingType.PHYSICAL) - packet = can_transport_interface.segmenter.segmentation(uds_message)[0] - with pytest.raises(TimeoutError): - can_transport_interface.receive_packet(timeout=100) - packet_record = can_transport_interface.send_packet(packet) - assert isinstance(packet_record, CanPacketRecord) - assert packet_record.direction == TransmissionDirection.TRANSMITTED + @pytest.mark.parametrize("packet_type, addressing_type, addressing_information, packet_type_specific_kwargs", [ + (CanPacketType.SINGLE_FRAME, + AddressingType.FUNCTIONAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}), + {"filler_byte": 0x1E, "payload": [0x10, 0x04]}), + (CanPacketType.FIRST_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC}), + {"dlc": 8, "payload": [0x22, 0x10, 0x00, 0x10, 0x01, 0x10], "data_length": 0x13}), + (CanPacketType.CONSECUTIVE_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, + tx_physical={"can_id": 0x987, "target_address": 0x90}, + rx_physical={"can_id": 0x987, "target_address": 0xFE}, + tx_functional={"can_id": 0x11765, "target_address": 0x5A}, + rx_functional={"can_id": 0x11765, "target_address": 0xFF}), + {"payload": [0x32, 0xFF], "sequence_number": 0xF}), + (CanPacketType.FLOW_CONTROL, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, + tx_physical={"can_id": 0x651, "address_extension": 0x87}, + rx_physical={"can_id": 0x652, "address_extension": 0xFE}, + tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, + rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}), + {"dlc": 8, "flow_status": CanFlowStatus.ContinueToSend, "block_size": 0x15, "st_min": 0xFE}), + (CanPacketType.SINGLE_FRAME, + AddressingType.PHYSICAL, + CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}), + {"filler_byte": 0xBC, "payload": [0x22, 0x12, 0x34, 0x12, 0x56, 0x12, 0x78, 0x12, 0x9A, 0x12, 0xBC], "dlc":0xF}), + ]) + @pytest.mark.asyncio + async def test_async_send_packet(self, packet_type, addressing_type, addressing_information, packet_type_specific_kwargs): + """ + Check for simple asynchronous sending of a CAN packet. - def test_timeout_then_receive(self, example_addressing_information, example_rx_frame): - # TODO: docstring - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, - addressing_information=example_addressing_information) - with pytest.raises(TimeoutError): - can_transport_interface.receive_packet(timeout=100) - self.bus2.send(example_rx_frame) - packet_record = can_transport_interface.receive_packet(timeout=100) - assert isinstance(packet_record, CanPacketRecord) - assert packet_record.direction == TransmissionDirection.RECEIVED + Procedure: + 1. Send (using async method) a CAN packet via Transport Interface. + Expected: CAN packet record returned. + 2. Validate transmitted CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the transmitted CAN packet. - def test_observe_tx_packet(self, example_addressing_information, example_tx_frame, example_tx_uds_message): - # TODO: docstring + :param packet_type: Type of CAN Packet to send. + :param addressing_type: Addressing type to use for transmitting a CAN packet. + :param addressing_information: Example Addressing Information of a CAN Node. + :param packet_type_specific_kwargs: Parameters specific for this CAN Packet type. + """ + if addressing_type == AddressingType.PHYSICAL: + can_id = addressing_information.tx_packets_physical_ai["can_id"] + target_address = addressing_information.tx_packets_physical_ai["target_address"] + source_address = addressing_information.tx_packets_physical_ai["source_address"] + address_extension = addressing_information.tx_packets_physical_ai["address_extension"] + else: + can_id = addressing_information.tx_packets_functional_ai["can_id"] + target_address = addressing_information.tx_packets_functional_ai["target_address"] + source_address = addressing_information.tx_packets_functional_ai["source_address"] + address_extension = addressing_information.tx_packets_functional_ai["address_extension"] can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, - addressing_information=example_addressing_information) - packet = can_transport_interface.segmenter.segmentation(example_tx_uds_message)[0] - self.bus1.send(example_tx_frame) - sleep(0.1) + addressing_information=addressing_information) + packet = CanPacket(packet_type=packet_type, + addressing_format=addressing_information.addressing_format, + addressing_type=addressing_type, + can_id=can_id, + target_address=target_address, + source_address=source_address, + address_extension=address_extension, + **packet_type_specific_kwargs) datetime_before_send = datetime.now() - packet_record = can_transport_interface.send_packet(packet) + packet_record = await can_transport_interface.async_send_packet(packet) + datetime_after_send = datetime.now() assert isinstance(packet_record, CanPacketRecord) assert packet_record.direction == TransmissionDirection.TRANSMITTED - assert packet_record.transmission_time > datetime_before_send - - -class TestAsyncPythonCanKvaser: - """System Tests for asynchronous functions of `PyCanTransportInterface` with Kvaser as bus manager.""" - - def setup_class(self): - self.bus1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) - self.bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) - - def teardown_class(self): - """Safely close bus objects.""" - self.bus1.shutdown() - self.bus2.shutdown() + assert packet_record.raw_frame_data == packet.raw_frame_data + assert packet_record.addressing_format == packet.addressing_format == addressing_information.addressing_format + assert packet_record.packet_type == packet.packet_type == packet_type + assert packet_record.can_id == packet.can_id == can_id + assert packet_record.addressing_type == packet.addressing_type == addressing_type + 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 + # 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("timeout", [1000, 50]) @pytest.mark.asyncio async def test_async_receive_packet__timeout(self, example_addressing_information, timeout): - # TODO: docstring + """ + Check for a timeout during packet asynchronous receiving. + + Procedure: + 1. Call async method to receive packet via Transport Interface with timeout set just before CAN packet reaches CAN bus. + Expected: Timeout exception is raised. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param timeout: Timeout to pass to receive method [ms]. + """ can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, addressing_information=example_addressing_information) time_before_receive = time() with pytest.raises((TimeoutError, asyncio.TimeoutError)): await can_transport_interface.async_receive_packet(timeout=timeout) time_after_receive = time() - assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + 25. + assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -396,7 +511,21 @@ async def test_async_receive_packet__timeout(self, example_addressing_informatio ]) @pytest.mark.asyncio async def test_async_receive_packet__physical(self, addressing_information, frame, timeout, send_after): - # TODO: docstring + """ + Check for a simple asynchronous CAN packet (physically addressed) receiving. + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received CAN packet. + 2. Call async method to receive packet via Transport Interface with timeout set just after CAN packet reaches CAN bus. + Expected: CAN packet is received. + 3. Validate received CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the received CAN packet. + + :param addressing_information: Example Addressing Information of CAN Node. + :param frame: CAN frame to send (must be decoded as UDS CAN Packet). + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ async def _send_frame(): await asyncio.sleep(send_after/1000.) self.bus2.send(frame) @@ -425,7 +554,7 @@ async def _send_frame(): assert packet_record.address_extension == addressing_information.rx_packets_physical_ai["address_extension"] # performance checks assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive @pytest.mark.parametrize("addressing_information, frame", [ @@ -466,7 +595,21 @@ async def _send_frame(): ]) @pytest.mark.asyncio async def test_async_receive_packet__functional(self, addressing_information, frame, timeout, send_after): - # TODO: docstring + """ + Check for a simple asynchronous CAN packet (functionally addressed) receiving. + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received CAN packet. + 2. Call async method to receive packet via Transport Interface with timeout set just after CAN packet reaches CAN bus. + Expected: CAN packet is received. + 3. Validate received CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the received CAN packet. + + :param addressing_information: Example Addressing Information of CAN Node. + :param frame: CAN frame to send (must be decoded as UDS CAN Packet). + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ async def _send_frame(): await asyncio.sleep(send_after/1000.) self.bus2.send(frame) @@ -495,96 +638,280 @@ async def _send_frame(): assert packet_record.address_extension == addressing_information.rx_packets_functional_ai["address_extension"] # performance checks assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive - # async_send_packet + # use cases - @pytest.mark.parametrize("packet_type, addressing_type, addressing_information, packet_type_specific_kwargs", [ - (CanPacketType.SINGLE_FRAME, - AddressingType.FUNCTIONAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - tx_physical={"can_id": 0x611}, - rx_physical={"can_id": 0x612}, - tx_functional={"can_id": 0x6FF}, - rx_functional={"can_id": 0x6FE}), - {"filler_byte": 0x1E, "payload": [0x10, 0x04]}), - (CanPacketType.FIRST_FRAME, - AddressingType.PHYSICAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_FIXED_ADDRESSING, - tx_physical={"target_address": 0x1B, "source_address": 0xFF}, - rx_physical={"target_address": 0xFF, "source_address": 0x1B}, - tx_functional={"target_address": 0xAC, "source_address": 0xFE}, - rx_functional={"target_address": 0xFE, "source_address": 0xAC}), - {"dlc": 8, "payload": [0x22, 0x10, 0x00, 0x10, 0x01, 0x10], "data_length": 0x13}), - (CanPacketType.CONSECUTIVE_FRAME, - AddressingType.PHYSICAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.EXTENDED_ADDRESSING, - tx_physical={"can_id": 0x987, "target_address": 0x90}, - rx_physical={"can_id": 0x987, "target_address": 0xFE}, - tx_functional={"can_id": 0x11765, "target_address": 0x5A}, - rx_functional={"can_id": 0x11765, "target_address": 0xFF}), - {"payload": [0x32, 0xFF], "sequence_number": 0xF}), - (CanPacketType.FLOW_CONTROL, - AddressingType.PHYSICAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_11BIT_ADDRESSING, - tx_physical={"can_id": 0x651, "address_extension": 0x87}, - rx_physical={"can_id": 0x652, "address_extension": 0xFE}, - tx_functional={"can_id": 0x6FF, "address_extension": 0xA5}, - rx_functional={"can_id": 0x6FF, "address_extension": 0xFF}), - {"dlc": 8, "flow_status": CanFlowStatus.ContinueToSend, "block_size": 0x15, "st_min": 0xFE}), - (CanPacketType.SINGLE_FRAME, - AddressingType.PHYSICAL, - CanAddressingInformation(addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, - tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, - rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, - tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, - rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}), - {"filler_byte": 0xBC, "payload": [0x22, 0x12, 0x34, 0x12, 0x56, 0x12, 0x78, 0x12, 0x9A, 0x12, 0xBC], "dlc":0xF}), + @pytest.mark.parametrize("payload, addressing_type", [ + ([0x22, 0x10, 0xF5], AddressingType.PHYSICAL), + ([0x3E, 0x80], AddressingType.FUNCTIONAL), + ]) + def test_send_on_one_receive_on_other_bus(self, example_addressing_information, + example_addressing_information_2nd_node, + payload, addressing_type): + """ + Check for sending and receiving CAN Packet using two Transport Interfaces. + + Procedure: + 1. Send a CAN Packet using Transport Interface 1 (via CAN Interface 1). + Expected: CAN packet record returned. + 2. Receive a CAN Packet using Transport Interface 2 (via CAN Interface 2). + Expected: CAN packet is received. + 3. Validate received CAN packet records attributes. + Expected: Attributes of CAN packet records are in line with each other + (the same packet was received and transmitted). + + :param example_addressing_information: Addressing Information for a receiving CAN Node. + :param example_addressing_information_2nd_node: Addressing Information for a transmitting CAN Node. + It is compatible with `example_addressing_information`. + :param payload: Payload of CAN Message to send. + :param addressing_type: Addressing Type of CAN Message to send. + """ + can_transport_interface_1 = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + can_transport_interface_2 = PyCanTransportInterface(can_bus_manager=self.bus2, + 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] + + sent_packet_record = can_transport_interface_2.send_packet(packet) + received_packet_record = can_transport_interface_1.receive_packet(timeout=100) + 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("payload, addressing_type", [ + ([0x22, 0x10, 0xF5], AddressingType.PHYSICAL), + ([0x3E, 0x80], AddressingType.FUNCTIONAL), ]) @pytest.mark.asyncio - async def test_async_send_packet(self, packet_type, addressing_type, addressing_information, packet_type_specific_kwargs): - # TODO: docstring - if addressing_type == AddressingType.PHYSICAL: - can_id = addressing_information.tx_packets_physical_ai["can_id"] - target_address = addressing_information.tx_packets_physical_ai["target_address"] - source_address = addressing_information.tx_packets_physical_ai["source_address"] - address_extension = addressing_information.tx_packets_physical_ai["address_extension"] - else: - can_id = addressing_information.tx_packets_functional_ai["can_id"] - target_address = addressing_information.tx_packets_functional_ai["target_address"] - source_address = addressing_information.tx_packets_functional_ai["source_address"] - address_extension = addressing_information.tx_packets_functional_ai["address_extension"] + async def test_async_send_on_one_receive_on_other_bus(self, example_addressing_information, + example_addressing_information_2nd_node, + payload, addressing_type): + """ + Check for asynchronous sending and receiving CAN Packet using two Transport Interfaces. + + Procedure: + 1. Send (using async method) a CAN Packet using Transport Interface 1 (via CAN Interface 1). + Expected: CAN packet record returned. + 2. Receive (using async method) a CAN Packet using Transport Interface 2 (via CAN Interface 2). + Expected: CAN packet is received. + 3. Validate received CAN packet records attributes. + Expected: Attributes of CAN packet records are in line with each other + (the same packet was received and transmitted). + + :param example_addressing_information: Addressing Information for a receiving CAN Node. + :param example_addressing_information_2nd_node: Addressing Information for a transmitting CAN Node. + It is compatible with `example_addressing_information`. + :param payload: Payload of CAN Message to send. + :param addressing_type: Addressing Type of CAN Message to send. + """ + can_transport_interface_1 = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + can_transport_interface_2 = PyCanTransportInterface(can_bus_manager=self.bus2, + 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] + done_tasks, _ = await asyncio.wait([can_transport_interface_2.async_send_packet(packet), + can_transport_interface_1.async_receive_packet(timeout=100)]) + 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 + + # error guessing + + @pytest.mark.parametrize("payload, addressing_type", [ + ([0x62, 0x10, 0xF5, 0x12, 0x34, 0xF0], AddressingType.PHYSICAL), + ([0x10, 0x81], AddressingType.FUNCTIONAL), + ]) + def test_timeout_then_send(self, example_addressing_information, payload, addressing_type): + """ + Check for sending a CAN Packet after a timeout exception during receiving. + + Procedure: + 1. Call method to receive packet via Transport Interface. + Expected: Timeout exception is raised. + 2. Send a CAN packet via Transport Interface. + Expected: CAN packet record returned. + 3. Validate transmitted CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the transmitted CAN packet. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param payload: Payload of CAN Message to send. + :param addressing_type: Addressing Type of CAN Message to send. + """ can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, - addressing_information=addressing_information) - packet = CanPacket(packet_type=packet_type, - addressing_format=addressing_information.addressing_format, - addressing_type=addressing_type, - can_id=can_id, - target_address=target_address, - source_address=source_address, - address_extension=address_extension, - **packet_type_specific_kwargs) - datetime_before_send = datetime.now() - packet_record = await can_transport_interface.async_send_packet(packet) - datetime_after_send = datetime.now() + addressing_information=example_addressing_information) + uds_message = UdsMessage(payload=payload, addressing_type=addressing_type) + packet = can_transport_interface.segmenter.segmentation(uds_message)[0] + with pytest.raises(TimeoutError): + can_transport_interface.receive_packet(timeout=100) + packet_record = can_transport_interface.send_packet(packet) assert isinstance(packet_record, CanPacketRecord) assert packet_record.direction == TransmissionDirection.TRANSMITTED + assert packet_record.addressing_type == packet.addressing_type == addressing_type assert packet_record.raw_frame_data == packet.raw_frame_data - assert packet_record.addressing_format == packet.addressing_format == addressing_information.addressing_format - assert packet_record.packet_type == packet.packet_type == packet_type - assert packet_record.can_id == packet.can_id == can_id + assert packet_record.payload == packet.payload == tuple(payload) + assert packet_record.can_id == packet.can_id + + @pytest.mark.parametrize("payload, addressing_type", [ + ([0x62, 0x10, 0xF5, 0x12, 0x34, 0xF0], AddressingType.PHYSICAL), + ([0x10, 0x81], AddressingType.FUNCTIONAL), + ]) + @pytest.mark.asyncio + async def test_async_timeout_then_send(self, example_addressing_information, payload, addressing_type): + """ + Check for asynchronous sending a CAN Packet after a timeout exception during asynchronous receiving. + + Procedure: + 1. Call async method to receive packet via Transport Interface. + Expected: Timeout exception is raised. + 2. Send (using async method) a CAN packet via Transport Interface. + Expected: CAN packet record returned. + 3. Validate transmitted CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the transmitted CAN packet. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param payload: Payload of CAN Message to send. + :param addressing_type: Addressing Type of CAN Message to send. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + uds_message = UdsMessage(payload=payload, addressing_type=addressing_type) + packet = can_transport_interface.segmenter.segmentation(uds_message)[0] + with pytest.raises((TimeoutError, asyncio.TimeoutError)): + await can_transport_interface.async_receive_packet(timeout=100) + packet_record = await can_transport_interface.async_send_packet(packet) + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.TRANSMITTED assert packet_record.addressing_type == packet.addressing_type == addressing_type - 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 - # performance checks - # TODO: sometimes fail because of https://github.com/hardbyte/python-can/issues/1676 - uncomment when resolved - # assert datetime_before_send < packet_record.transmission_time < datetime_after_send + assert packet_record.raw_frame_data == packet.raw_frame_data + assert packet_record.payload == packet.payload == tuple(payload) + assert packet_record.can_id == packet.can_id - # use cases + def test_timeout_then_receive(self, example_addressing_information, example_rx_frame): + """ + Check for receiving a CAN Packet after a timeout exception during receiving. + + Procedure: + 1. Call method to receive packet via Transport Interface. + Expected: Timeout exception is raised. + 2. Send a CAN frame that carries CAN packet targeting configured CAN Node. + 3. Call method to receive packet via Transport Interface. + Expected: CAN packet is received. + 4. Validate received CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the received CAN packet. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_rx_frame: Example CAN frame that shall be recognized as a CAN packet. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + with pytest.raises(TimeoutError): + can_transport_interface.receive_packet(timeout=100) + datetime_before_send = datetime.now() + self.bus2.send(example_rx_frame) + packet_record = can_transport_interface.receive_packet(timeout=100) + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.RECEIVED + assert packet_record.transmission_time > datetime_before_send - # TODO: multiple packets tests - # Procedure 1 - timeout at receive then receive another packet - # Procedure 2 - skip receiving packet, then receive exactly the same one later, make sure timing is good - # Procedure 3 - send and receive in the same task, make sure both are executed. + @pytest.mark.asyncio + async def test_async_timeout_then_receive(self, example_addressing_information, example_rx_frame): + """ + Check for asynchronous receiving a CAN Packet after a timeout exception during receiving. + + Procedure: + 1. Call async method to receive packet via Transport Interface. + Expected: Timeout exception is raised. + 2. Send a CAN frame that carries CAN packet targeting configured CAN Node. + 3. Call async method to receive packet via Transport Interface. + Expected: CAN packet is received. + 4. Validate received CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the received CAN packet. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_rx_frame: Example CAN frame that shall be recognized as a CAN packet. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + with pytest.raises((TimeoutError, asyncio.TimeoutError)): + await can_transport_interface.async_receive_packet(timeout=100) + datetime_before_send = datetime.now() + self.bus2.send(example_rx_frame) + packet_record = await can_transport_interface.async_receive_packet(timeout=100) + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.RECEIVED + assert packet_record.transmission_time > datetime_before_send + + def test_observe_tx_packet(self, example_addressing_information, example_tx_frame, example_tx_uds_message): + """ + Check for transmitting a CAN Packet after a sending identical CAN frame. + + Procedure: + 1. Send a CAN frame (which is identical to a future CAN packet) directly using CAN interface. + 2. Send a CAN packet via Transport Interface. + Expected: CAN packet record returned. + 2. Validate transmitted CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the transmitted CAN packet. + Make sure timing confirms that it is packet transmitted in step two. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_tx_frame: Example CAN frame that shall be recognized as a CAN packet. + :param example_tx_uds_message: CAN Message carried by CAN packet in example_tx_frame. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + packet = can_transport_interface.segmenter.segmentation(example_tx_uds_message)[0] + self.bus1.send(example_tx_frame) + sleep(0.1) + datetime_before_send = datetime.now() + packet_record = can_transport_interface.send_packet(packet) + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.TRANSMITTED + assert packet_record.raw_frame_data == packet.raw_frame_data == tuple(example_tx_frame.data) + assert packet_record.payload == packet.payload + assert packet_record.can_id == packet.can_id == example_tx_frame.arbitration_id + assert packet_record.transmission_time > datetime_before_send + + @pytest.mark.asyncio + async def test_async_observe_tx_packet(self, example_addressing_information, example_tx_frame, example_tx_uds_message): + """ + Check for asynchronous transmitting a CAN Packet after a sending identical CAN frame. + + Procedure: + 1. Send a CAN frame (which is identical to a future CAN packet) directly using CAN interface. + 2. Send (using async method) a CAN packet via Transport Interface. + Expected: CAN packet record returned. + 2. Validate transmitted CAN packet record attributes. + Expected: Attributes of CAN packet record are in line with the transmitted CAN packet. + Make sure timing confirms that it is packet transmitted in step two. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_tx_frame: Example CAN frame that shall be recognized as a CAN packet. + :param example_tx_uds_message: CAN Message carried by CAN packet in example_tx_frame. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + addressing_information=example_addressing_information) + packet = can_transport_interface.segmenter.segmentation(example_tx_uds_message)[0] + self.bus1.send(example_tx_frame) + sleep(0.1) + datetime_before_send = datetime.now() + packet_record = await can_transport_interface.async_send_packet(packet) + assert isinstance(packet_record, CanPacketRecord) + assert packet_record.direction == TransmissionDirection.TRANSMITTED + assert packet_record.raw_frame_data == packet.raw_frame_data + assert packet_record.payload == packet.payload + assert packet_record.can_id == packet.can_id + assert packet_record.transmission_time > datetime_before_send diff --git a/tests/system_tests/transport_interface/conftest.py b/tests/system_tests/transport_interface/conftest.py index 23959355..5e127266 100644 --- a/tests/system_tests/transport_interface/conftest.py +++ b/tests/system_tests/transport_interface/conftest.py @@ -17,6 +17,16 @@ def example_addressing_information(): rx_functional={"can_id": 0x6FE}) +@fixture +def example_addressing_information_2nd_node(): + """Example Addressing Information that can be used in test cases.""" + return CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x612}, + rx_physical={"can_id": 0x611}, + tx_functional={"can_id": 0x6FE}, + rx_functional={"can_id": 0x6FF}) + + @fixture def example_rx_frame(): """ diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 2683f152..f727351b 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -398,8 +398,8 @@ def __init__(self, def __del__(self): # TODO: docstring and unit tests - self._teardown_notifier() - self._teardown_async_notifier() + self._teardown_notifier(suppress_warning=True) + self._teardown_async_notifier(suppress_warning=True) @property def n_as_measured(self) -> Optional[TimeMilliseconds]: @@ -437,17 +437,30 @@ def n_cr_measured(self) -> Optional[TimeMilliseconds]: """ return self.__n_cr_measured - def _teardown_notifier(self) -> None: + def _teardown_notifier(self, suppress_warning: bool = False) -> None: # TODO: docstring and unit tests if self.__notifier is not None: self.__notifier.stop(self._MIN_NOTIFIER_TIMEOUT) self.__notifier = None - - def _teardown_async_notifier(self): + if not suppress_warning: + warn(message="Asynchronous (`PyCanTransportInterface.async_send_packet`, " + "`PyCanTransportInterface.async_receive_packet methods`) " + "and synchronous (`PyCanTransportInterface.send_packet`, " + "`PyCanTransportInterface.receive_packet methods`) shall not be used together.", + category=UserWarning) + + def _teardown_async_notifier(self, suppress_warning: bool = False): # TODO: docstring and unit tests if self.__async_notifier is not None: self.__async_notifier.stop(self._MIN_NOTIFIER_TIMEOUT) self.__async_notifier = None + if not suppress_warning: + if not suppress_warning: + warn(message="Asynchronous (`PyCanTransportInterface.async_send_packet`, " + "`PyCanTransportInterface.async_receive_packet methods`) " + "and synchronous (`PyCanTransportInterface.send_packet`, " + "`PyCanTransportInterface.receive_packet methods`) shall not be used together.", + category=UserWarning) def _setup_notifier(self) -> None: # TODO: docstring and unit tests @@ -459,19 +472,19 @@ def _setup_notifier(self) -> None: def _setup_async_notifier(self, loop) -> None: # TODO: docstring and unit tests - if self.__notifier is not None: - # TODO: warn - self._teardown_notifier() + self._teardown_notifier() if self.__async_notifier is None: self.__async_notifier = Notifier(bus=self.bus_manager, listeners=[self.__async_frames_buffer], timeout=self._MIN_NOTIFIER_TIMEOUT, loop=loop) - def _clear_frames_buffer(self) -> None: + def clear_frames_buffers(self) -> None: # TODO: docstring and unit tests for _ in range(self.__frames_buffer.buffer.qsize()): self.__frames_buffer.buffer.get_nowait() + for _ in range(self.__async_frames_buffer.buffer.qsize()): + self.__async_frames_buffer.buffer.get_nowait() @staticmethod def is_supported_bus_manager(bus_manager: Any) -> bool: @@ -504,13 +517,14 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore data=packet.raw_frame_data, is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) self._setup_notifier() - self._clear_frames_buffer() + self.clear_frames_buffers() self.bus_manager.send(can_message) observed_frame = None while observed_frame is None \ or observed_frame.arbitration_id != packet.can_id \ or tuple(observed_frame.data) != packet.raw_frame_data \ - or not observed_frame.is_rx: + or not observed_frame.is_rx \ + or observed_frame.timestamp < time_start: timeout_left = timeout / 1000. - (time() - time_start) if timeout_left <= 0: raise TimeoutError("Timeout was reached before observing a CAN Packet being transmitted.") @@ -585,6 +599,7 @@ async def async_send_packet(self, is_flow_control_packet = packet.packet_type == CanPacketType.FLOW_CONTROL timeout = self.n_ar_timeout if is_flow_control_packet else self.n_as_timeout self._setup_async_notifier(loop) + self.clear_frames_buffers() can_message = Message(arbitration_id=packet.can_id, is_extended_id=CanIdHandler.is_extended_can_id(packet.can_id), data=packet.raw_frame_data, @@ -594,7 +609,8 @@ async def async_send_packet(self, while observed_frame is None \ or observed_frame.arbitration_id != packet.can_id \ or tuple(observed_frame.data) != packet.raw_frame_data \ - or not observed_frame.is_rx: + or not observed_frame.is_rx \ + or observed_frame.timestamp < time_start: 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: From a76d46c481fca890efcb2710d87e34e5e3455013 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 14:42:15 +0200 Subject: [PATCH 26/40] unit tests and python-can transport interface updated Adjust python-can transport interface implementation to pass all test cases (both software and system). Add unit tests to reach 100% branch coverage. --- pyproject.toml | 6 +- .../test_can_transport_interface.py | 230 ++++++++++++++++-- .../transport_interface/conftest.py | 7 +- .../can_transport_interface.py | 32 ++- 4 files changed, 242 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dbef4523..b0be3091 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ Wiki = "https://github.com/mdabrowski1990/uds/wiki" [project.optional-dependencies] test = [ - "pytest >= 6.0.0", + "pytest >= 7.0.0", "pytest-cov", "pytest-asyncio", "mock" @@ -101,12 +101,12 @@ docs = [ [tool.setuptools.packages.find] include = [ - "uds*" + "uds.*" ] [tool.pytest.ini_options] -minversion = "6.0" +minversion = "7.0" markers = [ "integration: Software integration tests.", "performance: Performance (non-functional) tests." 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 d5c2ba39..979e24ba 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -499,6 +499,8 @@ class TestPyCanTransportInterface: def setup_method(self): self.mock_can_transport_interface = MagicMock(spec=PyCanTransportInterface) # patching + self._patcher_warn = patch(f"{SCRIPT_LOCATION}.warn") + self.mock_warn = self._patcher_warn.start() self._patcher_wait_for = patch(f"{SCRIPT_LOCATION}.wait_for", AsyncMock(side_effect=lambda *args, **kwargs: args[0])) self.mock_wait_for = self._patcher_wait_for.start() self._patcher_time = patch(f"{SCRIPT_LOCATION}.time") @@ -513,16 +515,13 @@ def setup_method(self): self.mock_can_dlc_handler = self._patcher_can_dlc_handler.start() self._patcher_can_packet_record = patch(f"{SCRIPT_LOCATION}.CanPacketRecord") self.mock_can_packet_record = self._patcher_can_packet_record.start() - self._patcher_buffered_reader = patch(f"{SCRIPT_LOCATION}.BufferedReader") - self.mock_buffered_reader = self._patcher_buffered_reader.start() - self._patcher_async_buffered_reader = patch(f"{SCRIPT_LOCATION}.AsyncBufferedReader") - self.mock_async_buffered_reader = self._patcher_async_buffered_reader.start() self._patcher_notifier = patch(f"{SCRIPT_LOCATION}.Notifier") self.mock_notifier = self._patcher_notifier.start() self._patcher_message = patch(f"{SCRIPT_LOCATION}.Message") self.mock_message = self._patcher_message.start() def teardown_method(self): + self._patcher_warn.stop() self._patcher_wait_for.stop() self._patcher_time.stop() self._patcher_datetime.stop() @@ -530,8 +529,6 @@ def teardown_method(self): self._patcher_can_id_handler.stop() self._patcher_can_dlc_handler.stop() self._patcher_can_packet_record.stop() - self._patcher_buffered_reader.stop() - self._patcher_async_buffered_reader.stop() self._patcher_notifier.stop() self._patcher_message.stop() @@ -574,6 +571,13 @@ def test_init__all_args(self, can_bus_manager, addressing_information, kwargs): 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__ + + def test_del(self): + assert PyCanTransportInterface.__del__(self.mock_can_transport_interface) is None + self.mock_can_transport_interface._teardown_notifier.assert_called_once_with(suppress_warning=True) + self.mock_can_transport_interface._teardown_async_notifier.assert_called_once_with(suppress_warning=True) + # n_as_measured @pytest.mark.parametrize("value", ["something", Mock()]) @@ -602,6 +606,114 @@ 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): + self.mock_can_transport_interface._PyCanTransportInterface__notifier = None + assert PyCanTransportInterface._teardown_notifier(self.mock_can_transport_interface) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__notifier is None + self.mock_warn.assert_not_called() + + def test_teardown_notifier__notifier(self): + mock_notifier = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__notifier = mock_notifier + assert PyCanTransportInterface._teardown_notifier(self.mock_can_transport_interface) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__notifier is None + mock_notifier.stop.assert_called_once_with(self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT) + self.mock_warn.assert_called_once() + + def test_teardown_notifier__notifier_with_suppressed_warning(self): + mock_notifier = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__notifier = mock_notifier + assert PyCanTransportInterface._teardown_notifier(self.mock_can_transport_interface, suppress_warning=True) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__notifier is None + mock_notifier.stop.assert_called_once_with(self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT) + self.mock_warn.assert_not_called() + + # _teardown_async_notifier + + def test_teardown_async_notifier__no_notifier(self): + self.mock_can_transport_interface._PyCanTransportInterface__async_notifier = None + assert PyCanTransportInterface._teardown_async_notifier(self.mock_can_transport_interface) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__async_notifier is None + self.mock_warn.assert_not_called() + + def test_teardown_async_notifier__notifier(self): + mock_notifier = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__async_notifier = mock_notifier + assert PyCanTransportInterface._teardown_async_notifier(self.mock_can_transport_interface) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__async_notifier is None + mock_notifier.stop.assert_called_once_with(self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT) + self.mock_warn.assert_called_once() + + def test_teardown_async_notifier__notifier_with_suppressed_warning(self): + mock_notifier = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__async_notifier = mock_notifier + assert PyCanTransportInterface._teardown_async_notifier(self.mock_can_transport_interface, suppress_warning=True) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__async_notifier is None + mock_notifier.stop.assert_called_once_with(self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT) + self.mock_warn.assert_not_called() + + # _setup_notifier + + def test_setup_notifier__no_notifier(self): + self.mock_can_transport_interface._PyCanTransportInterface__notifier = None + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock() + assert PyCanTransportInterface._setup_notifier(self.mock_can_transport_interface) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__notifier == self.mock_notifier.return_value + self.mock_notifier.assert_called_once_with( + bus=self.mock_can_transport_interface.bus_manager, + listeners=[self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer], + timeout=self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT) + self.mock_can_transport_interface._teardown_async_notifier.assert_called_once_with() + + def test_setup_notifier__notifier_exists(self): + mock_notifier = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__notifier = mock_notifier + assert PyCanTransportInterface._setup_notifier(self.mock_can_transport_interface) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__notifier == mock_notifier + self.mock_notifier.assert_not_called() + self.mock_can_transport_interface._teardown_async_notifier.assert_called_once_with() + + # _setup_async_notifier + + @pytest.mark.parametrize("loop", ["some loop", Mock()]) + def test_setup_async_notifier__no_notifier(self, loop): + self.mock_can_transport_interface._PyCanTransportInterface__async_notifier = None + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock() + assert PyCanTransportInterface._setup_async_notifier(self.mock_can_transport_interface, loop=loop) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__async_notifier == self.mock_notifier.return_value + self.mock_notifier.assert_called_once_with( + bus=self.mock_can_transport_interface.bus_manager, + listeners=[self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer], + timeout=self.mock_can_transport_interface._MIN_NOTIFIER_TIMEOUT, + loop=loop) + self.mock_can_transport_interface._teardown_notifier.assert_called_once_with() + + @pytest.mark.parametrize("loop", ["some loop", Mock()]) + def test_setup_async_notifier__notifier_exists(self, loop): + mock_notifier = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__async_notifier = mock_notifier + assert PyCanTransportInterface._setup_async_notifier(self.mock_can_transport_interface, loop=loop) is None + assert self.mock_can_transport_interface._PyCanTransportInterface__async_notifier == mock_notifier + self.mock_notifier.assert_not_called() + self.mock_can_transport_interface._teardown_notifier.assert_called_once_with() + + # clear_frames_buffers + + @pytest.mark.parametrize("sync_queue_size", [0, 1, 7]) + @pytest.mark.parametrize("async_queue_size", [0, 1, 43]) + def test_clear_frames_buffers(self, sync_queue_size, async_queue_size): + mock_sync_queue = Mock(qsize=Mock(return_value=sync_queue_size)) + mock_async_queue = Mock(qsize=Mock(return_value=async_queue_size)) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(buffer=mock_sync_queue) + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(buffer=mock_async_queue) + assert PyCanTransportInterface.clear_frames_buffers(self.mock_can_transport_interface) is None + mock_sync_queue.qsize.assert_called_once_with() + mock_async_queue.qsize.assert_called_once_with() + assert mock_sync_queue.get_nowait.call_count == sync_queue_size + assert mock_async_queue.get_nowait.call_count == async_queue_size + # is_supported_bus_manager @pytest.mark.parametrize("value", ["something", Mock()]) @@ -620,6 +732,34 @@ def test_send_packet__type_error(self, mock_isinstance, packet): PyCanTransportInterface.send_packet(self.mock_can_transport_interface, packet) mock_isinstance.assert_called_once_with(packet, CanPacket) + @pytest.mark.parametrize("packet", [ + Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME, raw_frame_data=(0x12, 0x34)), + Mock(spec=CanPacket, packet_type=CanPacketType.FLOW_CONTROL, raw_frame_data=tuple(range(8))), + Mock(spec=CanPacket, packet_type=CanPacketType.CONSECUTIVE_FRAME, raw_frame_data=tuple(range(64, 128))), + ]) + def test_send_packet__timeout(self, packet): + mock_get_message = Mock(return_value=MagicMock(arbitration_id=packet.can_id, + data=packet.raw_frame_data, + is_rx=True, + timestamp=MagicMock(__lt__=Mock(return_value=False)))) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(get_message=mock_get_message) + self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured = None + self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured = None + self.mock_can_transport_interface.n_ar_timeout = self.mock_can_transport_interface.n_as_timeout \ + = MagicMock(__truediv__=lambda this, other: this, + __div__=lambda this, other: this, + __sub__=lambda this, other: this, + __le__=Mock(return_value=True)) + with pytest.raises(TimeoutError): + PyCanTransportInterface.send_packet(self.mock_can_transport_interface, packet) + self.mock_can_id_handler.is_extended_can_id.assert_called_once_with(packet.can_id) + self.mock_can_dlc_handler.is_can_fd_specific_dlc.assert_called_once_with(packet.dlc) + self.mock_message.assert_called_once_with(arbitration_id=packet.can_id, + is_extended_id=self.mock_can_id_handler.is_extended_can_id.return_value, + data=packet.raw_frame_data, + is_fd=self.mock_can_dlc_handler.is_can_fd_specific_dlc.return_value) + self.mock_can_transport_interface.bus_manager.send.assert_called_once_with(self.mock_message.return_value) + @pytest.mark.parametrize("packet", [ Mock(spec=CanPacket, packet_type=CanPacketType.FIRST_FRAME, raw_frame_data=(0x12, 0x34)), Mock(spec=CanPacket, packet_type=CanPacketType.FLOW_CONTROL, raw_frame_data=tuple(range(8))), @@ -628,10 +768,16 @@ def test_send_packet__type_error(self, mock_isinstance, packet): def test_send_packet(self, packet): mock_get_message = Mock(return_value=MagicMock(arbitration_id=packet.can_id, data=packet.raw_frame_data, - is_rx=True)) - self.mock_buffered_reader.return_value.get_message = mock_get_message + is_rx=True, + timestamp=MagicMock(__lt__=Mock(return_value=False)))) + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(get_message=mock_get_message) self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured = None self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured = None + self.mock_can_transport_interface.n_ar_timeout = self.mock_can_transport_interface.n_as_timeout \ + = MagicMock(__truediv__=lambda this, other: this, + __div__=lambda this, other: this, + __sub__=lambda this, other: this, + __le__=Mock(return_value=False)) assert PyCanTransportInterface.send_packet(self.mock_can_transport_interface, packet) \ == self.mock_can_packet_record.return_value self.mock_can_id_handler.is_extended_can_id.assert_called_once_with(packet.can_id) @@ -674,22 +820,46 @@ def test_receive_packet__value_error(self, timeout): PyCanTransportInterface.receive_packet(self.mock_can_transport_interface, timeout) @pytest.mark.parametrize("timeout", [0.001, 123.456]) - def test_receive_packet__timeout_error(self, timeout): + def test_receive_packet__timeout_error__no_message(self, timeout): + mock_is_timeout_reached = Mock(return_value=False) + self.mock_time.return_value = MagicMock(__sub__=lambda this, other: this, + __rsub__=lambda this, other: this, + __le__=mock_is_timeout_reached) mock_get_message = Mock(return_value=None) - self.mock_buffered_reader.return_value.get_message = mock_get_message + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(get_message=mock_get_message) + with pytest.raises(TimeoutError): + PyCanTransportInterface.receive_packet(self.mock_can_transport_interface, timeout) + mock_get_message.assert_called_once() + + @pytest.mark.parametrize("timeout", [0.001, 123.456]) + def test_receive_packet__timeout_error__out_of_time(self, timeout): + mock_is_timeout_reached = Mock(return_value=True) + self.mock_time.return_value = MagicMock(__sub__=lambda this, other: this, + __rsub__=lambda this, other: this, + __le__=mock_is_timeout_reached) + mock_get_message = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(get_message=mock_get_message) with pytest.raises(TimeoutError): PyCanTransportInterface.receive_packet(self.mock_can_transport_interface, timeout) + mock_is_timeout_reached.assert_called_once_with(0) @pytest.mark.parametrize("timeout", [None, 0.001, 123.456]) def test_receive_packet(self, timeout): + mock_is_timeout_reached = Mock(return_value=False) + self.mock_time.return_value = self.mock_can_transport_interface._MAX_LISTENER_TIMEOUT \ + = MagicMock(__sub__=lambda this, other: this, + __rsub__=lambda this, other: this, + __le__=mock_is_timeout_reached) + mock_get_message = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__frames_buffer = Mock(get_message=mock_get_message) assert PyCanTransportInterface.receive_packet(self.mock_can_transport_interface, timeout) \ == self.mock_can_packet_record.return_value - self.mock_datetime.fromtimestamp.assert_called_once_with(self.mock_buffered_reader.return_value.get_message.return_value.timestamp) + self.mock_datetime.fromtimestamp.assert_called_once_with(mock_get_message.return_value.timestamp) self.mock_can_transport_interface.segmenter.is_input_packet.assert_called_once_with( - can_id=self.mock_buffered_reader.return_value.get_message.return_value.arbitration_id, - data=self.mock_buffered_reader.return_value.get_message.return_value.data) + can_id=mock_get_message.return_value.arbitration_id, + data=mock_get_message.return_value.data) self.mock_can_packet_record.assert_called_once_with( - frame=self.mock_buffered_reader.return_value.get_message.return_value, + frame=mock_get_message.return_value, direction=TransmissionDirection.RECEIVED, addressing_type=self.mock_can_transport_interface.segmenter.is_input_packet.return_value, addressing_format=self.mock_can_transport_interface.segmenter.addressing_format, @@ -715,8 +885,9 @@ async def test_async_send_packet__type_error(self, mock_isinstance, packet): async def test_async_send_packet(self, packet): mock_get_message = Mock(return_value=MagicMock(arbitration_id=packet.can_id, data=packet.raw_frame_data, - is_rx=True)) - self.mock_async_buffered_reader.return_value.get_message = mock_get_message + is_rx=True, + timestamp=MagicMock(__lt__=Mock(return_value=False)))) + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(get_message=mock_get_message) self.mock_can_transport_interface._PyCanTransportInterface__n_ar_measured = None self.mock_can_transport_interface._PyCanTransportInterface__n_as_measured = None assert await PyCanTransportInterface.async_send_packet(self.mock_can_transport_interface, packet) \ @@ -762,17 +933,36 @@ async def test_async_receive_packet__value_error(self, timeout): with pytest.raises(ValueError): await PyCanTransportInterface.async_receive_packet(self.mock_can_transport_interface, timeout) + @pytest.mark.parametrize("timeout", [0.001, 123.456]) + @pytest.mark.asyncio + async def test_async_receive_packet__timeout(self, timeout): + mock_is_timeout_reached = Mock(return_value=True) + self.mock_time.return_value = MagicMock(__sub__=lambda this, other: this, + __rsub__=lambda this, other: this, + __le__=mock_is_timeout_reached) + mock_get_message = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(get_message=mock_get_message) + with pytest.raises(TimeoutError): + await PyCanTransportInterface.async_receive_packet(self.mock_can_transport_interface, timeout) + mock_is_timeout_reached.assert_called_once_with(0) + @pytest.mark.parametrize("timeout", [None, 0.001, 123.456]) @pytest.mark.asyncio async def test_async_receive_packet(self, timeout): + mock_is_timeout_reached = Mock(return_value=False) + self.mock_time.return_value = MagicMock(__sub__=lambda this, other: this, + __rsub__=lambda this, other: this, + __le__=mock_is_timeout_reached) + mock_get_message = Mock() + self.mock_can_transport_interface._PyCanTransportInterface__async_frames_buffer = Mock(get_message=mock_get_message) assert await PyCanTransportInterface.async_receive_packet(self.mock_can_transport_interface, timeout) \ == self.mock_can_packet_record.return_value - self.mock_datetime.fromtimestamp.assert_called_once_with(self.mock_async_buffered_reader.return_value.get_message.return_value.timestamp) + self.mock_datetime.fromtimestamp.assert_called_once_with(mock_get_message.return_value.timestamp) self.mock_can_transport_interface.segmenter.is_input_packet.assert_called_once_with( - can_id=self.mock_async_buffered_reader.return_value.get_message.return_value.arbitration_id, - data=self.mock_async_buffered_reader.return_value.get_message.return_value.data) + can_id=mock_get_message.return_value.arbitration_id, + data=mock_get_message.return_value.data) self.mock_can_packet_record.assert_called_once_with( - frame=self.mock_async_buffered_reader.return_value.get_message.return_value, + frame=mock_get_message.return_value, direction=TransmissionDirection.RECEIVED, addressing_type=self.mock_can_transport_interface.segmenter.is_input_packet.return_value, addressing_format=self.mock_can_transport_interface.segmenter.addressing_format, diff --git a/tests/system_tests/transport_interface/conftest.py b/tests/system_tests/transport_interface/conftest.py index 5e127266..f6effa2b 100644 --- a/tests/system_tests/transport_interface/conftest.py +++ b/tests/system_tests/transport_interface/conftest.py @@ -19,7 +19,12 @@ def example_addressing_information(): @fixture def example_addressing_information_2nd_node(): - """Example Addressing Information that can be used in test cases.""" + """ + Example Addressing Information that can be used in test cases. + + .. note:: + It is Addressing Information of a CAN Node on another end to example_addressing_information. + """ return CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x612}, rx_physical={"can_id": 0x611}, diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index f727351b..06580c0f 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -397,7 +397,7 @@ def __init__(self, self.__async_frames_buffer = AsyncBufferedReader() def __del__(self): - # TODO: docstring and unit tests + """Safely close all threads open by this object.""" self._teardown_notifier(suppress_warning=True) self._teardown_async_notifier(suppress_warning=True) @@ -438,7 +438,11 @@ def n_cr_measured(self) -> Optional[TimeMilliseconds]: return self.__n_cr_measured def _teardown_notifier(self, suppress_warning: bool = False) -> None: - # TODO: docstring and unit tests + """ + Stop and remove CAN frame notifier for synchronous communication. + + :param suppress_warning: Do not warn about mixing Synchronous and Asynchronous implementation. + """ if self.__notifier is not None: self.__notifier.stop(self._MIN_NOTIFIER_TIMEOUT) self.__notifier = None @@ -450,7 +454,11 @@ def _teardown_notifier(self, suppress_warning: bool = False) -> None: category=UserWarning) def _teardown_async_notifier(self, suppress_warning: bool = False): - # TODO: docstring and unit tests + """ + Stop and remove CAN frame notifier for asynchronous communication. + + :param suppress_warning: Do not warn about mixing Synchronous and Asynchronous implementation. + """ if self.__async_notifier is not None: self.__async_notifier.stop(self._MIN_NOTIFIER_TIMEOUT) self.__async_notifier = None @@ -463,15 +471,19 @@ def _teardown_async_notifier(self, suppress_warning: bool = False): category=UserWarning) def _setup_notifier(self) -> None: - # TODO: docstring and unit tests + """Configure CAN frame notifier for synchronous communication.""" self._teardown_async_notifier() if self.__notifier is None: self.__notifier = Notifier(bus=self.bus_manager, listeners=[self.__frames_buffer], timeout=self._MIN_NOTIFIER_TIMEOUT) - def _setup_async_notifier(self, loop) -> None: - # TODO: docstring and unit tests + def _setup_async_notifier(self, loop: AbstractEventLoop) -> None: + """ + Configure CAN frame notifier for asynchronous communication. + + :param loop: An :mod:`asyncio` event loop to use. + """ self._teardown_notifier() if self.__async_notifier is None: self.__async_notifier = Notifier(bus=self.bus_manager, @@ -480,7 +492,11 @@ def _setup_async_notifier(self, loop) -> None: loop=loop) def clear_frames_buffers(self) -> None: - # TODO: docstring and unit tests + """ + Clear buffers with transmitted and received frames. + + .. warning:: This will cause that all CAN packets received in a past are no longer accessible. + """ for _ in range(self.__frames_buffer.buffer.qsize()): self.__frames_buffer.buffer.get_nowait() for _ in range(self.__async_frames_buffer.buffer.qsize()): @@ -569,8 +585,6 @@ def receive_packet(self, timeout: Optional[TimeMilliseconds] = None) -> CanPacke 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.") - if timeout is not None and (received_frame.timestamp - time_start) * 1000. > timeout: - raise TimeoutError("CAN Packet was received after timeout.") packet_addressing_type = self.segmenter.is_input_packet(can_id=received_frame.arbitration_id, data=received_frame.data) return CanPacketRecord(frame=received_frame, From 8e2b2b96de7d0d4956fe88c271fee0b0ea2e17ee Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 14:50:59 +0200 Subject: [PATCH 27/40] Update can_packet_type.py Fix for static code analysis --- uds/packet/can_packet_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uds/packet/can_packet_type.py b/uds/packet/can_packet_type.py index 4e5d8277..6279c983 100644 --- a/uds/packet/can_packet_type.py +++ b/uds/packet/can_packet_type.py @@ -11,7 +11,7 @@ @unique -class CanPacketType(AbstractUdsPacketType): +class CanPacketType(AbstractUdsPacketType): # pylint: disable=too-many-ancestors """ Definition of CAN packet types. From 48c9ed30455a60ef91d85898cbbdb507c2a0fcaf Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 15:05:10 +0200 Subject: [PATCH 28/40] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c0813513..47384dba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -aenum>=3.0.0 +aenum>=3.0.0, <=3.1.12 # it seems that version 3.1.13 introduced a defect python-can>=4.0.0 \ No newline at end of file From 2d1478fe04b44d82ff8c1c647b61ca7892868464 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 15:05:47 +0200 Subject: [PATCH 29/40] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b0be3091..8db8e1a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ maintainers = [ {name="UDS Package Development", email="uds-package-development@googlegroups.com"}, ] dependencies = [ - "aenum", + "aenum >=3.0.0, <=3.1.12", "python-can" ] dynamic = ["version"] From 90665634ece67fe156628cd2d1e6187cbc7c1705 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 15:10:58 +0200 Subject: [PATCH 30/40] Update can_packet_type.py --- uds/packet/can_packet_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uds/packet/can_packet_type.py b/uds/packet/can_packet_type.py index 6279c983..4e5d8277 100644 --- a/uds/packet/can_packet_type.py +++ b/uds/packet/can_packet_type.py @@ -11,7 +11,7 @@ @unique -class CanPacketType(AbstractUdsPacketType): # pylint: disable=too-many-ancestors +class CanPacketType(AbstractUdsPacketType): """ Definition of CAN packet types. From a48886b8ba865fe0f19bf9b0fda193e98cb240d9 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 16:56:30 +0200 Subject: [PATCH 31/40] update examples Add comments and clean examples code. --- .../python_can_timing_issue.py | 20 ++++-- .../kvaser with python-can/receive_packets.py | 68 +++++++++++-------- .../receive_packets_asyncio.py | 67 ++++++++++-------- .../kvaser with python-can/send_packets.py | 52 ++++++++------ .../send_packets_asyncio.py | 60 +++++++++------- 5 files changed, 156 insertions(+), 111 deletions(-) diff --git a/examples/can/kvaser with python-can/python_can_timing_issue.py b/examples/can/kvaser with python-can/python_can_timing_issue.py index a7a81bd2..0c26d4c5 100644 --- a/examples/can/kvaser with python-can/python_can_timing_issue.py +++ b/examples/can/kvaser with python-can/python_can_timing_issue.py @@ -1,23 +1,29 @@ +"""UDS Issue: https://github.com/mdabrowski1990/uds/issues/228""" + from time import time from threading import Timer from can import Notifier, BufferedReader, Bus, Message if __name__ == "__main__": - kvaser_bus_1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) - kvaser_bus_2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) # connected with bus 1 + 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_bus_1, listeners=[buffered_reader]) + 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): - Timer(interval=0.1, function=kvaser_bus_1.send, args=(message, )).start() + Timer(interval=0.1, function=kvaser_interface_1.send, args=(message,)).start() sent_message = buffered_reader.get_message(timeout=1) timestamp_after_send = time() - print([sent_message.timestamp, timestamp_after_send, sent_message.timestamp <= timestamp_after_send]) + print(f"-----------------------------------------------\n" + f"Result:\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`)") - kvaser_bus_1.shutdown() - kvaser_bus_2.shutdown() + kvaser_interface_1.shutdown() + kvaser_interface_2.shutdown() diff --git a/examples/can/kvaser with python-can/receive_packets.py b/examples/can/kvaser with python-can/receive_packets.py index fdbe658d..6f53fda2 100644 --- a/examples/can/kvaser with python-can/receive_packets.py +++ b/examples/can/kvaser with python-can/receive_packets.py @@ -1,38 +1,46 @@ from pprint import pprint from threading import Timer +from time import sleep from can import Bus, Message from uds.transport_interface import PyCanTransportInterface from uds.can import CanAddressingInformation, CanAddressingFormat -kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) -kvaser_bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) - -example_addressing_information = CanAddressingInformation( - addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - tx_physical={"can_id": 0x611}, - rx_physical={"can_id": 0x612}, - tx_functional={"can_id": 0x6FF}, - rx_functional={"can_id": 0x6FE}, -) -can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, - addressing_information=example_addressing_information) - -frame_1 = Message(arbitration_id=0x6FE, data=[0x10, 0x03]) -frame_2 = Message(arbitration_id=0x611, data=[0x10, 0x03]) # shall be ignored, as it is not observed CAN ID -frame_3 = Message(arbitration_id=0x612, data=[0x3E, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]) - - -def main(): - Timer(interval=0.1, function=kvaser_bus.send, args=(frame_1, )).run() - record_1 = can_ti.receive_packet(timeout=1000) - pprint(record_1.__dict__) - - Timer(interval=0.3, function=kvaser_bus.send, args=(frame_2, )).run() - Timer(interval=0.8, function=kvaser_bus.send, args=(frame_3, )).run() - record_2 = can_ti.receive_packet(timeout=1000) - pprint(record_2.__dict__) - - if __name__ == "__main__": - main() + # configure CAN interfaces + 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) + + # configure Addressing Information of a CAN Node + addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}) + + # create Transport Interface object for UDS communication + can_ti = PyCanTransportInterface(can_bus_manager=kvaser_interface_1, + addressing_information=addressing_information) + + # some frames to be received later on + frame_1 = Message(arbitration_id=0x6FE, data=[0x10, 0x03]) + frame_2 = Message(arbitration_id=0x611, data=[0x10, 0x03]) # shall be ignored, as it is not observed CAN ID + frame_3 = Message(arbitration_id=0x612, data=[0x3E, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]) + + # receive CAN packet 1 + Timer(interval=0.1, function=kvaser_interface_1.send, args=(frame_1,)).start() # schedule transmission of frame 1 + record_1 = can_ti.receive_packet(timeout=1000) # receive CAN packet 1 carried by frame 1 + pprint(record_1.__dict__) # show attributes of CAN packet record 1 + + # receive CAN packet 2 + Timer(interval=0.3, function=kvaser_interface_1.send, args=(frame_2,)).start() # schedule transmission of frame 2 + Timer(interval=0.8, function=kvaser_interface_1.send, args=(frame_3,)).start() # schedule transmission of frame 3 + record_2 = can_ti.receive_packet(timeout=1000) # receive CAN packet 2 carried by frame 3 + pprint(record_2.__dict__) # show attributes of CAN packet record 2 + + # close connections with CAN interfaces + del can_ti + sleep(0.1) # wait to make sure all tasks are closed + kvaser_interface_1.shutdown() + kvaser_interface_2.shutdown() diff --git a/examples/can/kvaser with python-can/receive_packets_asyncio.py b/examples/can/kvaser with python-can/receive_packets_asyncio.py index c9bfaccf..6e78a38f 100644 --- a/examples/can/kvaser with python-can/receive_packets_asyncio.py +++ b/examples/can/kvaser with python-can/receive_packets_asyncio.py @@ -5,38 +5,45 @@ from uds.transport_interface import PyCanTransportInterface from uds.can import CanAddressingInformation, CanAddressingFormat -kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) -kvaser_bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) - -example_addressing_information = CanAddressingInformation( - addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - tx_physical={"can_id": 0x611}, - rx_physical={"can_id": 0x612}, - tx_functional={"can_id": 0x6FF}, - rx_functional={"can_id": 0x6FE}, -) -can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, - addressing_information=example_addressing_information) - -frame_1 = Message(arbitration_id=0x6FE, data=[0x10, 0x03]) -frame_2 = Message(arbitration_id=0x611, data=[0x10, 0x03]) # shall be ignored, as it is not observed CAN ID -frame_3 = Message(arbitration_id=0x612, data=[0x3E, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]) - async def main(): - r1 = can_ti.async_receive_packet(timeout=1000) - kvaser_bus2.send(frame_1) - record_1 = await r1 - pprint(record_1.__dict__) - - r2 = can_ti.async_receive_packet(timeout=1000) - kvaser_bus2.send(frame_2) - kvaser_bus2.send(frame_3) - record_2 = await r2 - pprint(record_2.__dict__) - - kvaser_bus.shutdown() - kvaser_bus2.shutdown() + # configure CAN interfaces + 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) + + # configure Addressing Information of a CAN Node + addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}) + + # create Transport Interface object for UDS communication + can_ti = PyCanTransportInterface(can_bus_manager=kvaser_interface_1, + addressing_information=addressing_information) + + # some frames to be received later on + frame_1 = Message(arbitration_id=0x6FE, data=[0x10, 0x03]) + frame_2 = Message(arbitration_id=0x611, data=[0x10, 0x03]) # shall be ignored, as it is not observed CAN ID + frame_3 = Message(arbitration_id=0x612, data=[0x3E, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]) + + # receive CAN packet 1 + kvaser_interface_2.send(frame_1) # transmit CAN Frame 1 + record_1 = await can_ti.async_receive_packet(timeout=1000) # receive CAN packet 1 carried by frame 1 + pprint(record_1.__dict__) # show attributes of CAN packet record 1 + + # receive CAN packet 2 + kvaser_interface_2.send(frame_2) # transmit CAN Frame 2 + kvaser_interface_2.send(frame_3) # transmit CAN Frame 3 + record_2 = await can_ti.async_receive_packet(timeout=1000) + pprint(record_2.__dict__) # show attributes of CAN packet record 2 + + # close connections with CAN interfaces + del can_ti + await asyncio.sleep(0.1) # wait to make sure all tasks are closed + kvaser_interface_1.shutdown() + kvaser_interface_2.shutdown() if __name__ == "__main__": diff --git a/examples/can/kvaser with python-can/send_packets.py b/examples/can/kvaser with python-can/send_packets.py index af76b651..90f4e72e 100644 --- a/examples/can/kvaser with python-can/send_packets.py +++ b/examples/can/kvaser with python-can/send_packets.py @@ -1,4 +1,5 @@ from pprint import pprint +from time import sleep from can import Bus from uds.transport_interface import PyCanTransportInterface @@ -6,33 +7,46 @@ from uds.message import UdsMessage from uds.transmission_attributes import AddressingType -kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) -kvaser_bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) - -example_addressing_information = CanAddressingInformation( - addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - tx_physical={"can_id": 0x611}, - rx_physical={"can_id": 0x612}, - tx_functional={"can_id": 0x6FF}, - rx_functional={"can_id": 0x6FE}, -) -can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, - addressing_information=example_addressing_information) - -message_1 = UdsMessage(addressing_type=AddressingType.PHYSICAL, payload=[0x10, 0x03]) -message_2 = UdsMessage(addressing_type=AddressingType.FUNCTIONAL, payload=[0x3E]) - -packet_1 = can_ti.segmenter.segmentation(message_1)[0] -packet_2 = can_ti.segmenter.segmentation(message_2)[0] - def main(): + # configure CAN interfaces + 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) + + # configure Addressing Information of a CAN Node + addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}) + + # create Transport Interface object for UDS communication + can_ti = PyCanTransportInterface(can_bus_manager=kvaser_interface_1, + addressing_information=addressing_information) + + # define UDS Messages to send + message_1 = UdsMessage(addressing_type=AddressingType.PHYSICAL, payload=[0x10, 0x03]) + message_2 = UdsMessage(addressing_type=AddressingType.FUNCTIONAL, payload=[0x3E]) + + # create CAN packets that carries those UDS Messages + packet_1 = can_ti.segmenter.segmentation(message_1)[0] + packet_2 = can_ti.segmenter.segmentation(message_2)[0] + + # send CAN Packet 1 record_1 = can_ti.send_packet(packet_1) pprint(record_1.__dict__) + # send CAN Packet 2 record_2 = can_ti.send_packet(packet_2) pprint(record_2.__dict__) + # close connections with CAN interfaces + del can_ti + sleep(0.1) # wait to make sure all tasks are closed + kvaser_interface_1.shutdown() + kvaser_interface_2.shutdown() + if __name__ == "__main__": main() diff --git a/examples/can/kvaser with python-can/send_packets_asyncio.py b/examples/can/kvaser with python-can/send_packets_asyncio.py index 5757cb6e..7c734345 100644 --- a/examples/can/kvaser with python-can/send_packets_asyncio.py +++ b/examples/can/kvaser with python-can/send_packets_asyncio.py @@ -7,36 +7,46 @@ from uds.message import UdsMessage from uds.transmission_attributes import AddressingType -kvaser_bus = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) -kvaser_bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) - -example_addressing_information = CanAddressingInformation( - addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, - tx_physical={"can_id": 0x611}, - rx_physical={"can_id": 0x612}, - tx_functional={"can_id": 0x6FF}, - rx_functional={"can_id": 0x6FE}, -) -can_ti = PyCanTransportInterface(can_bus_manager=kvaser_bus, - addressing_information=example_addressing_information) - -message_1 = UdsMessage(addressing_type=AddressingType.PHYSICAL, payload=[0x10, 0x03]) -message_2 = UdsMessage(addressing_type=AddressingType.FUNCTIONAL, payload=[0x3E]) - -packet_1 = can_ti.segmenter.segmentation(message_1)[0] -packet_2 = can_ti.segmenter.segmentation(message_2)[0] - async def main(): - r1 = can_ti.async_send_packet(packet_1) - r2 = can_ti.async_send_packet(packet_2) - - record_1 = await r1 - record_2 = await r2 - + # configure CAN interfaces + 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) + + # configure Addressing Information of a CAN Node + addressing_information = CanAddressingInformation( + addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, + tx_physical={"can_id": 0x611}, + rx_physical={"can_id": 0x612}, + tx_functional={"can_id": 0x6FF}, + rx_functional={"can_id": 0x6FE}) + + # create Transport Interface object for UDS communication + can_ti = PyCanTransportInterface(can_bus_manager=kvaser_interface_1, + addressing_information=addressing_information) + + # define UDS Messages to send + message_1 = UdsMessage(addressing_type=AddressingType.PHYSICAL, payload=[0x10, 0x03]) + message_2 = UdsMessage(addressing_type=AddressingType.FUNCTIONAL, payload=[0x3E]) + + # create CAN packets that carries those UDS Messages + packet_1 = can_ti.segmenter.segmentation(message_1)[0] + packet_2 = can_ti.segmenter.segmentation(message_2)[0] + + # send CAN Packet 1 + record_1 = await can_ti.async_send_packet(packet_1) pprint(record_1.__dict__) + + # send CAN Packet 2 + record_2 = await can_ti.async_send_packet(packet_2) pprint(record_2.__dict__) + # close connections with CAN interfaces + del can_ti + await asyncio.sleep(0.1) # wait to make sure all tasks are closed + kvaser_interface_1.shutdown() + kvaser_interface_2.shutdown() + if __name__ == "__main__": asyncio.run(main()) From 8f1cf549d5c11baaddc03934193312311f7edd38 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 17:06:34 +0200 Subject: [PATCH 32/40] adjust dependencies --- pyproject.toml | 4 ++-- requirements.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8db8e1a3..103676e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,8 +40,8 @@ maintainers = [ {name="UDS Package Development", email="uds-package-development@googlegroups.com"}, ] dependencies = [ - "aenum >=3.0.0, <=3.1.12", - "python-can" + "aenum >=3.0.0, <3.1.13", # version 3.1.13 contains a breaking change (potentially a defect) + "python-can == 4.*" ] dynamic = ["version"] diff --git a/requirements.txt b/requirements.txt index 47384dba..aeea7f7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -aenum>=3.0.0, <=3.1.12 # it seems that version 3.1.13 introduced a defect -python-can>=4.0.0 \ No newline at end of file +aenum >=3.0.0, <3.1.13 # version 3.1.13 contains a breaking change (potentially a defect) +python-can==4.* \ No newline at end of file From 4cefeae1ac08b3d9e4c7cc6b0a9fd1c50c59dd6a Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 17:09:26 +0200 Subject: [PATCH 33/40] updated testing dependencies --- pyproject.toml | 4 ++-- tests/requirements_for_software_tests.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 103676e0..5a10d77d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,10 +60,10 @@ Wiki = "https://github.com/mdabrowski1990/uds/wiki" [project.optional-dependencies] test = [ - "pytest >= 7.0.0", + "pytest == 7.*", "pytest-cov", "pytest-asyncio", - "mock" + "mock == 4.*" ] docs = [ "sphinx", diff --git a/tests/requirements_for_software_tests.txt b/tests/requirements_for_software_tests.txt index 4cbc1651..4e1de699 100644 --- a/tests/requirements_for_software_tests.txt +++ b/tests/requirements_for_software_tests.txt @@ -1,4 +1,4 @@ -pytest>=7.0.0 -pytest-cov>=3.0.0 +pytest==7.* +pytest-cov==3.* pytest-asyncio>=0.17.2 -mock>=4.0.0 \ No newline at end of file +mock==4.* \ No newline at end of file From 73f8787c7d687543a83bfd5f13d273ed6df089f9 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 17:24:23 +0200 Subject: [PATCH 34/40] Update reffering to script location Update of use of 'SCRIPT_LOCATION' variable in every test script. --- .../test_abstract_addressing_information.py | 7 +-- .../can/test_addressing_information.py | 23 ++++---- .../can/test_consecutive_frame.py | 21 ++++---- .../test_extended_addressing_information.py | 11 ++-- tests/software_tests/can/test_first_frame.py | 19 +++---- tests/software_tests/can/test_flow_control.py | 29 +++++----- tests/software_tests/can/test_frame_fields.py | 13 +++-- .../can/test_mixed_addressing_information.py | 19 ++++--- .../can/test_normal_addressing_information.py | 19 ++++--- tests/software_tests/can/test_single_frame.py | 23 ++++---- .../message/test_service_identifiers.py | 19 ++++--- .../message/test_uds_message.py | 15 +++--- .../test_abstract_can_packet_container.py | 19 +++---- .../packet/test_abstract_packet.py | 7 +-- .../packet/test_abstract_packet_type.py | 5 +- .../software_tests/packet/test_can_packet.py | 53 +++++++++---------- .../packet/test_can_packet_record.py | 19 +++---- .../segmentation/test_abstract_segmenter.py | 5 +- .../utilities/test_bytes_operations.py | 9 ++-- tests/software_tests/utilities/test_enums.py | 7 ++- 20 files changed, 173 insertions(+), 169 deletions(-) diff --git a/tests/software_tests/can/test_abstract_addressing_information.py b/tests/software_tests/can/test_abstract_addressing_information.py index 2f8a42bb..9c830578 100644 --- a/tests/software_tests/can/test_abstract_addressing_information.py +++ b/tests/software_tests/can/test_abstract_addressing_information.py @@ -4,11 +4,12 @@ from uds.can.abstract_addressing_information import AbstractCanAddressingInformation, AddressingType +SCRIPT_LOCATION = "uds.can.abstract_addressing_information" + + class TestAbstractCanAddressingInformation: """Unit tests for `AbstractCanAddressingInformation` class.""" - SCRIPT_LOCATION = "uds.can.abstract_addressing_information" - def setup_method(self): self.mock_addressing_information = Mock(spec=AbstractCanAddressingInformation, ADDRESSING_FORMAT_NAME="addressing_format", @@ -17,7 +18,7 @@ def setup_method(self): SOURCE_ADDRESS_NAME="source_address", ADDRESS_EXTENSION_NAME="address_extension") # patching - self._patcher_deepcopy = patch(f"{self.SCRIPT_LOCATION}.deepcopy") + self._patcher_deepcopy = patch(f"{SCRIPT_LOCATION}.deepcopy") self.mock_deepcopy = self._patcher_deepcopy.start() def teardown_method(self): diff --git a/tests/software_tests/can/test_addressing_information.py b/tests/software_tests/can/test_addressing_information.py index 15583a2f..ef230bb2 100644 --- a/tests/software_tests/can/test_addressing_information.py +++ b/tests/software_tests/can/test_addressing_information.py @@ -6,30 +6,31 @@ from uds.transmission_attributes import AddressingType +SCRIPT_LOCATION = "uds.can.addressing_information" + + class TestCanAddressingInformation: """Unit tests for `CanAddressingInformation` class.""" - SCRIPT_LOCATION = "uds.can.addressing_information" - def setup_method(self): # patching - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - self._patcher_validate_addressing_format = patch(f"{self.SCRIPT_LOCATION}.CanAddressingFormat.validate_member") + self._patcher_validate_addressing_format = patch(f"{SCRIPT_LOCATION}.CanAddressingFormat.validate_member") self.mock_validate_addressing_format = self._patcher_validate_addressing_format.start() - self._patcher_normal_11bit_ai_class = patch(f"{self.SCRIPT_LOCATION}.Normal11BitCanAddressingInformation") + self._patcher_normal_11bit_ai_class = patch(f"{SCRIPT_LOCATION}.Normal11BitCanAddressingInformation") self.mock_normal_11bit_ai_class = self._patcher_normal_11bit_ai_class.start() - self._patcher_normal_fixed_ai_class = patch(f"{self.SCRIPT_LOCATION}.NormalFixedCanAddressingInformation") + self._patcher_normal_fixed_ai_class = patch(f"{SCRIPT_LOCATION}.NormalFixedCanAddressingInformation") self.mock_normal_fixed_ai_class = self._patcher_normal_fixed_ai_class.start() - self._patcher_extended_ai_class = patch(f"{self.SCRIPT_LOCATION}.ExtendedCanAddressingInformation") + self._patcher_extended_ai_class = patch(f"{SCRIPT_LOCATION}.ExtendedCanAddressingInformation") self.mock_extended_ai_class = self._patcher_extended_ai_class.start() - self._patcher_mixed_11bit_ai_class = patch(f"{self.SCRIPT_LOCATION}.Mixed11BitCanAddressingInformation") + self._patcher_mixed_11bit_ai_class = patch(f"{SCRIPT_LOCATION}.Mixed11BitCanAddressingInformation") self.mock_mixed_11bit_ai_class = self._patcher_mixed_11bit_ai_class.start() - self._patcher_mixed_29bit_ai_class = patch(f"{self.SCRIPT_LOCATION}.Mixed29BitCanAddressingInformation") + self._patcher_mixed_29bit_ai_class = patch(f"{SCRIPT_LOCATION}.Mixed29BitCanAddressingInformation") self.mock_mixed_29bit_ai_class = self._patcher_mixed_29bit_ai_class.start() - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() def teardown_method(self): diff --git a/tests/software_tests/can/test_consecutive_frame.py b/tests/software_tests/can/test_consecutive_frame.py index 4a3e26b0..47513886 100644 --- a/tests/software_tests/can/test_consecutive_frame.py +++ b/tests/software_tests/can/test_consecutive_frame.py @@ -6,29 +6,30 @@ from uds.can import CanAddressingFormat +SCRIPT_LOCATION = "uds.can.consecutive_frame" + + class TestCanConsecutiveFrameHandler: """Unit tests for `CanConsecutiveFrameHandler` class.""" - SCRIPT_LOCATION = "uds.can.consecutive_frame" - def setup_method(self): - self._patcher_validate_nibble = patch(f"{self.SCRIPT_LOCATION}.validate_nibble") + self._patcher_validate_nibble = patch(f"{SCRIPT_LOCATION}.validate_nibble") self.mock_validate_nibble = self._patcher_validate_nibble.start() - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_encode_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.encode_dlc") + self._patcher_encode_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.encode_dlc") self.mock_encode_dlc = self._patcher_encode_dlc.start() - self._patcher_decode_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.decode_dlc") + self._patcher_decode_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.decode_dlc") self.mock_decode_dlc = self._patcher_decode_dlc.start() - self._patcher_get_min_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.get_min_dlc") + self._patcher_get_min_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.get_min_dlc") self.mock_get_min_dlc = self._patcher_get_min_dlc.start() self._patcher_encode_ai_data_bytes = \ - patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.encode_ai_data_bytes") + patch(f"{SCRIPT_LOCATION}.CanAddressingInformation.encode_ai_data_bytes") self.mock_encode_ai_data_bytes = self._patcher_encode_ai_data_bytes.start() self._patcher_get_ai_data_bytes_number = \ - patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") + patch(f"{SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") self.mock_get_ai_data_bytes_number = self._patcher_get_ai_data_bytes_number.start() def teardown_method(self): diff --git a/tests/software_tests/can/test_extended_addressing_information.py b/tests/software_tests/can/test_extended_addressing_information.py index d8c736f1..13bf99d9 100644 --- a/tests/software_tests/can/test_extended_addressing_information.py +++ b/tests/software_tests/can/test_extended_addressing_information.py @@ -5,19 +5,20 @@ CanAddressingFormat, InconsistentArgumentsError, UnusedArgumentError, AbstractCanAddressingInformation +SCRIPT_LOCATION = "uds.can.extended_addressing_information" + + class TestExtendedCanAddressingInformation: """Unit tests for `ExtendedCanAddressingInformation` class.""" - SCRIPT_LOCATION = "uds.can.extended_addressing_information" - def setup_method(self): self.mock_addressing_information = Mock(spec=ExtendedCanAddressingInformation) # patching - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_addressing_type = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") + self._patcher_validate_addressing_type = patch(f"{SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing_type = self._patcher_validate_addressing_type.start() - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() def teardown_method(self): diff --git a/tests/software_tests/can/test_first_frame.py b/tests/software_tests/can/test_first_frame.py index 0ac611ab..f88d8221 100644 --- a/tests/software_tests/can/test_first_frame.py +++ b/tests/software_tests/can/test_first_frame.py @@ -6,25 +6,26 @@ from uds.can import CanAddressingFormat +SCRIPT_LOCATION = "uds.can.first_frame" + + class TestCanFirstFrameHandler: """Unit tests for `CanFirstFrameHandler` class.""" - SCRIPT_LOCATION = "uds.can.first_frame" - def setup_method(self): - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_bytes_list_to_int = patch(f"{self.SCRIPT_LOCATION}.bytes_list_to_int") + self._patcher_bytes_list_to_int = patch(f"{SCRIPT_LOCATION}.bytes_list_to_int") self.mock_bytes_list_to_int = self._patcher_bytes_list_to_int.start() - self._patcher_get_ai_data_bytes_number = patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") + self._patcher_get_ai_data_bytes_number = patch(f"{SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") self.mock_get_ai_data_bytes_number = self._patcher_get_ai_data_bytes_number.start() - self._patcher_encode_ai_data_bytes = patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.encode_ai_data_bytes") + self._patcher_encode_ai_data_bytes = patch(f"{SCRIPT_LOCATION}.CanAddressingInformation.encode_ai_data_bytes") self.mock_encode_ai_data_bytes = self._patcher_encode_ai_data_bytes.start() - self._patcher_decode_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.decode_dlc") + self._patcher_decode_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.decode_dlc") self.mock_decode_dlc = self._patcher_decode_dlc.start() - self._patcher_encode_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.encode_dlc") + self._patcher_encode_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.encode_dlc") self.mock_encode_dlc = self._patcher_encode_dlc.start() - self._patcher_get_max_sf_dl = patch(f"{self.SCRIPT_LOCATION}.CanSingleFrameHandler.get_max_payload_size") + self._patcher_get_max_sf_dl = patch(f"{SCRIPT_LOCATION}.CanSingleFrameHandler.get_max_payload_size") self.mock_get_max_sf_dl = self._patcher_get_max_sf_dl.start() def teardown_method(self): diff --git a/tests/software_tests/can/test_flow_control.py b/tests/software_tests/can/test_flow_control.py index 3a6cb3b3..f827aeeb 100644 --- a/tests/software_tests/can/test_flow_control.py +++ b/tests/software_tests/can/test_flow_control.py @@ -7,6 +7,9 @@ from uds.utilities import ValidatedEnum, NibbleEnum +SCRIPT_LOCATION = "uds.can.flow_control" + + class TestCanFlowStatus: """Unit tests for 'CanFlowStatus' class.""" @@ -20,12 +23,10 @@ def test_inheritance__nibble_enum(self): class TestCanSTmin: """Unit tests for 'CanSTmin' class.""" - SCRIPT_LOCATION = "uds.can.flow_control" - def setup_method(self): - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") + self._patcher_warn = patch(f"{SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() def teardown_method(self): @@ -135,28 +136,26 @@ def test_encode_and_decode(self, time_value): class TestCanFlowControlHandler: """Unit tests for `CanFlowControlHandler` class.""" - SCRIPT_LOCATION = TestCanSTmin.SCRIPT_LOCATION - def setup_method(self): - self._patcher_validate_nibble = patch(f"{self.SCRIPT_LOCATION}.validate_nibble") + self._patcher_validate_nibble = patch(f"{SCRIPT_LOCATION}.validate_nibble") self.mock_validate_nibble = self._patcher_validate_nibble.start() - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_encode_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.encode_dlc") + self._patcher_encode_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.encode_dlc") self.mock_encode_dlc = self._patcher_encode_dlc.start() - self._patcher_decode_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.decode_dlc") + self._patcher_decode_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.decode_dlc") self.mock_decode_dlc = self._patcher_decode_dlc.start() - self._patcher_get_min_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.get_min_dlc") + self._patcher_get_min_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.get_min_dlc") self.mock_get_min_dlc = self._patcher_get_min_dlc.start() self._patcher_encode_ai_data_bytes = \ - patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.encode_ai_data_bytes") + patch(f"{SCRIPT_LOCATION}.CanAddressingInformation.encode_ai_data_bytes") self.mock_encode_ai_data_bytes = self._patcher_encode_ai_data_bytes.start() self._patcher_get_ai_data_bytes_number = \ - patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") + patch(f"{SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") self.mock_get_ai_data_bytes_number = self._patcher_get_ai_data_bytes_number.start() - self._patcher_validate_flow_status = patch(f"{self.SCRIPT_LOCATION}.CanFlowStatus.validate_member") + self._patcher_validate_flow_status = patch(f"{SCRIPT_LOCATION}.CanFlowStatus.validate_member") self.mock_validate_flow_status = self._patcher_validate_flow_status.start() def teardown_method(self): diff --git a/tests/software_tests/can/test_frame_fields.py b/tests/software_tests/can/test_frame_fields.py index bafe1284..00acc30b 100644 --- a/tests/software_tests/can/test_frame_fields.py +++ b/tests/software_tests/can/test_frame_fields.py @@ -5,17 +5,18 @@ AddressingType, CanAddressingFormat +SCRIPT_LOCATION = "uds.can.frame_fields" + + class TestCanIdHandler: """Unit tests for `CanIdHandler` class.""" - SCRIPT_LOCATION = "uds.can.frame_fields" - def setup_method(self): - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_addressing_format = patch(f"{self.SCRIPT_LOCATION}.CanAddressingFormat.validate_member") + self._patcher_validate_addressing_format = patch(f"{SCRIPT_LOCATION}.CanAddressingFormat.validate_member") self.mock_validate_addressing_format = self._patcher_validate_addressing_format.start() - self._patcher_validate_addressing_type = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") + self._patcher_validate_addressing_type = patch(f"{SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing_type = self._patcher_validate_addressing_type.start() def teardown_method(self): @@ -499,8 +500,6 @@ def test_validate_can_id__valid(self, mock_is_can_id, mock_is_standard_can_id, m class TestCanDlcHandler: """Unit tests for `CanDlcHandler` class.""" - SCRIPT_LOCATION = TestCanIdHandler.SCRIPT_LOCATION - # decode_dlc @pytest.mark.parametrize("dlc, data_bytes_number", [ diff --git a/tests/software_tests/can/test_mixed_addressing_information.py b/tests/software_tests/can/test_mixed_addressing_information.py index 6e14fe29..59e42501 100644 --- a/tests/software_tests/can/test_mixed_addressing_information.py +++ b/tests/software_tests/can/test_mixed_addressing_information.py @@ -5,19 +5,20 @@ CanAddressingFormat, InconsistentArgumentsError, UnusedArgumentError, AbstractCanAddressingInformation +SCRIPT_LOCATION = "uds.can.mixed_addressing_information" + + class TestMixed11BitCanAddressingInformation: """Unit tests for `Mixed11BitCanAddressingInformation` class.""" - SCRIPT_LOCATION = "uds.can.mixed_addressing_information" - def setup_method(self): self.mock_addressing_information = Mock(spec=Mixed11BitCanAddressingInformation) # patching - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_addressing_type = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") + self._patcher_validate_addressing_type = patch(f"{SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing_type = self._patcher_validate_addressing_type.start() - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() def teardown_method(self): @@ -85,16 +86,14 @@ def test_validate_ai_mixed_11bit__valid(self, addressing_type, can_id, address_e class TestMixed29BitCanAddressingInformation: """Unit tests for `Mixed29BitCanAddressingInformation` class.""" - SCRIPT_LOCATION = TestMixed11BitCanAddressingInformation.SCRIPT_LOCATION - def setup_method(self): self.mock_addressing_information = Mock(spec=Mixed29BitCanAddressingInformation) # patching - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_addressing_type = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") + self._patcher_validate_addressing_type = patch(f"{SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing_type = self._patcher_validate_addressing_type.start() - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() def teardown_method(self): diff --git a/tests/software_tests/can/test_normal_addressing_information.py b/tests/software_tests/can/test_normal_addressing_information.py index 53deb426..037eba90 100644 --- a/tests/software_tests/can/test_normal_addressing_information.py +++ b/tests/software_tests/can/test_normal_addressing_information.py @@ -5,19 +5,20 @@ CanAddressingFormat, InconsistentArgumentsError, UnusedArgumentError, AbstractCanAddressingInformation +SCRIPT_LOCATION = "uds.can.normal_addressing_information" + + class TestNormal11BitCanAddressingInformation: """Unit tests for `Normal11BitCanAddressingInformation` class.""" - SCRIPT_LOCATION = "uds.can.normal_addressing_information" - def setup_method(self): self.mock_addressing_information = Mock(spec=Normal11BitCanAddressingInformation) # patching - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_addressing_type = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") + self._patcher_validate_addressing_type = patch(f"{SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing_type = self._patcher_validate_addressing_type.start() - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() def teardown_method(self): @@ -79,16 +80,14 @@ def test_validate_packet_ai__valid(self, addressing_type, can_id): class TestNormalFixedCanAddressingInformation: """Unit tests for `NormalFixedCanAddressingInformation` class.""" - SCRIPT_LOCATION = TestNormal11BitCanAddressingInformation.SCRIPT_LOCATION - def setup_method(self): self.mock_addressing_information = Mock(spec=NormalFixedCanAddressingInformation) # patching - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_addressing_type = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") + self._patcher_validate_addressing_type = patch(f"{SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing_type = self._patcher_validate_addressing_type.start() - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() def teardown_method(self): diff --git a/tests/software_tests/can/test_single_frame.py b/tests/software_tests/can/test_single_frame.py index eafbac5b..805f1a77 100644 --- a/tests/software_tests/can/test_single_frame.py +++ b/tests/software_tests/can/test_single_frame.py @@ -6,31 +6,32 @@ from uds.can import CanAddressingFormat +SCRIPT_LOCATION = "uds.can.single_frame" + + class TestCanSingleFrameHandler: """Unit tests for `CanSingleFrameHandler` class.""" - SCRIPT_LOCATION = "uds.can.single_frame" - def setup_method(self): - self._patcher_validate_nibble = patch(f"{self.SCRIPT_LOCATION}.validate_nibble") + self._patcher_validate_nibble = patch(f"{SCRIPT_LOCATION}.validate_nibble") self.mock_validate_nibble = self._patcher_validate_nibble.start() - self._patcher_validate_raw_byte = patch(f"{self.SCRIPT_LOCATION}.validate_raw_byte") + self._patcher_validate_raw_byte = patch(f"{SCRIPT_LOCATION}.validate_raw_byte") self.mock_validate_raw_byte = self._patcher_validate_raw_byte.start() - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_encode_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.encode_dlc") + self._patcher_encode_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.encode_dlc") self.mock_encode_dlc = self._patcher_encode_dlc.start() - self._patcher_decode_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.decode_dlc") + self._patcher_decode_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.decode_dlc") self.mock_decode_dlc = self._patcher_decode_dlc.start() - self._patcher_get_min_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.get_min_dlc") + self._patcher_get_min_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.get_min_dlc") self.mock_get_min_dlc = self._patcher_get_min_dlc.start() - self._patcher_validate_dlc = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler.validate_dlc") + self._patcher_validate_dlc = patch(f"{SCRIPT_LOCATION}.CanDlcHandler.validate_dlc") self.mock_validate_dlc = self._patcher_validate_dlc.start() self._patcher_encode_ai_data_bytes = \ - patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.encode_ai_data_bytes") + patch(f"{SCRIPT_LOCATION}.CanAddressingInformation.encode_ai_data_bytes") self.mock_encode_ai_data_bytes = self._patcher_encode_ai_data_bytes.start() self._patcher_get_ai_data_bytes_number = \ - patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") + patch(f"{SCRIPT_LOCATION}.CanAddressingInformation.get_ai_data_bytes_number") self.mock_get_ai_data_bytes_number = self._patcher_get_ai_data_bytes_number.start() def teardown_method(self): diff --git a/tests/software_tests/message/test_service_identifiers.py b/tests/software_tests/message/test_service_identifiers.py index 9a8e91b5..9d1d0e3c 100644 --- a/tests/software_tests/message/test_service_identifiers.py +++ b/tests/software_tests/message/test_service_identifiers.py @@ -5,17 +5,18 @@ ByteEnum, ValidatedEnum, ExtendableEnum +SCRIPT_LOCATION = "uds.message.service_identifiers" + + class TestRequestSID: """Unit tests for 'RequestSID' enum.""" - SCRIPT_LOCATION = "uds.message.service_identifiers" - def setup_method(self): - self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") + self._patcher_warn = patch(f"{SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() - self._patcher_is_member = patch(f"{self.SCRIPT_LOCATION}.RequestSID.is_member") + self._patcher_is_member = patch(f"{SCRIPT_LOCATION}.RequestSID.is_member") self.mock_is_member = self._patcher_is_member.start() - self._patcher_possible_request_sids = patch(f"{self.SCRIPT_LOCATION}.POSSIBLE_REQUEST_SIDS") + self._patcher_possible_request_sids = patch(f"{SCRIPT_LOCATION}.POSSIBLE_REQUEST_SIDS") self.mock_possible_request_sids = self._patcher_possible_request_sids.start() def teardown_method(self): @@ -63,14 +64,12 @@ def test_is_request_sid__invalid(self, value): class TestResponseSID: """Unit tests for 'ResponseSID' enum.""" - SCRIPT_LOCATION = TestRequestSID.SCRIPT_LOCATION - def setup_method(self): - self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") + self._patcher_warn = patch(f"{SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() - self._patcher_is_member = patch(f"{self.SCRIPT_LOCATION}.ResponseSID.is_member") + self._patcher_is_member = patch(f"{SCRIPT_LOCATION}.ResponseSID.is_member") self.mock_is_member = self._patcher_is_member.start() - self._patcher_possible_response_sids = patch(f"{self.SCRIPT_LOCATION}.POSSIBLE_RESPONSE_SIDS") + self._patcher_possible_response_sids = patch(f"{SCRIPT_LOCATION}.POSSIBLE_RESPONSE_SIDS") self.mock_possible_response_sids = self._patcher_possible_response_sids.start() def teardown_method(self): diff --git a/tests/software_tests/message/test_uds_message.py b/tests/software_tests/message/test_uds_message.py index 7f41f78d..6d3602e0 100644 --- a/tests/software_tests/message/test_uds_message.py +++ b/tests/software_tests/message/test_uds_message.py @@ -6,17 +6,18 @@ from uds.transmission_attributes import TransmissionDirection +SCRIPT_LOCATION = "uds.message.uds_message" + + class TestUdsMessage: """Unit tests for 'UdsMessage' class.""" - SCRIPT_LOCATION = "uds.message.uds_message" - def setup_method(self): self.mock_uds_message = Mock(spec=UdsMessage) # patching - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_validate_addressing = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") + self._patcher_validate_addressing = patch(f"{SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing = self._patcher_validate_addressing.start() def teardown_method(self): @@ -88,14 +89,12 @@ def test_addressing_type__set_second_call(self, example_addressing_type): class TestUdsMessageRecord: """Unit tests for 'UdsMessageRecord' class.""" - SCRIPT_LOCATION = TestUdsMessage.SCRIPT_LOCATION - def setup_method(self): self.mock_uds_message_record = Mock(spec=UdsMessageRecord) # patching - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_validate_addressing = patch(f"{self.SCRIPT_LOCATION}.AddressingType.validate_member") + self._patcher_validate_addressing = patch(f"{SCRIPT_LOCATION}.AddressingType.validate_member") self.mock_validate_addressing = self._patcher_validate_addressing.start() def teardown_method(self): diff --git a/tests/software_tests/packet/test_abstract_can_packet_container.py b/tests/software_tests/packet/test_abstract_can_packet_container.py index 3420b72e..f04cd5f8 100644 --- a/tests/software_tests/packet/test_abstract_can_packet_container.py +++ b/tests/software_tests/packet/test_abstract_can_packet_container.py @@ -6,11 +6,12 @@ from uds.transmission_attributes import AddressingType +SCRIPT_LOCATION = "uds.packet.abstract_can_packet_container" + + class TestAbstractCanPacketContainer: """Unit tests for 'AbstractCanPacketContainer' class.""" - SCRIPT_LOCATION = "uds.packet.abstract_can_packet_container" - def setup_method(self): self.mock_can_packet_container = Mock(spec=AbstractCanPacketContainer) mock_can_packet_type_class = Mock(SINGLE_FRAME=CanPacketType.SINGLE_FRAME, @@ -18,19 +19,19 @@ def setup_method(self): CONSECUTIVE_FRAME=CanPacketType.CONSECUTIVE_FRAME, FLOW_CONTROL=CanPacketType.FLOW_CONTROL) # patching - self._patcher_ai_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation") + self._patcher_ai_handler_class = patch(f"{SCRIPT_LOCATION}.CanAddressingInformation") self.mock_ai_handler_class = self._patcher_ai_handler_class.start() - self._patcher_can_dlc_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler") + self._patcher_can_dlc_handler_class = patch(f"{SCRIPT_LOCATION}.CanDlcHandler") self.mock_can_dlc_handler_class = self._patcher_can_dlc_handler_class.start() - self._patcher_single_frame_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanSingleFrameHandler") + self._patcher_single_frame_handler_class = patch(f"{SCRIPT_LOCATION}.CanSingleFrameHandler") self.mock_single_frame_handler_class = self._patcher_single_frame_handler_class.start() - self._patcher_first_frame_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanFirstFrameHandler") + self._patcher_first_frame_handler_class = patch(f"{SCRIPT_LOCATION}.CanFirstFrameHandler") self.mock_first_frame_handler_class = self._patcher_first_frame_handler_class.start() - self._patcher_consecutive_frame_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanConsecutiveFrameHandler") + self._patcher_consecutive_frame_handler_class = patch(f"{SCRIPT_LOCATION}.CanConsecutiveFrameHandler") self.mock_consecutive_frame_handler_class = self._patcher_consecutive_frame_handler_class.start() - self._patcher_flow_control_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanFlowControlHandler") + self._patcher_flow_control_handler_class = patch(f"{SCRIPT_LOCATION}.CanFlowControlHandler") self.mock_flow_control_handler_class = self._patcher_flow_control_handler_class.start() - self._patcher_can_packet_type_class = patch(f"{self.SCRIPT_LOCATION}.CanPacketType", mock_can_packet_type_class) + self._patcher_can_packet_type_class = patch(f"{SCRIPT_LOCATION}.CanPacketType", mock_can_packet_type_class) self.mock_can_packet_type_class = self._patcher_can_packet_type_class.start() def teardown_method(self): diff --git a/tests/software_tests/packet/test_abstract_packet.py b/tests/software_tests/packet/test_abstract_packet.py index 51770416..edfcb7c4 100644 --- a/tests/software_tests/packet/test_abstract_packet.py +++ b/tests/software_tests/packet/test_abstract_packet.py @@ -5,15 +5,16 @@ TransmissionDirection, ReassignmentError, datetime +SCRIPT_LOCATION = "uds.packet.abstract_packet" + + class TestAbstractUdsPacketRecord: """Unit tests for 'AbstractUdsPacketRecord' class.""" - SCRIPT_LOCATION = "uds.packet.abstract_packet" - def setup_method(self): self.mock_packet_record = Mock(spec=AbstractUdsPacketRecord) # patching - self._patcher_validate_direction = patch(f"{self.SCRIPT_LOCATION}.TransmissionDirection.validate_member") + self._patcher_validate_direction = patch(f"{SCRIPT_LOCATION}.TransmissionDirection.validate_member") self.mock_validate_direction = self._patcher_validate_direction.start() def teardown_method(self): diff --git a/tests/software_tests/packet/test_abstract_packet_type.py b/tests/software_tests/packet/test_abstract_packet_type.py index 71d538dc..cb3681d2 100644 --- a/tests/software_tests/packet/test_abstract_packet_type.py +++ b/tests/software_tests/packet/test_abstract_packet_type.py @@ -2,11 +2,12 @@ from uds.utilities import NibbleEnum, ValidatedEnum, ExtendableEnum +SCRIPT_LOCATION = "uds.packet.abstract_packet" + + class TestAbstractPacketType: """Unit tests for 'AbstractPacketType' class.""" - SCRIPT_LOCATION = "uds.packet.abstract_packet" - def test_inheritance__nibble_enum(self): assert issubclass(AbstractUdsPacketType, NibbleEnum) diff --git a/tests/software_tests/packet/test_can_packet.py b/tests/software_tests/packet/test_can_packet.py index 1f8508f7..22c8b046 100644 --- a/tests/software_tests/packet/test_can_packet.py +++ b/tests/software_tests/packet/test_can_packet.py @@ -7,11 +7,12 @@ from uds.can import CanFlowStatus +SCRIPT_LOCATION = "uds.packet.can_packet" + + class TestCanPacket: """Unit tests for 'CanPacket' class.""" - SCRIPT_LOCATION = "uds.packet.can_packet" - def setup_method(self): self.mock_can_packet = Mock(spec=CanPacket) mock_can_id_handler_class = Mock(spec=CanIdHandler, @@ -19,37 +20,37 @@ def setup_method(self): TARGET_ADDRESS_NAME=CanIdHandler.TARGET_ADDRESS_NAME, SOURCE_ADDRESS_NAME=CanIdHandler.SOURCE_ADDRESS_NAME) # patching - self._patcher_warn = patch(f"{self.SCRIPT_LOCATION}.warn") + self._patcher_warn = patch(f"{SCRIPT_LOCATION}.warn") self.mock_warn = self._patcher_warn.start() - self._patcher_can_dlc_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler") + self._patcher_can_dlc_handler_class = patch(f"{SCRIPT_LOCATION}.CanDlcHandler") self.mock_can_dlc_handler_class = self._patcher_can_dlc_handler_class.start() - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler", mock_can_id_handler_class) + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler", mock_can_id_handler_class) self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - self._patcher_ai_class = patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation") + self._patcher_ai_class = patch(f"{SCRIPT_LOCATION}.CanAddressingInformation") self.mock_ai_class = self._patcher_ai_class.start() - self._patcher_normal_11bit_ai_class = patch(f"{self.SCRIPT_LOCATION}.Normal11BitCanAddressingInformation") + self._patcher_normal_11bit_ai_class = patch(f"{SCRIPT_LOCATION}.Normal11BitCanAddressingInformation") self.mock_normal_11bit_ai_class = self._patcher_normal_11bit_ai_class.start() - self._patcher_normal_fixed_ai_class = patch(f"{self.SCRIPT_LOCATION}.NormalFixedCanAddressingInformation") + self._patcher_normal_fixed_ai_class = patch(f"{SCRIPT_LOCATION}.NormalFixedCanAddressingInformation") self.mock_normal_fixed_ai_class = self._patcher_normal_fixed_ai_class.start() - self._patcher_extended_ai_class = patch(f"{self.SCRIPT_LOCATION}.ExtendedCanAddressingInformation") + self._patcher_extended_ai_class = patch(f"{SCRIPT_LOCATION}.ExtendedCanAddressingInformation") self.mock_extended_ai_class = self._patcher_extended_ai_class.start() - self._patcher_mixed_11bit_ai_class = patch(f"{self.SCRIPT_LOCATION}.Mixed11BitCanAddressingInformation") + self._patcher_mixed_11bit_ai_class = patch(f"{SCRIPT_LOCATION}.Mixed11BitCanAddressingInformation") self.mock_mixed_11bit_ai_class = self._patcher_mixed_11bit_ai_class.start() - self._patcher_mixed_29bit_ai_class = patch(f"{self.SCRIPT_LOCATION}.Mixed29BitCanAddressingInformation") + self._patcher_mixed_29bit_ai_class = patch(f"{SCRIPT_LOCATION}.Mixed29BitCanAddressingInformation") self.mock_mixed_29bit_ai_class = self._patcher_mixed_29bit_ai_class.start() - self._patcher_single_frame_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanSingleFrameHandler") + self._patcher_single_frame_handler_class = patch(f"{SCRIPT_LOCATION}.CanSingleFrameHandler") self.mock_single_frame_handler_class = self._patcher_single_frame_handler_class.start() - self._patcher_first_frame_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanFirstFrameHandler") + self._patcher_first_frame_handler_class = patch(f"{SCRIPT_LOCATION}.CanFirstFrameHandler") self.mock_first_frame_handler_class = self._patcher_first_frame_handler_class.start() - self._patcher_consecutive_frame_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanConsecutiveFrameHandler") + self._patcher_consecutive_frame_handler_class = patch(f"{SCRIPT_LOCATION}.CanConsecutiveFrameHandler") self.mock_consecutive_frame_handler_class = self._patcher_consecutive_frame_handler_class.start() - self._patcher_flow_control_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanFlowControlHandler") + self._patcher_flow_control_handler_class = patch(f"{SCRIPT_LOCATION}.CanFlowControlHandler") self.mock_flow_control_handler_class = self._patcher_flow_control_handler_class.start() - self._patcher_addressing_type_class = patch(f"{self.SCRIPT_LOCATION}.AddressingType") + self._patcher_addressing_type_class = patch(f"{SCRIPT_LOCATION}.AddressingType") self.mock_addressing_type_class = self._patcher_addressing_type_class.start() - self._patcher_validate_addressing_format = patch(f"{self.SCRIPT_LOCATION}.CanAddressingFormat.validate_member") + self._patcher_validate_addressing_format = patch(f"{SCRIPT_LOCATION}.CanAddressingFormat.validate_member") self.mock_validate_addressing_format = self._patcher_validate_addressing_format.start() - self._patcher_validate_packet_type = patch(f"{self.SCRIPT_LOCATION}.CanPacketType.validate_member") + self._patcher_validate_packet_type = patch(f"{SCRIPT_LOCATION}.CanPacketType.validate_member") self.mock_validate_packet_type = self._patcher_validate_packet_type.start() def teardown_method(self): @@ -812,24 +813,22 @@ def test_update_ai_data_byte(self, addressing_format, raw_frame_data, ai_data_by class TestAnyCanPacket: """Unit tests for `AnyCanPacket` class.""" - SCRIPT_LOCATION = TestCanPacket.SCRIPT_LOCATION - def setup_method(self): self.mock_any_can_packet = Mock(spec=AnyCanPacket) # patching - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_addressing_type_class = patch(f"{self.SCRIPT_LOCATION}.AddressingType") + self._patcher_addressing_type_class = patch(f"{SCRIPT_LOCATION}.AddressingType") self.mock_addressing_type_class = self._patcher_addressing_type_class.start() - self._patcher_addressing_format_class = patch(f"{self.SCRIPT_LOCATION}.CanAddressingFormat") + self._patcher_addressing_format_class = patch(f"{SCRIPT_LOCATION}.CanAddressingFormat") self.mock_addressing_format_class = self._patcher_addressing_format_class.start() - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - self._patcher_can_dlc_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler") + self._patcher_can_dlc_handler_class = patch(f"{SCRIPT_LOCATION}.CanDlcHandler") self.mock_can_dlc_handler_class = self._patcher_can_dlc_handler_class.start() - self._patcher_ai_class = patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation") + self._patcher_ai_class = patch(f"{SCRIPT_LOCATION}.CanAddressingInformation") self.mock_ai_class = self._patcher_ai_class.start() - self._patcher_can_packet_class = patch(f"{self.SCRIPT_LOCATION}.CanPacket") + self._patcher_can_packet_class = patch(f"{SCRIPT_LOCATION}.CanPacket") self.mock_can_packet_class = self._patcher_can_packet_class.start() def teardown_method(self): diff --git a/tests/software_tests/packet/test_can_packet_record.py b/tests/software_tests/packet/test_can_packet_record.py index 44edd209..9b5814dc 100644 --- a/tests/software_tests/packet/test_can_packet_record.py +++ b/tests/software_tests/packet/test_can_packet_record.py @@ -9,27 +9,28 @@ from uds.can import CanFlowStatus +SCRIPT_LOCATION = "uds.packet.can_packet_record" + + class TestCanPacketRecord: """Unit tests for `CanPacketRecord` class.""" - SCRIPT_LOCATION = "uds.packet.can_packet_record" - def setup_method(self): self.mock_can_packet_record = Mock(spec=CanPacketRecord) # patching - self._patcher_addressing_type_class = patch(f"{self.SCRIPT_LOCATION}.AddressingType") + self._patcher_addressing_type_class = patch(f"{SCRIPT_LOCATION}.AddressingType") self.mock_addressing_type_class = self._patcher_addressing_type_class.start() - self._patcher_can_addressing_format_class = patch(f"{self.SCRIPT_LOCATION}.CanAddressingFormat") + self._patcher_can_addressing_format_class = patch(f"{SCRIPT_LOCATION}.CanAddressingFormat") self.mock_can_addressing_format_class = self._patcher_can_addressing_format_class.start() - self._patcher_can_ai_class = patch(f"{self.SCRIPT_LOCATION}.CanAddressingInformation") + self._patcher_can_ai_class = patch(f"{SCRIPT_LOCATION}.CanAddressingInformation") self.mock_can_ai_class = self._patcher_can_ai_class.start() - self._patcher_can_packet_type_class = patch(f"{self.SCRIPT_LOCATION}.CanPacketType") + self._patcher_can_packet_type_class = patch(f"{SCRIPT_LOCATION}.CanPacketType") self.mock_can_packet_type_class = self._patcher_can_packet_type_class.start() - self._patcher_can_id_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanIdHandler") + self._patcher_can_id_handler_class = patch(f"{SCRIPT_LOCATION}.CanIdHandler") self.mock_can_id_handler_class = self._patcher_can_id_handler_class.start() - self._patcher_can_dlc_handler_class = patch(f"{self.SCRIPT_LOCATION}.CanDlcHandler") + self._patcher_can_dlc_handler_class = patch(f"{SCRIPT_LOCATION}.CanDlcHandler") self.mock_can_dlc_handler_class = self._patcher_can_dlc_handler_class.start() - self._patcher_abstract_uds_packet_record_init = patch(f"{self.SCRIPT_LOCATION}.AbstractUdsPacketRecord.__init__") + self._patcher_abstract_uds_packet_record_init = patch(f"{SCRIPT_LOCATION}.AbstractUdsPacketRecord.__init__") self.mock_abstract_uds_packet_record_init = self._patcher_abstract_uds_packet_record_init.start() def teardown_method(self): diff --git a/tests/software_tests/segmentation/test_abstract_segmenter.py b/tests/software_tests/segmentation/test_abstract_segmenter.py index 06e1b991..1ded96ba 100644 --- a/tests/software_tests/segmentation/test_abstract_segmenter.py +++ b/tests/software_tests/segmentation/test_abstract_segmenter.py @@ -4,11 +4,12 @@ from uds.segmentation.abstract_segmenter import AbstractSegmenter +SCRIPT_PATH = "uds.segmentation.abstract_segmenter" + + class TestAbstractSegmenter: """Unit tests for `AbstractSegmenter` class.""" - SCRIPT_PATH = "uds.segmentation.abstract_segmenter" - def setup_method(self): self.mock_abstract_segmenter = Mock(spec=AbstractSegmenter) diff --git a/tests/software_tests/utilities/test_bytes_operations.py b/tests/software_tests/utilities/test_bytes_operations.py index c1e6060a..05585d76 100644 --- a/tests/software_tests/utilities/test_bytes_operations.py +++ b/tests/software_tests/utilities/test_bytes_operations.py @@ -6,6 +6,9 @@ InconsistentArgumentsError +SCRIPT_LOCATION = "uds.utilities.bytes_operations" + + class TestEndianness: """Unit tests for `Endianness` class.""" @@ -16,12 +19,10 @@ def test_inheritance_validated_enum(self): class TestFunctions: """Unit tests for module functions.""" - SCRIPT_LOCATION = "uds.utilities.bytes_operations" - def setup_method(self): - self._patcher_validate_raw_bytes = patch(f"{self.SCRIPT_LOCATION}.validate_raw_bytes") + self._patcher_validate_raw_bytes = patch(f"{SCRIPT_LOCATION}.validate_raw_bytes") self.mock_validate_raw_bytes = self._patcher_validate_raw_bytes.start() - self._patcher_validate_endianness = patch(f"{self.SCRIPT_LOCATION}.Endianness.validate_member") + self._patcher_validate_endianness = patch(f"{SCRIPT_LOCATION}.Endianness.validate_member") self.mock_validate_endianness = self._patcher_validate_endianness.start() def teardown_method(self): diff --git a/tests/software_tests/utilities/test_enums.py b/tests/software_tests/utilities/test_enums.py index 786f2d83..68d6e1e5 100644 --- a/tests/software_tests/utilities/test_enums.py +++ b/tests/software_tests/utilities/test_enums.py @@ -5,6 +5,9 @@ from uds.utilities.enums import ExtendableEnum, ValidatedEnum, ByteEnum, NibbleEnum +SCRIPT_LOCATION = "uds.utilities.enums" + + class TestByteEnum: """ Tests for `ByteEnum` class. @@ -72,8 +75,6 @@ class TestValidatedEnum: In fact these are integration tests, but we were unable to effectively mock enum and it is not worth the effort. """ - SCRIPT_LOCATION = "uds.utilities.enums" - class ExampleByteEnum1(ValidatedEnum): A = 0 B = 987654321 @@ -182,8 +183,6 @@ def test_add_member__existing_value(self, enum_class, name, value): class TestMultipleEnums: """Integration tests for multiple Enum classes.""" - SCRIPT_LOCATION = "uds.utilities.enums" - class ExtendableByteEnumWithValidated(ByteEnum, ValidatedEnum, ExtendableEnum): V1 = 11 V2 = 0x11 From 241aa3521bcac0495e1f300d5f4091be1d0a6eca Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 18:15:25 +0200 Subject: [PATCH 35/40] formatting minor formatting adjustment for consitency reasons --- uds/can/addressing_information.py | 3 +-- uds/can/extended_addressing_information.py | 3 +-- uds/can/mixed_addressing_information.py | 9 +++------ uds/can/normal_addressing_information.py | 9 +++------ 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/uds/can/addressing_information.py b/uds/can/addressing_information.py index 400e69bb..97b237dd 100644 --- a/uds/can/addressing_information.py +++ b/uds/can/addressing_information.py @@ -92,8 +92,7 @@ def validate_packet_ai(cls, can_id=can_id, target_address=target_address, source_address=source_address, - address_extension=address_extension - ) + address_extension=address_extension) @classmethod def validate_ai_data_bytes(cls, addressing_format: CanAddressingFormatAlias, ai_data_bytes: RawBytes) -> None: diff --git a/uds/can/extended_addressing_information.py b/uds/can/extended_addressing_information.py index 4aa3f9a7..6f70460d 100644 --- a/uds/can/extended_addressing_information.py +++ b/uds/can/extended_addressing_information.py @@ -59,5 +59,4 @@ def validate_packet_ai(cls, can_id=can_id, # type: ignore target_address=target_address, source_address=source_address, - address_extension=address_extension - ) + address_extension=address_extension) diff --git a/uds/can/mixed_addressing_information.py b/uds/can/mixed_addressing_information.py index 21566910..f346c86c 100644 --- a/uds/can/mixed_addressing_information.py +++ b/uds/can/mixed_addressing_information.py @@ -59,8 +59,7 @@ def validate_packet_ai(cls, can_id=can_id, # type: ignore target_address=target_address, source_address=source_address, - address_extension=address_extension - ) + address_extension=address_extension) class Mixed29BitCanAddressingInformation(AbstractCanAddressingInformation): @@ -116,8 +115,7 @@ def validate_packet_ai(cls, can_id=encoded_can_id, target_address=target_address, source_address=source_address, - address_extension=address_extension - ) + address_extension=address_extension) decoded_info = CanIdHandler.decode_mixed_addressed_29bit_can_id(can_id) if addressing_type != decoded_info[CanIdHandler.ADDRESSING_TYPE_NAME]: # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with Addressing Type." @@ -134,5 +132,4 @@ def validate_packet_ai(cls, can_id=can_id, target_address=decoded_info[CanIdHandler.TARGET_ADDRESS_NAME], # type: ignore source_address=decoded_info[CanIdHandler.SOURCE_ADDRESS_NAME], # type: ignore - address_extension=address_extension - ) + address_extension=address_extension) diff --git a/uds/can/normal_addressing_information.py b/uds/can/normal_addressing_information.py index 6176ab70..eb029fd9 100644 --- a/uds/can/normal_addressing_information.py +++ b/uds/can/normal_addressing_information.py @@ -58,8 +58,7 @@ def validate_packet_ai(cls, can_id=can_id, # type: ignore target_address=target_address, source_address=source_address, - address_extension=address_extension - ) + address_extension=address_extension) class NormalFixedCanAddressingInformation(AbstractCanAddressingInformation): @@ -118,8 +117,7 @@ def validate_packet_ai(cls, can_id=encoded_can_id, target_address=target_address, source_address=source_address, - address_extension=address_extension - ) + address_extension=address_extension) decoded_info = CanIdHandler.decode_normal_fixed_addressed_can_id(can_id) if addressing_type != decoded_info[CanIdHandler.ADDRESSING_TYPE_NAME]: # type: ignore raise InconsistentArgumentsError(f"Provided value of CAN ID is not compatible with Addressing Type." @@ -136,5 +134,4 @@ def validate_packet_ai(cls, can_id=can_id, target_address=decoded_info[CanIdHandler.TARGET_ADDRESS_NAME], # type: ignore source_address=decoded_info[CanIdHandler.SOURCE_ADDRESS_NAME], # type: ignore - address_extension=address_extension - ) + address_extension=address_extension) From 037d75a575e0235b57eab95320d459f4e7a0dfec Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 18:18:57 +0200 Subject: [PATCH 36/40] Update conftest.py --- tests/system_tests/transport_interface/conftest.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/system_tests/transport_interface/conftest.py b/tests/system_tests/transport_interface/conftest.py index f6effa2b..80f17063 100644 --- a/tests/system_tests/transport_interface/conftest.py +++ b/tests/system_tests/transport_interface/conftest.py @@ -9,7 +9,7 @@ @fixture def example_addressing_information(): - """Example Addressing Information that can be used in test cases.""" + """Example Addressing Information of a CAN Node.""" return CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x611}, rx_physical={"can_id": 0x612}, @@ -20,10 +20,11 @@ def example_addressing_information(): @fixture def example_addressing_information_2nd_node(): """ - Example Addressing Information that can be used in test cases. + Example Addressing Information of a 2nd CAN Node. .. note:: - It is Addressing Information of a CAN Node on another end to example_addressing_information. + Values of example_addressing_information and example_addressing_information_2nd_node are defined, so + these two CAN nodes can communicate with each other over physical and functional addressing. """ return CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, tx_physical={"can_id": 0x612}, @@ -60,7 +61,7 @@ def example_tx_uds_message(): Example CAN Frame containing a CAN Packet with transmitted addressing information. .. note:: - The same as example_tx_frame. + It is the same message as one carried in example_tx_frame. Compatible with example_addressing_information. """ return UdsMessage(payload=[0x50, 0x03], addressing_type=AddressingType.PHYSICAL) From 83e977f44dcf0a095626ebe632ff9babfcfb3fd1 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 18:19:53 +0200 Subject: [PATCH 37/40] Update conftest.py --- tests/system_tests/transport_interface/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system_tests/transport_interface/conftest.py b/tests/system_tests/transport_interface/conftest.py index 80f17063..07c155f6 100644 --- a/tests/system_tests/transport_interface/conftest.py +++ b/tests/system_tests/transport_interface/conftest.py @@ -23,7 +23,7 @@ def example_addressing_information_2nd_node(): Example Addressing Information of a 2nd CAN Node. .. note:: - Values of example_addressing_information and example_addressing_information_2nd_node are defined, so + Values of example_addressing_information and example_addressing_information_2nd_node are compatible, so these two CAN nodes can communicate with each other over physical and functional addressing. """ return CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, From 3d0bc39e6acecf012a1f098a4780fb789c158eea Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Thu, 19 Oct 2023 18:30:58 +0200 Subject: [PATCH 38/40] Update ci.yml Update actions --- .github/workflows/ci.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e0f9f62..5b1c72d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,10 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -48,10 +48,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python 3.11 - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: 3.11 @@ -75,7 +75,7 @@ jobs: pytest tests/software_tests --cov-report=xml --cov=uds -m "not integration and not performance" - name: Upload unit tests report [CodeCov] - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml @@ -86,7 +86,7 @@ jobs: pytest tests/software_tests --cov-report=xml --cov=uds -m "integration" - name: Upload integration tests report [CodeCov] - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml @@ -98,7 +98,7 @@ jobs: # pytest tests/software_tests --cov-report=xml --cov=uds -m "performance" # # - name: Upload performance tests report [CodeCov] -# uses: codecov/codecov-action@v2 +# uses: codecov/codecov-action@v3 # with: # token: ${{ secrets.CODECOV_TOKEN }} # files: coverage.xml @@ -112,10 +112,10 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -141,10 +141,10 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} From 457425f8ec4c38ce162650cd85d7ca164df4166f Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Fri, 20 Oct 2023 10:27:13 +0200 Subject: [PATCH 39/40] self-review Fix a few things during self-reviewing the code --- .../can/test_addressing_information.py | 1 + .../test_can_transport_interface.py | 29 +++++++++++++++++-- uds/segmentation/can_segmenter.py | 6 ++-- .../abstract_transport_interface.py | 5 ++-- .../can_transport_interface.py | 8 +++-- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/tests/software_tests/can/test_addressing_information.py b/tests/software_tests/can/test_addressing_information.py index ef230bb2..27c48a09 100644 --- a/tests/software_tests/can/test_addressing_information.py +++ b/tests/software_tests/can/test_addressing_information.py @@ -249,6 +249,7 @@ def test_get_ai_data_bytes_number(self, mock_ai_mapping, addressing_format): mock_ai_mapping.__getitem__.assert_called_once_with(addressing_format) +@pytest.mark.integration class TestCanAddressingInformationIntegration: """Integration tests for `CanAddressingInformation` class.""" 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 979e24ba..e6cda8a1 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -969,6 +969,7 @@ async def test_async_receive_packet(self, timeout): transmission_time=self.mock_datetime.fromtimestamp.return_value) +@pytest.mark.integration class TestPyCanTransportInterfaceIntegration: """Integration tests for `PyCanTransportInterface` class.""" @@ -980,13 +981,35 @@ class TestPyCanTransportInterfaceIntegration: rx_physical={"can_id": 0x641}, tx_physical={"can_id": 0x642}, rx_functional={"can_id": 0x6FE}, - tx_functional={"can_id": 0x6FF}, - ), - } + tx_functional={"can_id": 0x6FF}), + }, + { + "can_bus_manager": Mock(spec=BusABC), + "addressing_information": CanAddressingInformation( + addressing_format=CanAddressingFormat.MIXED_29BIT_ADDRESSING, + tx_physical={"target_address": 0x1B, "source_address": 0xFF, "address_extension": 0x87}, + rx_physical={"target_address": 0xFF, "source_address": 0x1B, "address_extension": 0x87}, + tx_functional={"target_address": 0xAC, "source_address": 0xFE, "address_extension": 0xFF}, + rx_functional={"target_address": 0xFE, "source_address": 0xAC, "address_extension": 0xFF}), + "n_as_timeout": 0.1, + "n_ar_timeout": 987, + "n_bs_timeout": 43, + "n_br": 5.3, + "n_cs": 0.92, + "n_cr_timeout": 98.32, + }, ]) def test_init(self, init_kwargs): py_can_ti = PyCanTransportInterface(**init_kwargs) + assert py_can_ti.bus_manager == init_kwargs["can_bus_manager"] + assert py_can_ti.addressing_information == init_kwargs["addressing_information"] assert py_can_ti.n_as_measured is None assert py_can_ti.n_ar_measured is None assert py_can_ti.n_bs_measured is None assert py_can_ti.n_cr_measured is None + assert py_can_ti.n_as_timeout == init_kwargs.get("n_as_timeout", AbstractCanTransportInterface.N_AS_TIMEOUT) + assert py_can_ti.n_ar_timeout == init_kwargs.get("n_ar_timeout", AbstractCanTransportInterface.N_AR_TIMEOUT) + assert py_can_ti.n_bs_timeout == init_kwargs.get("n_bs_timeout", AbstractCanTransportInterface.N_BS_TIMEOUT) + assert py_can_ti.n_br == init_kwargs.get("n_br", AbstractCanTransportInterface.DEFAULT_N_BR) + assert py_can_ti.n_cs == init_kwargs.get("n_cs", AbstractCanTransportInterface.DEFAULT_N_CS) + assert py_can_ti.n_cr_timeout == init_kwargs.get("n_cr_timeout", AbstractCanTransportInterface.N_CR_TIMEOUT) diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py index b854cc2b..ac2b58e4 100644 --- a/uds/segmentation/can_segmenter.py +++ b/uds/segmentation/can_segmenter.py @@ -16,7 +16,7 @@ class CanSegmenter(AbstractSegmenter): - """Segmenter class that provides utilities for segmentation and desegmentation on CAN bus.""" + """Segmenter class that provides utilities for segmentation and desegmentation specific for CAN bus.""" def __init__(self, *, addressing_information: AbstractCanAddressingInformation, @@ -26,7 +26,7 @@ def __init__(self, *, """ Configure CAN Segmenter. - :param addressing_information: Addressing Information configuration of a CAN entity. + :param addressing_information: Addressing Information configuration of a CAN node. :param dlc: Base CAN DLC value to use for creating CAN Packets. :param use_data_optimization: Information whether to use CAN Frame Data Optimization in created CAN Packets during segmentation. @@ -75,7 +75,7 @@ def tx_packets_functional_ai(self) -> PacketAIParamsAlias: @property def addressing_information(self) -> AbstractCanAddressingInformation: - """Addressing Information configuration of a CAN entity.""" + """Addressing Information configuration of a CAN node.""" return self.__addressing_information @addressing_information.setter diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index 488465a1..204ac508 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -90,7 +90,7 @@ async def async_send_packet(self, Transmit UDS packet asynchronously. :param packet: A packet to send. - :param loop: An asyncio event loop used for observing messages. + :param loop: An asyncio event loop to use for scheduling this task. :return: Record with historic information about transmitted UDS packet. """ @@ -103,9 +103,10 @@ async def async_receive_packet(self, Receive UDS packet asynchronously. :param timeout: Maximal time (in milliseconds) to wait. - :param loop: An asyncio event loop used for observing messages. + :param loop: An asyncio event loop to use for scheduling this task. :raise TimeoutError: Timeout was reached. + :raise asyncio.TimeoutError: Timeout was reached. :return: Record with historic information about received UDS packet. """ diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index 06580c0f..38b646a3 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -38,7 +38,7 @@ class AbstractCanTransportInterface(AbstractTransportInterface): DEFAULT_N_BR: TimeMilliseconds = 0 """Default value of :ref:`N_Br ` time parameter.""" DEFAULT_N_CS: Optional[TimeMilliseconds] = None - """Default value of :ref:`N_Cs ` time parameter.""" + """Default value of :ref:`N_Cs ` time parameter.""" def __init__(self, can_bus_manager: Any, @@ -70,9 +70,9 @@ def __init__(self, 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) + self.n_cr_timeout = kwargs.pop("n_cr_timeout", self.N_CR_TIMEOUT) self.n_br = kwargs.pop("n_br", self.DEFAULT_N_BR) self.n_cs = kwargs.pop("n_cs", self.DEFAULT_N_CS) - self.n_cr_timeout = kwargs.pop("n_cr_timeout", self.N_CR_TIMEOUT) self.__segmenter = CanSegmenter(addressing_information=addressing_information, **kwargs) @property @@ -236,6 +236,8 @@ def n_cs(self, value: Optional[TimeMilliseconds]): Set the value of 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 + - int/float type - timing value to be used regardless of a received STmin value :raise TypeError: Provided value is not int or float. :raise ValueError: Provided value is out of range. @@ -453,7 +455,7 @@ def _teardown_notifier(self, suppress_warning: bool = False) -> None: "`PyCanTransportInterface.receive_packet methods`) shall not be used together.", category=UserWarning) - def _teardown_async_notifier(self, suppress_warning: bool = False): + def _teardown_async_notifier(self, suppress_warning: bool = False) -> None: """ Stop and remove CAN frame notifier for asynchronous communication. From 3d5c6c788f9311fff2b7987ed5ad2740db48f265 Mon Sep 17 00:00:00 2001 From: Maciej Dabrowski Date: Fri, 20 Oct 2023 10:32:36 +0200 Subject: [PATCH 40/40] Update test_python_can.py --- .../test_python_can.py | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py b/tests/system_tests/transport_interface/can_transport_interface/test_python_can.py index 15ee98fd..d574271b 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 @@ -13,22 +13,28 @@ class TestPythonCanKvaser: - """System Tests for `PyCanTransportInterface` with Kvaser as bus manager.""" + """ + System Tests for `PyCanTransportInterface` with Kvaser as bus manager. + + Hardware setup: + - two Kvaser interfaces (either https://www.kvaser.com/products-services/our-products/#/?pc_int=usb) + connected with each in the same CAN bus (do not forget about CAN termination) + """ TASK_TIMING_TOLERANCE = 30. # ms def setup_class(self): - self.bus1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) - self.bus2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) + self.can_interface_1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) + self.can_interface_2 = Bus(interface="kvaser", channel=1, fd=True, receive_own_messages=True) def setup_method(self): - self.bus1.flush_tx_buffer() - self.bus2.flush_tx_buffer() + self.can_interface_1.flush_tx_buffer() + self.can_interface_2.flush_tx_buffer() def teardown_class(self): """Safely close bus objects.""" - self.bus1.shutdown() - self.bus2.shutdown() + self.can_interface_1.shutdown() + self.can_interface_2.shutdown() # send_packet @@ -99,7 +105,7 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, target_address = addressing_information.tx_packets_functional_ai["target_address"] source_address = addressing_information.tx_packets_functional_ai["source_address"] address_extension = addressing_information.tx_packets_functional_ai["address_extension"] - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) packet = CanPacket(packet_type=packet_type, addressing_format=addressing_information.addressing_format, @@ -194,9 +200,9 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) - Timer(interval=send_after/1000., function=self.bus2.send, args=(frame,)).start() + Timer(interval=send_after/1000., function=self.can_interface_2.send, args=(frame,)).start() time_before_receive = time() with pytest.raises(TimeoutError): can_transport_interface.receive_packet(timeout=timeout) @@ -258,9 +264,9 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) - Timer(interval=send_after/1000., function=self.bus2.send, args=(frame, )).start() + Timer(interval=send_after/1000., function=self.can_interface_2.send, args=(frame,)).start() datetime_before_receive = datetime.now() packet_record = can_transport_interface.receive_packet(timeout=timeout) datetime_after_receive = datetime.now() @@ -333,9 +339,9 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) - Timer(interval=send_after/1000., function=self.bus2.send, args=(frame, )).start() + Timer(interval=send_after/1000., function=self.can_interface_2.send, args=(frame,)).start() datetime_before_receive = datetime.now() packet_record = can_transport_interface.receive_packet(timeout=timeout) datetime_after_receive = datetime.now() @@ -423,7 +429,7 @@ async def test_async_send_packet(self, packet_type, addressing_type, addressing_ target_address = addressing_information.tx_packets_functional_ai["target_address"] source_address = addressing_information.tx_packets_functional_ai["source_address"] address_extension = addressing_information.tx_packets_functional_ai["address_extension"] - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) packet = CanPacket(packet_type=packet_type, addressing_format=addressing_information.addressing_format, @@ -465,7 +471,7 @@ async def test_async_receive_packet__timeout(self, example_addressing_informatio :param example_addressing_information: Example Addressing Information of a CAN Node. :param timeout: Timeout to pass to receive method [ms]. """ - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) time_before_receive = time() with pytest.raises((TimeoutError, asyncio.TimeoutError)): @@ -528,12 +534,12 @@ async def test_async_receive_packet__physical(self, addressing_information, fram """ async def _send_frame(): await asyncio.sleep(send_after/1000.) - self.bus2.send(frame) + self.can_interface_2.send(frame) frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + 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) datetime_before_receive = datetime.now() @@ -612,12 +618,12 @@ async def test_async_receive_packet__functional(self, addressing_information, fr """ async def _send_frame(): await asyncio.sleep(send_after/1000.) - self.bus2.send(frame) + self.can_interface_2.send(frame) frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] # data parameter of `frame` object must be set manually and according to `addressing_format` # and `addressing_information` - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + 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) datetime_before_receive = datetime.now() @@ -668,9 +674,9 @@ def test_send_on_one_receive_on_other_bus(self, example_addressing_information, :param payload: Payload of CAN Message to send. :param addressing_type: Addressing Type of CAN Message to send. """ - can_transport_interface_1 = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface_1 = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) - can_transport_interface_2 = PyCanTransportInterface(can_bus_manager=self.bus2, + can_transport_interface_2 = PyCanTransportInterface(can_bus_manager=self.can_interface_2, 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] @@ -712,9 +718,9 @@ async def test_async_send_on_one_receive_on_other_bus(self, example_addressing_i :param payload: Payload of CAN Message to send. :param addressing_type: Addressing Type of CAN Message to send. """ - can_transport_interface_1 = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface_1 = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) - can_transport_interface_2 = PyCanTransportInterface(can_bus_manager=self.bus2, + can_transport_interface_2 = PyCanTransportInterface(can_bus_manager=self.can_interface_2, 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] @@ -751,7 +757,7 @@ def test_timeout_then_send(self, example_addressing_information, payload, addres :param payload: Payload of CAN Message to send. :param addressing_type: Addressing Type of CAN Message to send. """ - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) uds_message = UdsMessage(payload=payload, addressing_type=addressing_type) packet = can_transport_interface.segmenter.segmentation(uds_message)[0] @@ -786,7 +792,7 @@ async def test_async_timeout_then_send(self, example_addressing_information, pay :param payload: Payload of CAN Message to send. :param addressing_type: Addressing Type of CAN Message to send. """ - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) uds_message = UdsMessage(payload=payload, addressing_type=addressing_type) packet = can_transport_interface.segmenter.segmentation(uds_message)[0] @@ -816,12 +822,12 @@ def test_timeout_then_receive(self, example_addressing_information, example_rx_f :param example_addressing_information: Example Addressing Information of a CAN Node. :param example_rx_frame: Example CAN frame that shall be recognized as a CAN packet. """ - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) with pytest.raises(TimeoutError): can_transport_interface.receive_packet(timeout=100) datetime_before_send = datetime.now() - self.bus2.send(example_rx_frame) + self.can_interface_2.send(example_rx_frame) packet_record = can_transport_interface.receive_packet(timeout=100) assert isinstance(packet_record, CanPacketRecord) assert packet_record.direction == TransmissionDirection.RECEIVED @@ -844,12 +850,12 @@ async def test_async_timeout_then_receive(self, example_addressing_information, :param example_addressing_information: Example Addressing Information of a CAN Node. :param example_rx_frame: Example CAN frame that shall be recognized as a CAN packet. """ - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) with pytest.raises((TimeoutError, asyncio.TimeoutError)): await can_transport_interface.async_receive_packet(timeout=100) datetime_before_send = datetime.now() - self.bus2.send(example_rx_frame) + self.can_interface_2.send(example_rx_frame) packet_record = await can_transport_interface.async_receive_packet(timeout=100) assert isinstance(packet_record, CanPacketRecord) assert packet_record.direction == TransmissionDirection.RECEIVED @@ -871,10 +877,10 @@ def test_observe_tx_packet(self, example_addressing_information, example_tx_fram :param example_tx_frame: Example CAN frame that shall be recognized as a CAN packet. :param example_tx_uds_message: CAN Message carried by CAN packet in example_tx_frame. """ - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) packet = can_transport_interface.segmenter.segmentation(example_tx_uds_message)[0] - self.bus1.send(example_tx_frame) + self.can_interface_1.send(example_tx_frame) sleep(0.1) datetime_before_send = datetime.now() packet_record = can_transport_interface.send_packet(packet) @@ -902,10 +908,10 @@ async def test_async_observe_tx_packet(self, example_addressing_information, exa :param example_tx_frame: Example CAN frame that shall be recognized as a CAN packet. :param example_tx_uds_message: CAN Message carried by CAN packet in example_tx_frame. """ - can_transport_interface = PyCanTransportInterface(can_bus_manager=self.bus1, + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) packet = can_transport_interface.segmenter.segmentation(example_tx_uds_message)[0] - self.bus1.send(example_tx_frame) + self.can_interface_1.send(example_tx_frame) sleep(0.1) datetime_before_send = datetime.now() packet_record = await can_transport_interface.async_send_packet(packet)