diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 80475846..f53a6e8c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,11 @@ -## Description +# Description -## How Has This Been Tested? +# How Has This Been Tested? -## Process +# Process I know the [process](https://github.com/mdabrowski1990/uds/wiki/Software-Development-Life-Cycle) and did my best to follow it diff --git a/.github/workflows/ci.yml b/.github/workflows/testing.yml similarity index 68% rename from .github/workflows/ci.yml rename to .github/workflows/testing.yml index 24afec9a..2161d16b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/testing.yml @@ -1,4 +1,4 @@ -name: CI +name: Testing on: [push, pull_request] @@ -28,7 +28,7 @@ jobs: - name: Install external packages used for dynamic tests run: | - pip install -r tests/requirements_for_software_tests.txt + pip install .[test] - name: Execute unit tests with coverage report [pytest] run: | @@ -50,10 +50,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v4 with: - python-version: 3.11 + python-version: 3.12 - name: Install project dependencies run: | @@ -63,20 +63,16 @@ jobs: - name: Install external packages used for dynamic tests run: | - pip install -r tests/requirements_for_software_tests.txt - - - name: Prepare coverage uploader [CodeCov] - run: | - curl -Os https://app.codecov.io/gh/mdabrowski1990/uds/uploader/linux/codecov - chmod +x codecov + pip install .[test] - name: Execute unit tests [pytest] run: | 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@v3 + uses: codecov/codecov-action@v4 with: + fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml flags: unit-tests @@ -86,23 +82,14 @@ jobs: pytest tests/software_tests --cov-report=xml --cov=uds -m "integration" - name: Upload integration tests report [CodeCov] - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: + fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml flags: integration-tests -# TODO: uncomment when performance tests are added -# - name: Execute performance tests [pytest] -# run: | -# pytest tests/software_tests --cov-report=xml --cov=uds -m "performance" -# -# - name: Upload performance tests report [CodeCov] -# uses: codecov/codecov-action@v3 -# with: -# token: ${{ secrets.CODECOV_TOKEN }} -# files: coverage.xml -# flags: performance-tests +# TODO: add performance tests upload when added static_code_analysis: @@ -127,7 +114,7 @@ jobs: - name: Install external packages used for static tests run: | - pip install -r tests/requirements_for_static_code_analysis.txt + pip install .[static-code-analysis] - name: Execute static code analysis [prospector] run: | @@ -150,8 +137,63 @@ jobs: - name: Install external packages used for dependency scanning run: | - pip install -r tests/requirements_for_dependency_scanning.txt + python -m pip install --upgrade pip + pip install .[dependency-scan] - name: Execute dependency scanning [safety] run: | safety check -r requirements.txt + + - name: Execute dependency scanning [pip-audit] + run: | + pip-audit + + sorting_imports: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install project dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade setuptools + pip install -r requirements.txt + + - name: Install external packages used for sorting imports + run: | + pip install .[sorting-imports] + + - name: Check imports are sorted [isort] + run: | + isort uds + isort tests + isort examples + + documentation_checks: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install project dependencies + run: | + pip install -r requirements.txt + + - name: Install external packages used for documentation checking + run: | + pip install .[docs-checks] + + - name: Check RST documentation [doc8] + run: | + doc8 docs\source diff --git a/README.rst b/README.rst index 2edd4d79..45d85e87 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -***** +*** UDS -***** +*** |CI| |SecurityScan| |BestPractices| |ReadTheDocs| |CodeCoverage| @@ -19,6 +19,64 @@ Security policy for this package is defined in `SECURITY.md`_ file. If you want to become a contributor, please read `CONTRIBUTING.md`_ file. +Why another UDS package? +------------------------ +There are a few already existing python packages to handle UDS protocol, +so you might wonder why would you consider using this one? + +This package is meant to support **multiple buses** (including CAN, LIN, Ethernet, K-Line, FlexRay) and **multiple +bus managers** (e.g. `python-can`_). +Additionally, it handles both communication nodes (client and server), and contains detailed configuration to fully +control all timing and transmission parameters. + +Thanks to all these features, this package can have multiple use-cases, including: + +- simple send-receive messages to/from any network +- comprehensive node simulations +- testing of UDS protocol communication implementation + +Unfortunately, all previously mentioned plans make the project a huge effort and at the time of writing, +the implementation process of these features is still ongoing (and probably the status will stay the same for at least +a couple more years). +On the other hand, the architecture to support all these features is already designed and some of the features +are already implemented with others defined or planned. + +To check the current implementation status, visit: + +- https://uds.readthedocs.io/en/stable/#features +- https://uds.readthedocs.io/en/stable/pages/knowledge_base/osi_model.html#uds-functionalities + + +Alternative options +``````````````````` + +python-udsoncan +''''''''''''''' +Link: https://github.com/pylessard/python-udsoncan + +- pros: + - comprehensive documentation + - handlers for multiple diagnostic services is implemented +- cons: + - only positive use case scenarios (where communication is in line with UDS standard) are supported + - only CAN bus is supported + - only Client side communication can be handled + - limited communication parameters configuration + + +python-uds +'''''''''' +Link: https://github.com/richClubb/python-uds + +- pros: + - CAN and LIN buses are supported +- cons: + - modest documentation + - only a few communication interfaces are supported + - only Client side communication can be handled + - limited communication parameters configuration + + Contact ------- - e-mail: uds-package-development@googlegroups.com @@ -34,8 +92,10 @@ Contact .. _Unified Diagnostic Services: https://en.wikipedia.org/wiki/Unified_Diagnostic_Services -.. |CI| image:: https://github.com/mdabrowski1990/uds/actions/workflows/ci.yml/badge.svg?branch=main - :target: https://github.com/mdabrowski1990/uds/actions/workflows/ci.yml +.. _python-can: https://github.com/hardbyte/python-can + +.. |CI| image:: https://github.com/mdabrowski1990/uds/actions/workflows/testing.yml/badge.svg?branch=main + :target: https://github.com/mdabrowski1990/uds/actions/workflows/testing.yml :alt: Continuous Integration Status .. |SecurityScan| image:: https://github.com/mdabrowski1990/uds/actions/workflows/codeql-analysis.yml/badge.svg?branch=main diff --git a/docs/source/conf.py b/docs/source/conf.py index d6fa4bcc..c68d2770 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,10 +14,11 @@ # import sys # sys.path.insert(0, os.path.abspath(".")) -import os -import sys import datetime +import os import re +import sys + try: # python 3.11 or newer import tomlib except ModuleNotFoundError: diff --git a/docs/source/pages/knowledge_base/diagnostic_message.rst b/docs/source/pages/knowledge_base/diagnostic_message.rst index a96ea608..88724e7b 100644 --- a/docs/source/pages/knowledge_base/diagnostic_message.rst +++ b/docs/source/pages/knowledge_base/diagnostic_message.rst @@ -231,8 +231,9 @@ in one or multiple servers' memory. ReadDTCInformation `````````````````` ReadDTCInformation service allows the client to read from any server or group of servers within a vehicle, -current information about all Diagnostic Trouble Codes. This could be a status of reported Diagnostic Trouble Code (DTC), -number of currently active DTCs or any other information returned by supported ReadDTCInformation SubFunctions. +current information about all Diagnostic Trouble Codes. +This could be a status of reported Diagnostic Trouble Code (DTC), number of currently active DTCs or any other +information returned by supported ReadDTCInformation SubFunctions. ReadDataByIdentifier diff --git a/docs/source/pages/knowledge_base/packet.rst b/docs/source/pages/knowledge_base/packet.rst index d6ec6cb9..45ab6624 100644 --- a/docs/source/pages/knowledge_base/packet.rst +++ b/docs/source/pages/knowledge_base/packet.rst @@ -196,8 +196,8 @@ where: Extended Addressing ''''''''''''''''''' If extended addressing format is used, then the value of **the first CAN frame byte informs about a target** of -a UDS packet and remaining `Network Address Information`_ (a sending entity and :ref:`an addressing type `) -are determined by CAN Identifier value. +a UDS packet and remaining `Network Address Information`_ (a sending entity and +:ref:`an addressing type `) are determined by CAN Identifier value. Following parameters specifies `Network Address Information`_ when Extended Addressing is used: - CAN ID @@ -402,7 +402,8 @@ Number of payload bytes carried by SF is specified by Single Frame Data Length ........................ Single Frame Data Length (SF_DL) is 4-bit (for CAN packets with DLC<=8) or 8-bit (for CAN packets with DLC>8) value -carried by every Single Frame as presented in :ref:`the table with CAN packet formats`. +carried by every Single Frame as presented in +:ref:`the table with CAN packet formats`. SF_DL specifies number of diagnostic message payload bytes transmitted in a Single Frame. .. note:: Maximal value of SF_DL depends on Single Frame :ref:`addressing format ` @@ -534,8 +535,9 @@ Block Size (BS) is a one byte value specified by receiving entity that informs a Block Size values: - 0x00 - The value 0 of the Block Size parameter informs a sender that no more :ref:`Flow Control ` - frames shall be sent during the transmission of the segmented message. + The value 0 of the Block Size parameter informs a sender that no more + :ref:`Flow Control ` frames shall be sent during the transmission + of the segmented message. Reception of Block Size = 0 shall cause the sender to send all remaining :ref:`Consecutive Frames ` without any stop for further diff --git a/docs/source/pages/knowledge_base/performance.rst b/docs/source/pages/knowledge_base/performance.rst index 0ceecab3..d04e0d1a 100644 --- a/docs/source/pages/knowledge_base/performance.rst +++ b/docs/source/pages/knowledge_base/performance.rst @@ -42,8 +42,8 @@ Timeout value: 1000 ms Error handling: - If N_As timeout is exceeded, then the transmission of the :ref:`diagnostic message ` - shall be aborted. + If N_As timeout is exceeded, then the transmission of + the :ref:`diagnostic message ` shall be aborted. Affected :ref:`CAN Packets `: - :ref:`Single Frame ` @@ -75,10 +75,10 @@ Affected :ref:`CAN Packets `: N_Bs ```` N_Bs is a time parameter related to :ref:`Flow Control (CAN Packet) ` reception -by a sender. -It is measured from the end of the last CAN Packet transmission (either transmitted :ref:`First Frame `, -:ref:`Consecutive Frame ` or received :ref:`Flow Control `), -till the reception of :ref:`Flow Control `. +by a sender. It is measured from the end of the last CAN Packet transmission (either transmitted +:ref:`First Frame `, :ref:`Consecutive Frame ` +or received :ref:`Flow Control `), till the reception of +:ref:`Flow Control `. Timeout value: 1000 ms @@ -96,10 +96,10 @@ Affected :ref:`CAN Packets `: N_Br ```` N_Br is a time parameter related to :ref:`Flow Control (CAN Packet) ` transmission -by a receiver. -It is measured from the end of the last CAN Packet transmission (either received :ref:`First Frame `, -:ref:`Consecutive Frame ` or transmitted :ref:`Flow Control `), -till the start of :ref:`Flow Control ` transmission. +by a receiver. It is measured from the end of the last CAN Packet transmission (either received +:ref:`First Frame `, :ref:`Consecutive Frame ` +or transmitted :ref:`Flow Control `), till the start of +:ref:`Flow Control ` transmission. Performance requirement: A receiving entity is obliged to transmit :ref:`Flow Control ` packet before value @@ -119,9 +119,9 @@ Affected :ref:`CAN Packets `: N_Cs ```` N_Cs is a time parameter related to :ref:`Consecutive Frame (CAN Packet) ` -transmission by a sender. -It is measured from the end of the last CAN Packet transmission (either received :ref:`Flow Control ` -or transmitted :ref:`Consecutive Frame `), till the start of +transmission by a sender. It is measured from the end of the last CAN Packet transmission (either received +:ref:`Flow Control ` or transmitted +:ref:`Consecutive Frame `), till the start of :ref:`Consecutive Frame ` transmission. Performance requirement: @@ -142,9 +142,9 @@ Affected :ref:`CAN Packets `: N_Cr ```` N_Cr is a time parameter related to :ref:`Consecutive Frame (CAN Packet) ` -reception by a receiver. -It is measured from the end of the last CAN Packet transmission (either transmitted :ref:`Flow Control ` -or received :ref:`Consecutive Frame `), till the reception of +reception by a receiver. It is measured from the end of the last CAN Packet transmission (either transmitted +:ref:`Flow Control ` or received +:ref:`Consecutive Frame `), till the reception of :ref:`Consecutive Frame `. Timeout value: diff --git a/docs/source/pages/user_guide/client_simulation.rst b/docs/source/pages/user_guide/client_simulation.rst index 9d32557e..71af6a52 100644 --- a/docs/source/pages/user_guide/client_simulation.rst +++ b/docs/source/pages/user_guide/client_simulation.rst @@ -5,4 +5,4 @@ ECUs) in UDS communication. Client simulation enables sending diagnostic request from connected nodes. `THIS FEATURE `_ IS PLANNED BUT NOT IMPLEMENTED YET, -THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. \ No newline at end of file +THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. diff --git a/docs/source/pages/user_guide/segmentation.rst b/docs/source/pages/user_guide/segmentation.rst index d204368c..86dfe3cb 100644 --- a/docs/source/pages/user_guide/segmentation.rst +++ b/docs/source/pages/user_guide/segmentation.rst @@ -87,7 +87,8 @@ Following functionalities are provided by :class:`~uds.segmentation.can_segmente - CAN packets desegmentation: As a user, you are able to :ref:`desegment CAN packets ` - (either objects of :class:`~uds.packet.can_packet.CanPacket` or :class:`~uds.packet.can_packet_record.CanPacketRecord` class) + (either objects of :class:`~uds.packet.can_packet.CanPacket` or + :class:`~uds.packet.can_packet_record.CanPacketRecord` class) into diagnostic messages (either objects of :class:`~uds.message.uds_message.UdsMessage` or :class:`~uds.message.uds_message.UdsMessageRecord` class). diff --git a/docs/source/pages/user_guide/transport.rst b/docs/source/pages/user_guide/transport.rst index 2260c0af..60e5f165 100644 --- a/docs/source/pages/user_guide/transport.rst +++ b/docs/source/pages/user_guide/transport.rst @@ -2,8 +2,8 @@ Transport Interfaces ==================== Transport interfaces are meant to handle Physical (layer 1), Data (layer 2), Network (layer 3) and Transport (layer 4) layers of UDS OSI model which are unique for every communication bus. First two layers (Physical and Data Link) are -usually handled by some external packages. Abstract API that is common for all Transport Interfaces (and therefore buses) -is defined in :class:`~uds.transport_interface.abstract_transport_interface.AbstractTransportInterface` class. +usually handled by some external packages. Abstract API that is common for all Transport Interfaces (and therefore +buses) is defined in :class:`~uds.transport_interface.abstract_transport_interface.AbstractTransportInterface` class. The implementation is located in :mod:`uds.transport_interface` sub-package. .. toctree:: diff --git a/docs/source/pages/user_guide/transport/can.rst b/docs/source/pages/user_guide/transport/can.rst index d57c2933..a34c451a 100644 --- a/docs/source/pages/user_guide/transport/can.rst +++ b/docs/source/pages/user_guide/transport/can.rst @@ -38,7 +38,8 @@ The following configuration parameters are set then: :attr:`~uds.transport_interface.can_transport_interface.AbstractCanTransportInterface.use_data_optimization`, :attr:`~uds.transport_interface.can_transport_interface.AbstractCanTransportInterface.filler_byte`, -Most of these attributes (all except :attr:`~uds.transport_interface.can_transport_interface.AbstractCanTransportInterface.addressing_information`) +Most of these attributes (all except +:attr:`~uds.transport_interface.can_transport_interface.AbstractCanTransportInterface.addressing_information`) can be changed after object is created. @@ -160,8 +161,8 @@ there are two methods which can be used to receive CAN packets: - :meth:`~uds.transport_interface.can_transport_interface.PyCanTransportInterface.receive_packet` - for synchronous implementation -- :meth:`~uds.transport_interface.can_transport_interface.PyCanTransportInterface.async_receive_packet` - for asynchronous - implementation +- :meth:`~uds.transport_interface.can_transport_interface.PyCanTransportInterface.async_receive_packet` - for + asynchronous implementation **Example synchronous code:** diff --git a/docs/source/pages/user_guide/transport/custom.rst b/docs/source/pages/user_guide/transport/custom.rst index ad481d6f..abadeec8 100644 --- a/docs/source/pages/user_guide/transport/custom.rst +++ b/docs/source/pages/user_guide/transport/custom.rst @@ -1,3 +1,3 @@ Custom Transport Interfaces =========================== -THIS FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. \ No newline at end of file +THIS FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. diff --git a/docs/source/pages/user_guide/transport/ethernet.rst b/docs/source/pages/user_guide/transport/ethernet.rst index 79e9a7f5..d1fa1090 100644 --- a/docs/source/pages/user_guide/transport/ethernet.rst +++ b/docs/source/pages/user_guide/transport/ethernet.rst @@ -1,3 +1,3 @@ Ethernet Transport Interfaces ============================= -Ethernet FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. \ No newline at end of file +Ethernet FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. diff --git a/docs/source/pages/user_guide/transport/flexray.rst b/docs/source/pages/user_guide/transport/flexray.rst index cae2022b..a087eb08 100644 --- a/docs/source/pages/user_guide/transport/flexray.rst +++ b/docs/source/pages/user_guide/transport/flexray.rst @@ -1,3 +1,3 @@ FlexRay Transport Interfaces ============================ -FlexRay FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. \ No newline at end of file +FlexRay FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. diff --git a/docs/source/pages/user_guide/transport/kline.rst b/docs/source/pages/user_guide/transport/kline.rst index c6d50adc..95109594 100644 --- a/docs/source/pages/user_guide/transport/kline.rst +++ b/docs/source/pages/user_guide/transport/kline.rst @@ -1,3 +1,3 @@ K-Line Transport Interfaces =========================== -K-Line FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. \ No newline at end of file +K-Line FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. diff --git a/docs/source/pages/user_guide/transport/lin.rst b/docs/source/pages/user_guide/transport/lin.rst index dbb6ef8c..03907c2e 100644 --- a/docs/source/pages/user_guide/transport/lin.rst +++ b/docs/source/pages/user_guide/transport/lin.rst @@ -1,3 +1,3 @@ LIN Transport Interfaces ======================== -LIN FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. \ No newline at end of file +LIN FEATURE IS PLANNED BUT NOT IMPLEMENTED YET, THEREFORE THERE ARE NO MORE INFORMATION TO DISPLAY. diff --git a/examples/can/python-can/kvaser/python_can_timing_issue.py b/examples/can/python-can/kvaser/python_can_timing_issue.py index 0c26d4c5..c4e06425 100644 --- a/examples/can/python-can/kvaser/python_can_timing_issue.py +++ b/examples/can/python-can/kvaser/python_can_timing_issue.py @@ -1,8 +1,9 @@ """UDS Issue: https://github.com/mdabrowski1990/uds/issues/228""" -from time import time from threading import Timer -from can import Notifier, BufferedReader, Bus, Message +from time import time + +from can import BufferedReader, Bus, Message, Notifier if __name__ == "__main__": kvaser_interface_1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) @@ -13,7 +14,7 @@ message = Message(data=[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], arbitration_id=0x100) - for _ in range(10): + for _ in range(100): Timer(interval=0.1, function=kvaser_interface_1.send, args=(message,)).start() sent_message = buffered_reader.get_message(timeout=1) diff --git a/examples/can/python-can/kvaser/receive_packets.py b/examples/can/python-can/kvaser/receive_packets.py index 0b301d22..98e2c596 100644 --- a/examples/can/python-can/kvaser/receive_packets.py +++ b/examples/can/python-can/kvaser/receive_packets.py @@ -3,8 +3,8 @@ from time import sleep from can import Bus, Message +from uds.can import CanAddressingFormat, CanAddressingInformation from uds.transport_interface import PyCanTransportInterface -from uds.can import CanAddressingInformation, CanAddressingFormat def main(): @@ -25,9 +25,9 @@ def main(): 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]) + frame_1 = Message(arbitration_id=0x6FE, data=[0x02, 0x10, 0x03]) + frame_2 = Message(arbitration_id=0x611, data=[0x02, 0x10, 0x03]) # shall be ignored, as it is not observed CAN ID + frame_3 = Message(arbitration_id=0x612, data=[0x02, 0x3E, 0x00, 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 diff --git a/examples/can/python-can/kvaser/receive_packets_asyncio.py b/examples/can/python-can/kvaser/receive_packets_asyncio.py index 6e78a38f..4d9421cb 100644 --- a/examples/can/python-can/kvaser/receive_packets_asyncio.py +++ b/examples/can/python-can/kvaser/receive_packets_asyncio.py @@ -2,8 +2,8 @@ from pprint import pprint from can import Bus, Message +from uds.can import CanAddressingFormat, CanAddressingInformation from uds.transport_interface import PyCanTransportInterface -from uds.can import CanAddressingInformation, CanAddressingFormat async def main(): @@ -24,9 +24,9 @@ async def main(): 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]) + frame_1 = Message(arbitration_id=0x6FE, data=[0x02, 0x10, 0x03]) + frame_2 = Message(arbitration_id=0x611, data=[0x02, 0x10, 0x03]) # shall be ignored, as it is not observed CAN ID + frame_3 = Message(arbitration_id=0x612, data=[0x02, 0x3E, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]) # receive CAN packet 1 kvaser_interface_2.send(frame_1) # transmit CAN Frame 1 diff --git a/examples/can/python-can/kvaser/send_message.py b/examples/can/python-can/kvaser/send_message.py new file mode 100644 index 00000000..811d3225 --- /dev/null +++ b/examples/can/python-can/kvaser/send_message.py @@ -0,0 +1,48 @@ +from pprint import pprint +from time import sleep + +from can import Bus +from uds.can import CanAddressingFormat, CanAddressingInformation +from uds.message import UdsMessage +from uds.transmission_attributes import AddressingType +from uds.transport_interface import PyCanTransportInterface + + +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.FUNCTIONAL, payload=[0x10, 0x03]) + message_2 = UdsMessage(addressing_type=AddressingType.PHYSICAL, payload=[0x22, *range(64)]) + + # send CAN Message 1 + record_1 = can_ti.send_message(message_1) + pprint(record_1.__dict__) + + # send CAN Packet 2 + record_2 = can_ti.send_message(message_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/python-can/kvaser/send_message_asyncio.py b/examples/can/python-can/kvaser/send_message_asyncio.py new file mode 100644 index 00000000..a87118c6 --- /dev/null +++ b/examples/can/python-can/kvaser/send_message_asyncio.py @@ -0,0 +1,48 @@ +import asyncio +from pprint import pprint + +from can import Bus +from uds.can import CanAddressingFormat, CanAddressingInformation +from uds.message import UdsMessage +from uds.transmission_attributes import AddressingType +from uds.transport_interface import PyCanTransportInterface + + +async 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.FUNCTIONAL, payload=[0x10, 0x03]) + message_2 = UdsMessage(addressing_type=AddressingType.PHYSICAL, payload=[0x22, *range(64)]) + + # send CAN Packet 1 + record_1 = await can_ti.async_send_message(message_1) + pprint(record_1.__dict__) + + # send CAN Packet 2 + record_2 = await can_ti.async_send_message(message_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()) diff --git a/examples/can/python-can/kvaser/send_packets.py b/examples/can/python-can/kvaser/send_packets.py index 90f4e72e..149f6858 100644 --- a/examples/can/python-can/kvaser/send_packets.py +++ b/examples/can/python-can/kvaser/send_packets.py @@ -2,10 +2,10 @@ from time import sleep from can import Bus -from uds.transport_interface import PyCanTransportInterface -from uds.can import CanAddressingInformation, CanAddressingFormat +from uds.can import CanAddressingFormat, CanAddressingInformation from uds.message import UdsMessage from uds.transmission_attributes import AddressingType +from uds.transport_interface import PyCanTransportInterface def main(): diff --git a/examples/can/python-can/kvaser/send_packets_asyncio.py b/examples/can/python-can/kvaser/send_packets_asyncio.py index 7c734345..b698ccad 100644 --- a/examples/can/python-can/kvaser/send_packets_asyncio.py +++ b/examples/can/python-can/kvaser/send_packets_asyncio.py @@ -2,10 +2,10 @@ from pprint import pprint from can import Bus -from uds.transport_interface import PyCanTransportInterface -from uds.can import CanAddressingInformation, CanAddressingFormat +from uds.can import CanAddressingFormat, CanAddressingInformation from uds.message import UdsMessage from uds.transmission_attributes import AddressingType +from uds.transport_interface import PyCanTransportInterface async def main(): diff --git a/pyproject.toml b/pyproject.toml index 9decfa54..18a13551 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,12 +66,6 @@ test = [ "pytest-cov", "pytest-asyncio", ] -docs = [ - "sphinx", - "sphinx-autoapi", - "sphinx-rtd-theme", - "tomli", -] static-code-analysis = [ "prospector == 1.10.3", "mypy == 1.9.0", @@ -81,6 +75,19 @@ static-code-analysis = [ ] dependency-scan = [ "safety == 3.1.0", + "pip-audit == 2.7.3", +] +sorting-imports = [ + "isort == 5.13.2" +] +docs = [ + "sphinx", + "sphinx-autoapi", + "sphinx-rtd-theme", + "tomli", +] +docs-checks = [ + "doc8 == 1.1.1", ] @@ -131,3 +138,20 @@ testpaths = [ ] filterwarnings = "ignore:::.*uds*" asyncio_mode = "strict" + + +[tool.isort] +src_paths = ["uds"] +line_length = 120 +use_parentheses = true +include_trailing_comma = true +force_grid_wrap = 0 +multi_line_output = 3 + + +[tool.doc8] +max-line-length = 120 +ignore = ["D004"] +ignore-path-errors = [ + "docs\\source\\pages\\knowledge_base\\diagnostic_message.rst;D001", # tables +] diff --git a/tests/requirements_for_dependency_scanning.txt b/tests/requirements_for_dependency_scanning.txt deleted file mode 100644 index 34b1dca7..00000000 --- a/tests/requirements_for_dependency_scanning.txt +++ /dev/null @@ -1 +0,0 @@ -safety==3.1.0 \ No newline at end of file diff --git a/tests/requirements_for_software_tests.txt b/tests/requirements_for_software_tests.txt deleted file mode 100644 index e271c41e..00000000 --- a/tests/requirements_for_software_tests.txt +++ /dev/null @@ -1,4 +0,0 @@ -mock==5.* -pytest==8.* -pytest-cov==5.* -pytest-asyncio>=0.17.2 diff --git a/tests/requirements_for_static_code_analysis.txt b/tests/requirements_for_static_code_analysis.txt deleted file mode 100644 index 0675b3c2..00000000 --- a/tests/requirements_for_static_code_analysis.txt +++ /dev/null @@ -1,5 +0,0 @@ -prospector==1.10.3 -mypy==1.9.0 -vulture==2.11 -bandit==1.7.8 -pyroma==4.2 \ 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 9c830578..a2a70887 100644 --- a/tests/software_tests/can/test_abstract_addressing_information.py +++ b/tests/software_tests/can/test_abstract_addressing_information.py @@ -3,7 +3,6 @@ from uds.can.abstract_addressing_information import AbstractCanAddressingInformation, AddressingType - SCRIPT_LOCATION = "uds.can.abstract_addressing_information" diff --git a/tests/software_tests/can/test_addressing_format.py b/tests/software_tests/can/test_addressing_format.py index 602e76a8..31452d81 100644 --- a/tests/software_tests/can/test_addressing_format.py +++ b/tests/software_tests/can/test_addressing_format.py @@ -1,6 +1,7 @@ +from aenum import StrEnum + from uds.can.addressing_format import CanAddressingFormat from uds.utilities import ValidatedEnum -from aenum import StrEnum class TestCanAddressingFormat: diff --git a/tests/software_tests/can/test_addressing_information.py b/tests/software_tests/can/test_addressing_information.py index 27c48a09..36b10101 100644 --- a/tests/software_tests/can/test_addressing_information.py +++ b/tests/software_tests/can/test_addressing_information.py @@ -1,11 +1,14 @@ import pytest -from mock import patch, MagicMock, Mock +from mock import MagicMock, Mock, patch -from uds.can.addressing_information import CanAddressingInformation, \ - CanAddressingFormat, InconsistentArgumentsError, AbstractCanAddressingInformation +from uds.can.addressing_information import ( + AbstractCanAddressingInformation, + CanAddressingFormat, + CanAddressingInformation, + InconsistentArgumentsError, +) from uds.transmission_attributes import AddressingType - SCRIPT_LOCATION = "uds.can.addressing_information" diff --git a/tests/software_tests/can/test_consecutive_frame.py b/tests/software_tests/can/test_consecutive_frame.py index 47513886..3ac8b2c7 100644 --- a/tests/software_tests/can/test_consecutive_frame.py +++ b/tests/software_tests/can/test_consecutive_frame.py @@ -1,10 +1,13 @@ import pytest from mock import patch -from uds.can.consecutive_frame import CanConsecutiveFrameHandler, \ - InconsistentArgumentsError, CanDlcHandler, DEFAULT_FILLER_BYTE from uds.can import CanAddressingFormat - +from uds.can.consecutive_frame import ( + DEFAULT_FILLER_BYTE, + CanConsecutiveFrameHandler, + CanDlcHandler, + InconsistentArgumentsError, +) SCRIPT_LOCATION = "uds.can.consecutive_frame" diff --git a/tests/software_tests/can/test_extended_addressing_information.py b/tests/software_tests/can/test_extended_addressing_information.py index 28c6eb2c..9e6b610b 100644 --- a/tests/software_tests/can/test_extended_addressing_information.py +++ b/tests/software_tests/can/test_extended_addressing_information.py @@ -1,9 +1,13 @@ import pytest from mock import Mock, patch -from uds.can.extended_addressing_information import ExtendedCanAddressingInformation, \ - CanAddressingFormat, InconsistentArgumentsError, UnusedArgumentError, AbstractCanAddressingInformation - +from uds.can.extended_addressing_information import ( + AbstractCanAddressingInformation, + CanAddressingFormat, + ExtendedCanAddressingInformation, + InconsistentArgumentsError, + UnusedArgumentError, +) SCRIPT_LOCATION = "uds.can.extended_addressing_information" diff --git a/tests/software_tests/can/test_first_frame.py b/tests/software_tests/can/test_first_frame.py index f88d8221..8e4351f3 100644 --- a/tests/software_tests/can/test_first_frame.py +++ b/tests/software_tests/can/test_first_frame.py @@ -1,10 +1,8 @@ import pytest from mock import patch -from uds.can.first_frame import CanFirstFrameHandler, \ - InconsistentArgumentsError, CanDlcHandler from uds.can import CanAddressingFormat - +from uds.can.first_frame import CanDlcHandler, CanFirstFrameHandler, InconsistentArgumentsError SCRIPT_LOCATION = "uds.can.first_frame" diff --git a/tests/software_tests/can/test_flow_control.py b/tests/software_tests/can/test_flow_control.py index f827aeeb..cd0923d8 100644 --- a/tests/software_tests/can/test_flow_control.py +++ b/tests/software_tests/can/test_flow_control.py @@ -1,11 +1,17 @@ import pytest -from mock import patch, call +from mock import Mock, call, patch -from uds.can.flow_control import CanSTminTranslator, CanFlowStatus, CanFlowControlHandler, \ - InconsistentArgumentsError, CanDlcHandler from uds.can import CanAddressingFormat -from uds.utilities import ValidatedEnum, NibbleEnum - +from uds.can.flow_control import ( + AbstractFlowControlParametersGenerator, + CanDlcHandler, + CanFlowControlHandler, + CanFlowStatus, + CanSTminTranslator, + DefaultFlowControlParametersGenerator, + InconsistentArgumentsError, +) +from uds.utilities import NibbleEnum, ValidatedEnum SCRIPT_LOCATION = "uds.can.flow_control" @@ -792,3 +798,83 @@ def test_validate_frame_data__invalid(self, addressing_format, raw_frame_data): with pytest.raises(ValueError): CanFlowControlHandler.validate_frame_data(addressing_format=addressing_format, raw_frame_data=raw_frame_data) + + +class TestAbstractFlowControlParametersGenerator: + """Unit tests for 'AbstractFlowControlParametersGenerator' class.""" + + def setup_method(self): + self.mock_flow_control_parameters_generator = Mock(spec=AbstractFlowControlParametersGenerator) + + # __iter__ + + def test_iter(self): + assert (AbstractFlowControlParametersGenerator.__iter__(self.mock_flow_control_parameters_generator) + == self.mock_flow_control_parameters_generator) + + +class TestDefaultFlowControlParametersGenerator: + """Unit tests for 'DefaultFlowControlParametersGenerator' class.""" + + def setup_method(self): + self.mock_flow_control_parameters_generator = Mock(spec=AbstractFlowControlParametersGenerator) + # patching + 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): + self._patcher_validate_raw_byte.stop() + + # inheritance + + def test_inheritance(self): + assert issubclass(DefaultFlowControlParametersGenerator, AbstractFlowControlParametersGenerator) + + # __init__ + + @pytest.mark.parametrize("block_size, st_min", [ + (0, 0), + (0xFF, 0xFF) + ]) + def test_init(self, block_size, st_min): + DefaultFlowControlParametersGenerator.__init__(self.mock_flow_control_parameters_generator, block_size, st_min) + assert self.mock_flow_control_parameters_generator.block_size == block_size + assert self.mock_flow_control_parameters_generator.st_min == st_min + + # next + + @pytest.mark.parametrize("block_size, st_min", [ + (0, 0), + (0xFF, 0xFF) + ]) + def test_next(self, block_size, st_min): + self.mock_flow_control_parameters_generator.block_size = block_size + self.mock_flow_control_parameters_generator.st_min = st_min + assert (DefaultFlowControlParametersGenerator.__next__(self.mock_flow_control_parameters_generator) == + (CanFlowStatus.ContinueToSend, block_size, st_min)) + + # block_size + + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_block_size__get(self, value): + self.mock_flow_control_parameters_generator._DefaultFlowControlParametersGenerator__block_size = value + assert DefaultFlowControlParametersGenerator.block_size.fget(self.mock_flow_control_parameters_generator) == value + + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_flow_control_parameters_generator__set(self, value): + DefaultFlowControlParametersGenerator.block_size.fset(self.mock_flow_control_parameters_generator, value) + assert self.mock_flow_control_parameters_generator._DefaultFlowControlParametersGenerator__block_size == value + self.mock_validate_raw_byte.assert_called_once_with(value) + + # st_min + + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_st_min__get(self, value): + self.mock_flow_control_parameters_generator._DefaultFlowControlParametersGenerator__st_min = value + assert DefaultFlowControlParametersGenerator.st_min.fget(self.mock_flow_control_parameters_generator) == value + + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_flow_control_parameters_generator__set(self, value): + DefaultFlowControlParametersGenerator.st_min.fset(self.mock_flow_control_parameters_generator, value) + assert self.mock_flow_control_parameters_generator._DefaultFlowControlParametersGenerator__st_min == value + self.mock_validate_raw_byte.assert_called_once_with(value) diff --git a/tests/software_tests/can/test_frame_fields.py b/tests/software_tests/can/test_frame_fields.py index 00acc30b..a5aa7939 100644 --- a/tests/software_tests/can/test_frame_fields.py +++ b/tests/software_tests/can/test_frame_fields.py @@ -1,9 +1,7 @@ import pytest -from mock import patch, call - -from uds.can.frame_fields import CanIdHandler, CanDlcHandler, \ - AddressingType, CanAddressingFormat +from mock import call, patch +from uds.can.frame_fields import AddressingType, CanAddressingFormat, CanDlcHandler, CanIdHandler SCRIPT_LOCATION = "uds.can.frame_fields" diff --git a/tests/software_tests/can/test_mixed_addressing_information.py b/tests/software_tests/can/test_mixed_addressing_information.py index 75de2f42..8d3ed9c0 100644 --- a/tests/software_tests/can/test_mixed_addressing_information.py +++ b/tests/software_tests/can/test_mixed_addressing_information.py @@ -1,9 +1,14 @@ import pytest -from mock import Mock, patch, call - -from uds.can.mixed_addressing_information import Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation, \ - CanAddressingFormat, InconsistentArgumentsError, UnusedArgumentError, AbstractCanAddressingInformation - +from mock import Mock, call, patch + +from uds.can.mixed_addressing_information import ( + AbstractCanAddressingInformation, + CanAddressingFormat, + InconsistentArgumentsError, + Mixed11BitCanAddressingInformation, + Mixed29BitCanAddressingInformation, + UnusedArgumentError, +) SCRIPT_LOCATION = "uds.can.mixed_addressing_information" diff --git a/tests/software_tests/can/test_normal_addressing_information.py b/tests/software_tests/can/test_normal_addressing_information.py index 100c7e2e..db9b9281 100644 --- a/tests/software_tests/can/test_normal_addressing_information.py +++ b/tests/software_tests/can/test_normal_addressing_information.py @@ -1,9 +1,14 @@ import pytest -from mock import Mock, patch, call - -from uds.can.normal_addressing_information import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation, \ - CanAddressingFormat, InconsistentArgumentsError, UnusedArgumentError, AbstractCanAddressingInformation - +from mock import Mock, call, patch + +from uds.can.normal_addressing_information import ( + AbstractCanAddressingInformation, + CanAddressingFormat, + InconsistentArgumentsError, + Normal11BitCanAddressingInformation, + NormalFixedCanAddressingInformation, + UnusedArgumentError, +) SCRIPT_LOCATION = "uds.can.normal_addressing_information" diff --git a/tests/software_tests/can/test_single_frame.py b/tests/software_tests/can/test_single_frame.py index 805f1a77..8be0d1b1 100644 --- a/tests/software_tests/can/test_single_frame.py +++ b/tests/software_tests/can/test_single_frame.py @@ -1,10 +1,8 @@ import pytest from mock import patch -from uds.can.single_frame import CanSingleFrameHandler, \ - InconsistentArgumentsError, CanDlcHandler, DEFAULT_FILLER_BYTE from uds.can import CanAddressingFormat - +from uds.can.single_frame import DEFAULT_FILLER_BYTE, CanDlcHandler, CanSingleFrameHandler, InconsistentArgumentsError SCRIPT_LOCATION = "uds.can.single_frame" diff --git a/tests/software_tests/conftest.py b/tests/software_tests/conftest.py index 18efe23d..aee1f116 100644 --- a/tests/software_tests/conftest.py +++ b/tests/software_tests/conftest.py @@ -1,12 +1,16 @@ from pytest import fixture from can import Message - -from uds.transmission_attributes import TransmissionDirection, AddressingType -from uds.can import CanAddressingFormat, Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation, \ - ExtendedCanAddressingInformation, Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation +from uds.can import ( + CanAddressingFormat, + ExtendedCanAddressingInformation, + Mixed11BitCanAddressingInformation, + Mixed29BitCanAddressingInformation, + Normal11BitCanAddressingInformation, + NormalFixedCanAddressingInformation, +) from uds.segmentation import CanSegmenter - +from uds.transmission_attributes import AddressingType, TransmissionDirection # Common diff --git a/tests/software_tests/message/test_nrc.py b/tests/software_tests/message/test_nrc.py index a13d75af..cf347584 100644 --- a/tests/software_tests/message/test_nrc.py +++ b/tests/software_tests/message/test_nrc.py @@ -1,7 +1,6 @@ import pytest -from uds.message.nrc import NRC, \ - ByteEnum, ValidatedEnum, ExtendableEnum +from uds.message.nrc import NRC, ByteEnum, ExtendableEnum, ValidatedEnum class TestNRC: diff --git a/tests/software_tests/message/test_service_identifiers.py b/tests/software_tests/message/test_service_identifiers.py index d5ab2ae0..0ad0393b 100644 --- a/tests/software_tests/message/test_service_identifiers.py +++ b/tests/software_tests/message/test_service_identifiers.py @@ -1,9 +1,7 @@ import pytest from mock import patch -from uds.message.service_identifiers import RequestSID, ResponseSID, \ - ByteEnum, ValidatedEnum, ExtendableEnum - +from uds.message.service_identifiers import ByteEnum, ExtendableEnum, RequestSID, ResponseSID, ValidatedEnum SCRIPT_LOCATION = "uds.message.service_identifiers" diff --git a/tests/software_tests/message/test_uds_message.py b/tests/software_tests/message/test_uds_message.py index ca07cd9b..a2c4cee8 100644 --- a/tests/software_tests/message/test_uds_message.py +++ b/tests/software_tests/message/test_uds_message.py @@ -1,11 +1,15 @@ import pytest from mock import Mock, patch -from uds.message.uds_message import UdsMessage, UdsMessageRecord, \ - AddressingType, ReassignmentError, AbstractUdsPacketRecord +from uds.message.uds_message import ( + AbstractUdsPacketRecord, + AddressingType, + ReassignmentError, + UdsMessage, + UdsMessageRecord, +) from uds.transmission_attributes import TransmissionDirection - SCRIPT_LOCATION = "uds.message.uds_message" 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 f04cd5f8..c51da523 100644 --- a/tests/software_tests/packet/test_abstract_can_packet_container.py +++ b/tests/software_tests/packet/test_abstract_can_packet_container.py @@ -1,11 +1,13 @@ import pytest from mock import Mock, patch -from uds.packet.abstract_can_packet_container import AbstractCanPacketContainer, \ - CanPacketType, AbstractCanAddressingInformation +from uds.packet.abstract_can_packet_container import ( + AbstractCanAddressingInformation, + AbstractCanPacketContainer, + CanPacketType, +) from uds.transmission_attributes import AddressingType - SCRIPT_LOCATION = "uds.packet.abstract_can_packet_container" diff --git a/tests/software_tests/packet/test_abstract_packet.py b/tests/software_tests/packet/test_abstract_packet.py index a4a4bc6a..bd18db50 100644 --- a/tests/software_tests/packet/test_abstract_packet.py +++ b/tests/software_tests/packet/test_abstract_packet.py @@ -1,9 +1,7 @@ import pytest from mock import Mock, patch -from uds.packet.abstract_packet import AbstractUdsPacketRecord, \ - TransmissionDirection, ReassignmentError, datetime - +from uds.packet.abstract_packet import AbstractUdsPacketRecord, ReassignmentError, TransmissionDirection, datetime SCRIPT_LOCATION = "uds.packet.abstract_packet" diff --git a/tests/software_tests/packet/test_abstract_packet_type.py b/tests/software_tests/packet/test_abstract_packet_type.py index cb3681d2..9899703d 100644 --- a/tests/software_tests/packet/test_abstract_packet_type.py +++ b/tests/software_tests/packet/test_abstract_packet_type.py @@ -1,6 +1,5 @@ from uds.packet.abstract_packet_type import AbstractUdsPacketType -from uds.utilities import NibbleEnum, ValidatedEnum, ExtendableEnum - +from uds.utilities import ExtendableEnum, NibbleEnum, ValidatedEnum SCRIPT_LOCATION = "uds.packet.abstract_packet" diff --git a/tests/software_tests/packet/test_can_packet.py b/tests/software_tests/packet/test_can_packet.py index aa702bb8..20baae1e 100644 --- a/tests/software_tests/packet/test_can_packet.py +++ b/tests/software_tests/packet/test_can_packet.py @@ -1,11 +1,16 @@ import pytest -from mock import patch, Mock, call +from mock import Mock, call, patch -from uds.packet.can_packet import CanPacket, \ - CanPacketType, CanAddressingFormat, AbstractCanAddressingInformation, AddressingType, \ - DEFAULT_FILLER_BYTE, AmbiguityError from uds.can import CanFlowStatus - +from uds.packet.can_packet import ( + DEFAULT_FILLER_BYTE, + AbstractCanAddressingInformation, + AddressingType, + AmbiguityError, + CanAddressingFormat, + CanPacket, + CanPacketType, +) SCRIPT_LOCATION = "uds.packet.can_packet" diff --git a/tests/software_tests/packet/test_can_packet_record.py b/tests/software_tests/packet/test_can_packet_record.py index 49827075..45dd23c9 100644 --- a/tests/software_tests/packet/test_can_packet_record.py +++ b/tests/software_tests/packet/test_can_packet_record.py @@ -1,13 +1,18 @@ +from datetime import datetime + import pytest from mock import Mock, patch -from datetime import datetime - -from uds.packet.can_packet_record import CanPacketRecord, \ - PythonCanMessage, AbstractCanAddressingInformation, InconsistentArgumentsError, CanAddressingFormat, CanPacketType -from uds.transmission_attributes import TransmissionDirection, AddressingType from uds.can import CanFlowStatus - +from uds.packet.can_packet_record import ( + AbstractCanAddressingInformation, + CanAddressingFormat, + CanPacketRecord, + CanPacketType, + InconsistentArgumentsError, + PythonCanMessage, +) +from uds.transmission_attributes import AddressingType, TransmissionDirection SCRIPT_LOCATION = "uds.packet.can_packet_record" diff --git a/tests/software_tests/packet/test_can_packet_type.py b/tests/software_tests/packet/test_can_packet_type.py index 10bee46d..a6d8686d 100644 --- a/tests/software_tests/packet/test_can_packet_type.py +++ b/tests/software_tests/packet/test_can_packet_type.py @@ -1,8 +1,8 @@ import pytest from mock import patch -from uds.packet.can_packet_type import CanPacketType from uds.packet import AbstractUdsPacketType +from uds.packet.can_packet_type import CanPacketType class TestCanPacketType: diff --git a/tests/software_tests/segmentation/test_abstract_segmenter.py b/tests/software_tests/segmentation/test_abstract_segmenter.py index 1ded96ba..e15d5bc2 100644 --- a/tests/software_tests/segmentation/test_abstract_segmenter.py +++ b/tests/software_tests/segmentation/test_abstract_segmenter.py @@ -3,7 +3,6 @@ from uds.segmentation.abstract_segmenter import AbstractSegmenter - SCRIPT_PATH = "uds.segmentation.abstract_segmenter" diff --git a/tests/software_tests/segmentation/test_can_segmenter.py b/tests/software_tests/segmentation/test_can_segmenter.py index 44f9825e..c86f4f15 100644 --- a/tests/software_tests/segmentation/test_can_segmenter.py +++ b/tests/software_tests/segmentation/test_can_segmenter.py @@ -1,12 +1,26 @@ import pytest -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, AbstractCanAddressingInformation -from uds.can import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation, \ - ExtendedCanAddressingInformation, Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation - +from mock import MagicMock, Mock, call, patch + +from uds.can import ( + ExtendedCanAddressingInformation, + Mixed11BitCanAddressingInformation, + Mixed29BitCanAddressingInformation, + Normal11BitCanAddressingInformation, + NormalFixedCanAddressingInformation, +) +from uds.segmentation.can_segmenter import ( + DEFAULT_FILLER_BYTE, + AbstractCanAddressingInformation, + AddressingType, + CanDlcHandler, + CanFirstFrameHandler, + CanPacket, + CanPacketRecord, + CanPacketType, + CanSegmenter, + SegmentationError, + UdsMessage, +) SCRIPT_LOCATION = "uds.segmentation.can_segmenter" 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 e05d7d54..81179f3d 100644 --- a/tests/software_tests/transport_interface/test_abstract_transport_interface.py +++ b/tests/software_tests/transport_interface/test_abstract_transport_interface.py @@ -3,7 +3,6 @@ from uds.transport_interface.abstract_transport_interface import AbstractTransportInterface - SCRIPT_LOCATION = "uds.transport_interface.abstract_transport_interface" 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 e6cda8a1..487002f9 100644 --- a/tests/software_tests/transport_interface/test_can_transport_interface.py +++ b/tests/software_tests/transport_interface/test_can_transport_interface.py @@ -1,10 +1,24 @@ -import pytest -from mock import MagicMock, Mock, AsyncMock, patch - -from uds.transport_interface.can_transport_interface import AbstractCanTransportInterface, PyCanTransportInterface, \ - AbstractCanAddressingInformation, BusABC, CanPacket, CanPacketType, TransmissionDirection -from uds.can import CanAddressingInformation, CanAddressingFormat +from random import randint +import pytest +from mock import AsyncMock, MagicMock, Mock, patch + +from uds.can import CanAddressingFormat, CanAddressingInformation +from uds.transmission_attributes import AddressingType +from uds.transport_interface.can_transport_interface import ( + AbstractCanAddressingInformation, + AbstractCanTransportInterface, + AbstractFlowControlParametersGenerator, + BusABC, + CanPacket, + CanPacketRecord, + CanPacketType, + DefaultFlowControlParametersGenerator, + PyCanTransportInterface, + TransmissionDirection, + UdsMessage, + UdsMessageRecord, +) SCRIPT_LOCATION = "uds.transport_interface.can_transport_interface" @@ -68,22 +82,24 @@ def test_init__valid_mandatory_args(self, mock_isinstance, assert self.mock_can_transport_interface.n_br == self.mock_can_transport_interface.DEFAULT_N_BR assert self.mock_can_transport_interface.n_cs == self.mock_can_transport_interface.DEFAULT_N_CS assert self.mock_can_transport_interface.n_cr_timeout == self.mock_can_transport_interface.N_CR_TIMEOUT + assert (self.mock_can_transport_interface.flow_control_parameters_generator + == self.mock_can_transport_interface.DEFAULT_FLOW_CONTROL_PARAMETERS) @pytest.mark.parametrize("can_bus_manager, addressing_information", [ ("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", [ + "dlc, use_data_optimization, filler_byte, flow_control_parameters_generator", [ ("n_as_timeout", "n_ar_timeout", "n_bs_timeout", "n_br", "n_cs", "n_cr_timeout", "dlc", "use_data_optimization", - "filler_byte"), - (Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock(), Mock()), + "filler_byte", "flow_control_parameters_generator"), + (Mock(), 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): + dlc, use_data_optimization, filler_byte, flow_control_parameters_generator): mock_isinstance.return_value = True AbstractCanTransportInterface.__init__(self=self.mock_can_transport_interface, can_bus_manager=can_bus_manager, @@ -96,7 +112,8 @@ 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) + filler_byte=filler_byte, + flow_control_parameters_generator=flow_control_parameters_generator) 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( @@ -114,6 +131,7 @@ def test_init__valid_all_args(self, mock_isinstance, assert self.mock_can_transport_interface.n_br == n_br assert self.mock_can_transport_interface.n_cs == n_cs assert self.mock_can_transport_interface.n_cr_timeout == n_cr_timeout + assert self.mock_can_transport_interface.flow_control_parameters_generator == flow_control_parameters_generator # segmenter @@ -492,6 +510,25 @@ 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_parameters_generator + + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_flow_control_parameters_generator__get(self, value): + self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_parameters_generator = value + assert AbstractCanTransportInterface.flow_control_parameters_generator.fget(self.mock_can_transport_interface) \ + == value + + @pytest.mark.parametrize("value", [Mock(spec=AbstractFlowControlParametersGenerator), + Mock(spec=DefaultFlowControlParametersGenerator)]) + def test_flow_control_parameters_generator__set(self, value): + AbstractCanTransportInterface.flow_control_parameters_generator.fset(self.mock_can_transport_interface, value) + assert self.mock_can_transport_interface._AbstractCanTransportInterface__flow_control_parameters_generator == value + + @pytest.mark.parametrize("value", ["something", Mock()]) + def test_flow_control_parameters_generator__set__type_error(self, value): + with pytest.raises(TypeError): + AbstractCanTransportInterface.flow_control_parameters_generator.fset(self.mock_can_transport_interface, value) + class TestPyCanTransportInterface: """Unit tests for `PyCanTransportInterface` class.""" @@ -509,6 +546,10 @@ def setup_method(self): 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_uds_message = patch(f"{SCRIPT_LOCATION}.UdsMessage") + self.mock_uds_message = self._patcher_uds_message.start() + self._patcher_uds_message_record = patch(f"{SCRIPT_LOCATION}.UdsMessageRecord") + self.mock_uds_message_record = self._patcher_uds_message_record.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") @@ -526,6 +567,8 @@ def teardown_method(self): self._patcher_time.stop() self._patcher_datetime.stop() self._patcher_abstract_can_ti_init.stop() + self._patcher_uds_message.stop() + self._patcher_uds_message_record.stop() self._patcher_can_id_handler.stop() self._patcher_can_dlc_handler.stop() self._patcher_can_packet_record.stop() @@ -968,6 +1011,132 @@ async def test_async_receive_packet(self, timeout): addressing_format=self.mock_can_transport_interface.segmenter.addressing_format, transmission_time=self.mock_datetime.fromtimestamp.return_value) + # send_message + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.FUNCTIONAL), + ]) + def test_send_message__single_frame(self, message): + mock_segmented_message = [Mock(spec=CanPacket)] + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + assert PyCanTransportInterface.send_message(self.mock_can_transport_interface, + message) == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.send_packet.assert_called_once_with(mock_segmented_message[0]) + self.mock_uds_message_record.assert_called_once_with((self.mock_can_transport_interface.send_packet.return_value, )) + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + def test_send_message__multiple_packets(self, message): + mock_segmented_message = [Mock(spec=CanPacket) for _ in range(randint(2, 20))] + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + with pytest.raises(NotImplementedError): + PyCanTransportInterface.send_message(self.mock_can_transport_interface, message) + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + + # async_send_message + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.FUNCTIONAL), + ]) + @pytest.mark.asyncio + async def test_async_send_message__single_frame(self, message): + mock_segmented_message = [Mock(spec=CanPacket)] + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + assert await PyCanTransportInterface.async_send_message(self.mock_can_transport_interface, message) \ + == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + self.mock_can_transport_interface.async_send_packet.assert_called_once_with(mock_segmented_message[0], loop=None) + self.mock_uds_message_record.assert_called_once_with((self.mock_can_transport_interface.async_send_packet.return_value, )) + + @pytest.mark.parametrize("message", [ + Mock(spec=UdsMessage, payload=[0x22, 0xF1, 0x86, 0xF1, 0x87, 0xF1, 0x88], addressing_type=AddressingType.PHYSICAL), + Mock(spec=UdsMessage, payload=[0x3E, 0x80], addressing_type=AddressingType.PHYSICAL), + ]) + @pytest.mark.asyncio + async def test_async_send_message__multiple_packets(self, message): + mock_segmented_message = [Mock(spec=CanPacket) for _ in range(randint(2, 20))] + self.mock_can_transport_interface.segmenter.segmentation = Mock(return_value=mock_segmented_message) + with pytest.raises(NotImplementedError): + await PyCanTransportInterface.async_send_message(self.mock_can_transport_interface, message) + self.mock_can_transport_interface.segmenter.segmentation.assert_called_once_with(message) + + # receive_message + + @pytest.mark.parametrize("timeout", ["something", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + def test_receive_message__type_error(self, mock_isinstance, timeout): + mock_isinstance.return_value = False + with pytest.raises(TypeError): + PyCanTransportInterface.receive_message(self.mock_can_transport_interface, timeout) + mock_isinstance.assert_called_once_with(timeout, (int, float)) + + @pytest.mark.parametrize("timeout", [0, -654]) + def test_receive_message__value_error(self, timeout): + with pytest.raises(ValueError): + PyCanTransportInterface.receive_message(self.mock_can_transport_interface, timeout) + + @pytest.mark.parametrize("timeout", [0.001, 123.456]) + def test_receive_message__single_frame(self, timeout): + mock_received_packet_record = Mock(spec=CanPacketRecord, packet_type=CanPacketType.SINGLE_FRAME) + self.mock_can_transport_interface.receive_packet.return_value = mock_received_packet_record + assert PyCanTransportInterface.receive_message(self.mock_can_transport_interface, timeout) \ + == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.receive_packet.assert_called_once_with(timeout=timeout) + self.mock_uds_message_record.assert_called_once_with([mock_received_packet_record]) + + @pytest.mark.parametrize("timeout", [0.001, 123.456]) + @pytest.mark.parametrize("packets_received", [ + (Mock(spec=CanPacketRecord, packet_type=CanPacketType.FIRST_FRAME),), + (Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME),), + ]) + def test_receive_message__multiple_packets(self, timeout, packets_received): + self.mock_can_transport_interface.receive_packet.side_effect = packets_received + with pytest.raises(NotImplementedError): + PyCanTransportInterface.receive_message(self.mock_can_transport_interface, timeout) + + # async_receive_message + + @pytest.mark.parametrize("timeout", ["something", Mock()]) + @patch(f"{SCRIPT_LOCATION}.isinstance") + @pytest.mark.asyncio + async def test_async_receive_message__type_error(self, mock_isinstance, timeout): + mock_isinstance.return_value = False + with pytest.raises(TypeError): + await PyCanTransportInterface.async_receive_message(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_message__value_error(self, timeout): + with pytest.raises(ValueError): + await PyCanTransportInterface.async_receive_message(self.mock_can_transport_interface, timeout) + + @pytest.mark.parametrize("timeout", [0.001, 123.456]) + @pytest.mark.asyncio + async def test_async_receive_message__single_frame(self, timeout): + mock_received_packet_record = Mock(spec=CanPacketRecord, packet_type=CanPacketType.SINGLE_FRAME) + self.mock_can_transport_interface.async_receive_packet.return_value = mock_received_packet_record + assert await PyCanTransportInterface.async_receive_message(self.mock_can_transport_interface, timeout) \ + == self.mock_uds_message_record.return_value + self.mock_can_transport_interface.async_receive_packet.assert_called_once_with(timeout=timeout, loop=None) + self.mock_uds_message_record.assert_called_once_with([mock_received_packet_record]) + + @pytest.mark.parametrize("timeout", [0.001, 123.456]) + @pytest.mark.parametrize("packets_received", [ + (Mock(spec=CanPacketRecord, packet_type=CanPacketType.FIRST_FRAME),), + (Mock(spec=CanPacketRecord, packet_type=CanPacketType.CONSECUTIVE_FRAME),), + ]) + @pytest.mark.asyncio + async def test_async_receive_message__multiple_packets(self, timeout, packets_received): + self.mock_can_transport_interface.async_receive_packet.side_effect = packets_received + with pytest.raises(NotImplementedError): + await PyCanTransportInterface.async_receive_message(self.mock_can_transport_interface, timeout) + @pytest.mark.integration class TestPyCanTransportInterfaceIntegration: diff --git a/tests/software_tests/utilities/test_bytes_operations.py b/tests/software_tests/utilities/test_bytes_operations.py index 04571995..ef98db4b 100644 --- a/tests/software_tests/utilities/test_bytes_operations.py +++ b/tests/software_tests/utilities/test_bytes_operations.py @@ -2,9 +2,7 @@ from mock import patch from uds.utilities import ValidatedEnum -from uds.utilities.bytes_operations import Endianness, bytes_list_to_int, int_to_bytes_list, \ - InconsistentArgumentsError - +from uds.utilities.bytes_operations import Endianness, InconsistentArgumentsError, bytes_list_to_int, int_to_bytes_list SCRIPT_LOCATION = "uds.utilities.bytes_operations" diff --git a/tests/software_tests/utilities/test_common_types.py b/tests/software_tests/utilities/test_common_types.py index 041e2d7f..c6f301ac 100644 --- a/tests/software_tests/utilities/test_common_types.py +++ b/tests/software_tests/utilities/test_common_types.py @@ -1,6 +1,6 @@ import pytest -from uds.utilities.common_types import validate_raw_bytes, validate_raw_byte, validate_nibble +from uds.utilities.common_types import validate_nibble, validate_raw_byte, validate_raw_bytes class TestFunctions: diff --git a/tests/software_tests/utilities/test_enums.py b/tests/software_tests/utilities/test_enums.py index 44b48f0b..d026f552 100644 --- a/tests/software_tests/utilities/test_enums.py +++ b/tests/software_tests/utilities/test_enums.py @@ -1,8 +1,7 @@ import pytest from aenum import IntEnum, StrEnum -from uds.utilities.enums import ExtendableEnum, ValidatedEnum, ByteEnum, NibbleEnum - +from uds.utilities.enums import ByteEnum, ExtendableEnum, NibbleEnum, ValidatedEnum SCRIPT_LOCATION = "uds.utilities.enums" 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 d574271b..d581f8ef 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,15 +1,17 @@ -import pytest import asyncio -from threading import Timer -from time import time, sleep from datetime import datetime -from can import Bus, Message +from threading import Timer +from time import sleep, time -from uds.can import CanAddressingInformation, CanAddressingFormat, CanFlowStatus -from uds.transport_interface import PyCanTransportInterface +import pytest + +from can import Bus, Message +from uds.can import CanAddressingFormat, CanAddressingInformation, CanFlowStatus +from uds.message import UdsMessage, UdsMessageRecord +from uds.packet import CanPacket, CanPacketRecord, CanPacketType +from uds.segmentation import CanSegmenter from uds.transmission_attributes import AddressingType, TransmissionDirection -from uds.packet import CanPacket, CanPacketType, CanPacketRecord -from uds.message import UdsMessage +from uds.transport_interface import PyCanTransportInterface class TestPythonCanKvaser: @@ -22,6 +24,8 @@ class TestPythonCanKvaser: """ TASK_TIMING_TOLERANCE = 30. # ms + DELAY_AFTER_RECEIVING_FRAME = 1. # ms + DELAY_BETWEEN_CONSECUTIVE_FRAMES = 50. # ms def setup_class(self): self.can_interface_1 = Bus(interface="kvaser", channel=0, fd=True, receive_own_messages=True) @@ -78,7 +82,7 @@ def teardown_class(self): 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}), + {"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): """ @@ -90,10 +94,10 @@ def test_send_packet(self, packet_type, addressing_type, addressing_information, 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 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. + :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"] @@ -190,7 +194,7 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, :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 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]. """ @@ -202,12 +206,13 @@ def test_receive_packet__timeout(self, addressing_information, addressing_type, # and `addressing_information` can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) - Timer(interval=send_after/1000., function=self.can_interface_2.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) time_after_receive = time() assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE + sleep((send_after - timeout) * 2 / 1000.) # wait till packet arrives @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -251,13 +256,14 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, 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. + 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 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]. """ @@ -266,7 +272,7 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, # and `addressing_information` can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) - Timer(interval=send_after/1000., function=self.can_interface_2.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() @@ -280,8 +286,8 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, 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: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive @pytest.mark.parametrize("addressing_information, frame", [ @@ -301,19 +307,19 @@ def test_receive_packet__physical(self, addressing_information, frame, timeout, 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=[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}, ), + 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}, ), + 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", [ @@ -332,7 +338,7 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout 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 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]. """ @@ -341,7 +347,7 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout # and `addressing_information` can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) - Timer(interval=send_after/1000., function=self.can_interface_2.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() @@ -355,8 +361,8 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout 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: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive # async_send_packet @@ -401,10 +407,11 @@ def test_receive_packet__functional(self, addressing_information, frame, timeout 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}), + {"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): + 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. @@ -414,10 +421,10 @@ async def test_async_send_packet(self, packet_type, addressing_type, addressing_ 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 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. + :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"] @@ -458,26 +465,66 @@ async def test_async_send_packet(self, packet_type, addressing_type, addressing_ # async_receive_packet - @pytest.mark.parametrize("timeout", [1000, 50]) + @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.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), + ]) @pytest.mark.asyncio - async def test_async_receive_packet__timeout(self, example_addressing_information, timeout): + async def test_async_receive_packet__timeout(self, example_addressing_information, + addressing_type, addressing_information, frame, timeout, send_after): """ 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. + 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 before any CAN packet + reaches CAN bus. Expected: Timeout exception is raised. - :param example_addressing_information: Example Addressing Information of a CAN Node. + :param example_addressing_information: Example Addressing Information of CAN Node. + :param addressing_type: Addressing Type used to transmit the frame. + :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.can_interface_2.send(frame) + + 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.can_interface_1, addressing_information=example_addressing_information) + future_record = can_transport_interface.async_receive_packet(timeout=timeout) + frame_sending_task = asyncio.create_task(_send_frame()) time_before_receive = time() with pytest.raises((TimeoutError, asyncio.TimeoutError)): - await can_transport_interface.async_receive_packet(timeout=timeout) + await future_record time_after_receive = time() assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE + await frame_sending_task + sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.) @pytest.mark.parametrize("addressing_information, frame", [ (CanAddressingInformation(addressing_format=CanAddressingFormat.NORMAL_11BIT_ADDRESSING, @@ -522,18 +569,20 @@ async def test_async_receive_packet__physical(self, addressing_information, fram 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. + 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 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.) + await asyncio.sleep(send_after / 1000.) self.can_interface_2.send(frame) frame.arbitration_id = addressing_information.rx_packets_physical_ai["can_id"] @@ -542,12 +591,13 @@ async def _send_frame(): can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) future_record = can_transport_interface.async_receive_packet(timeout=timeout) + tasks = [asyncio.create_task(_send_frame()), asyncio.create_task(future_record)] datetime_before_receive = datetime.now() - done_tasks, _ = await asyncio.wait([_send_frame(), future_record]) + done_tasks, _ = await asyncio.wait(tasks) 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" + 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 @@ -559,8 +609,8 @@ async def _send_frame(): 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: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive @pytest.mark.parametrize("addressing_information, frame", [ @@ -580,19 +630,19 @@ async def _send_frame(): 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=[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}, ), + 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}, ), + 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", [ @@ -606,18 +656,20 @@ async def test_async_receive_packet__functional(self, addressing_information, fr 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. + 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 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.) + await asyncio.sleep(send_after / 1000.) self.can_interface_2.send(frame) frame.arbitration_id = addressing_information.rx_packets_functional_ai["can_id"] @@ -626,12 +678,13 @@ async def _send_frame(): can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=addressing_information) future_record = can_transport_interface.async_receive_packet(timeout=timeout) + tasks = [asyncio.create_task(_send_frame()), asyncio.create_task(future_record)] datetime_before_receive = datetime.now() - done_tasks, _ = await asyncio.wait([_send_frame(), future_record]) + done_tasks, _ = await asyncio.wait(tasks) 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" + 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 @@ -643,26 +696,306 @@ async def _send_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 - assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert send_after <= (datetime_after_receive - datetime_before_receive).total_seconds() * 1000. < timeout # assert datetime_before_receive < packet_record.transmission_time < datetime_after_receive + # send_message + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + # TODO: add more with https://github.com/mdabrowski1990/uds/issues/267 + ]) + def test_send_message(self, example_addressing_information, message): + """ + Check for a simple synchronous UDS message sending. + + Procedure: + 1. Send a UDS message using Transport Interface (via CAN Interface). + Expected: UDS message record returned. + 2. Validate transmitted UDS message record attributes. + Expected: Attributes of UDS message record are in line with the transmitted UDS message. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + datetime_before_send = datetime.now() + message_record = can_transport_interface.send_message(message) + datetime_after_send = datetime.now() + assert isinstance(message_record, UdsMessageRecord) + assert message_record.direction == TransmissionDirection.TRANSMITTED + assert message_record.payload == message.payload + assert message_record.addressing_type == message.addressing_type + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert datetime_before_send < message_record.transmission_start + # assert message_record.transmission_end < datetime_after_send + if len(message_record.packets_records) == 1: + assert message_record.transmission_start == message_record.transmission_end + else: + assert message_record.transmission_start < message_record.transmission_end + + # async_send_message + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + # TODO: add more with https://github.com/mdabrowski1990/uds/issues/267 + ]) + @pytest.mark.asyncio + async def test_async_send_message(self, example_addressing_information, message): + """ + Check for a simple asynchronous UDS message sending. + + Procedure: + 1. Send (using async method) a UDS message using Transport Interface (via CAN Interface). + Expected: UDS message record returned. + 2. Validate transmitted UDS message record attributes. + Expected: Attributes of UDS message record are in line with the transmitted UDS message. + + :param example_addressing_information: Example Addressing Information of a CAN Node. + :param message: UDS message to send. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + datetime_before_send = datetime.now() + message_record = await can_transport_interface.async_send_message(message) + datetime_after_send = datetime.now() + assert isinstance(message_record, UdsMessageRecord) + assert message_record.direction == TransmissionDirection.TRANSMITTED + assert message_record.payload == message.payload + assert message_record.addressing_type == message.addressing_type + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert datetime_before_send < message_record.transmission_start + # assert message_record.transmission_end < datetime_after_send + if len(message_record.packets_records) == 1: + assert message_record.transmission_start == message_record.transmission_end + else: + assert message_record.transmission_start < message_record.transmission_end + + # receive_message + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + ]) + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 1001), # ms + (50, 55), + ]) + def test_receive_message__sf__timeout(self, example_addressing_information, example_addressing_information_2nd_node, + message, timeout, send_after): + """ + Check for a timeout during receiving of a UDS message. + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received UDS message. + 2. Call method to receive packet via Transport Interface with timeout set just before UDS message + reaches CAN bus. + Expected: Timeout exception is raised. + + :param example_addressing_information: Addressing Information of receiving CAN Node. + :param example_addressing_information_2nd_node: Addressing Information of transmitting CAN Node. + :param message: UDS message to transmit. + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + packet = other_node_segmenter.segmentation(message)[0] + frame = Message(arbitration_id=packet.can_id, data=packet.raw_frame_data) + 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_message(timeout=timeout) + time_after_receive = time() + assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE + sleep((send_after - timeout) * 2 / 1000.) # wait till message arrives + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + ]) + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 950), # ms + (50, 20), + ]) + def test_receive_message__sf(self, example_addressing_information, example_addressing_information_2nd_node, + message, timeout, send_after): + """ + Check for receiving of a UDS message (carried by Single Frame packet). + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received + UDS message (Single Frame). + 2. Call method to receive packet via Transport Interface with timeout set just after UDS message + reaches CAN bus. + Expected: UDS message is received. + 3. Validate received UDS message record attributes. + Expected: Attributes of UDS message record are in line with the received UDS message. + + :param example_addressing_information: Addressing Information of receiving CAN Node. + :param example_addressing_information_2nd_node: Addressing Information of transmitting CAN Node. + :param message: UDS message to transmit. + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + packet = other_node_segmenter.segmentation(message)[0] + frame = Message(arbitration_id=packet.can_id, data=packet.raw_frame_data) + Timer(interval=send_after / 1000., function=self.can_interface_2.send, args=(frame,)).start() + datetime_before_receive = datetime.now() + message_record = can_transport_interface.receive_message(timeout=timeout) + datetime_after_receive = datetime.now() + assert isinstance(message_record, UdsMessageRecord) + assert message_record.direction == TransmissionDirection.RECEIVED + assert message_record.payload == message.payload + assert message_record.addressing_type == message.addressing_type + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert datetime_before_send < message_record.transmission_start + # assert message_record.transmission_end < datetime_after_send + if len(message_record.packets_records) == 1: + assert message_record.transmission_start == message_record.transmission_end + else: + assert message_record.transmission_start < message_record.transmission_end + + # TODO: add more with https://github.com/mdabrowski1990/uds/issues/266 + + # async_receive_message + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + ]) + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 1001), # ms + (50, 55), + ]) + @pytest.mark.asyncio + async def test_async_receive_message__sf__timeout(self, example_addressing_information, + example_addressing_information_2nd_node, + message, timeout, send_after): + """ + Check for a timeout during asynchronous receiving of a UDS message. + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received UDS message. + 2. Call async method to receive packet via Transport Interface with timeout set before any CAN packet + reaches CAN bus. + Expected: Timeout exception is raised. + + :param example_addressing_information: Addressing Information of receiving CAN Node. + :param message: UDS message to transmit. + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + packet = other_node_segmenter.segmentation(message)[0] + frame = Message(arbitration_id=packet.can_id, data=packet.raw_frame_data) + + async def _send_frame(): + await asyncio.sleep(send_after / 1000.) + self.can_interface_2.send(frame) + + future_record = can_transport_interface.async_receive_message(timeout=timeout) + frame_sending_task = asyncio.create_task(_send_frame()) + time_before_receive = time() + with pytest.raises(TimeoutError): + await future_record + time_after_receive = time() + assert timeout < (time_after_receive - time_before_receive) * 1000. < timeout + self.TASK_TIMING_TOLERANCE + await frame_sending_task + sleep(self.DELAY_AFTER_RECEIVING_FRAME / 1000.) + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + ]) + @pytest.mark.parametrize("timeout, send_after", [ + (1000, 950), # ms + (50, 20), + ]) + @pytest.mark.asyncio + async def test_async_receive_message__sf(self, example_addressing_information, + example_addressing_information_2nd_node, + message, timeout, send_after): + """ + Check for asynchronous receiving of a UDS message (carried by Single Frame packet). + + Procedure: + 1. Schedule transmission (using second CAN interface) of a CAN frame that carries received + UDS message (Single Frame). + 2. Call async method to receive packet via Transport Interface with timeout set just after UDS message + reaches CAN bus. + Expected: UDS message is received. + 3. Validate received UDS message record attributes. + Expected: Attributes of UDS message record are in line with the received UDS message. + + :param example_addressing_information: Addressing Information of receiving CAN Node. + :param example_addressing_information_2nd_node: Addressing Information of transmitting CAN Node. + :param message: UDS message to transmit. + :param timeout: Timeout to pass to receive method [ms]. + :param send_after: Time when to send CAN frame after call of receive method [ms]. + """ + can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, + addressing_information=example_addressing_information) + other_node_segmenter = CanSegmenter(addressing_information=example_addressing_information_2nd_node) + packet = other_node_segmenter.segmentation(message)[0] + frame = Message(arbitration_id=packet.can_id, data=packet.raw_frame_data) + + async def _send_frame(): + await asyncio.sleep(send_after / 1000.) + self.can_interface_2.send(frame) + + future_record = can_transport_interface.async_receive_message(timeout=timeout) + tasks = [asyncio.create_task(_send_frame()), asyncio.create_task(future_record)] + datetime_before_receive = datetime.now() + done_tasks, _ = await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) + datetime_after_receive = datetime.now() + received_records = tuple(filter(lambda result: isinstance(result, UdsMessageRecord), + (done_task.result() for done_task in done_tasks))) + assert len(received_records) == 1, "UDS message was received" + message_record = received_records[0] + assert message_record.direction == TransmissionDirection.RECEIVED + assert message_record.payload == message.payload + assert message_record.addressing_type == message.addressing_type + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert datetime_before_send < message_record.transmission_start + # assert message_record.transmission_end < datetime_after_send + if len(message_record.packets_records) == 1: + assert message_record.transmission_start == message_record.transmission_end + else: + assert message_record.transmission_start < message_record.transmission_end + + # TODO: add more with https://github.com/mdabrowski1990/uds/issues/266 + # use cases @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): + def test_send_packet_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. + 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). + 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). + 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 @@ -671,8 +1004,8 @@ def test_send_on_one_receive_on_other_bus(self, example_addressing_information, :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. + :param payload: Payload of UDS message to send. + :param addressing_type: Addressing Type of UDS message to send. """ can_transport_interface_1 = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) @@ -697,16 +1030,16 @@ def test_send_on_one_receive_on_other_bus(self, example_addressing_information, ([0x3E, 0x80], AddressingType.FUNCTIONAL), ]) @pytest.mark.asyncio - async def test_async_send_on_one_receive_on_other_bus(self, example_addressing_information, - example_addressing_information_2nd_node, - payload, addressing_type): + async def test_async_send_packet_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. + 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). + 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). + 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 @@ -715,8 +1048,8 @@ async def test_async_send_on_one_receive_on_other_bus(self, example_addressing_i :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. + :param payload: Payload of UDS message to send. + :param addressing_type: Addressing Type of UDS message to send. """ can_transport_interface_1 = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) @@ -724,8 +1057,9 @@ async def test_async_send_on_one_receive_on_other_bus(self, example_addressing_i 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)]) + tasks = [asyncio.create_task(can_transport_interface_2.async_send_packet(packet)), + asyncio.create_task(can_transport_interface_1.async_receive_packet(timeout=100))] + done_tasks, _ = await asyncio.wait(tasks) packet_record_1, packet_record_2 = [done_task.result() for done_task in done_tasks] assert isinstance(packet_record_1, CanPacketRecord) and isinstance(packet_record_2, CanPacketRecord) assert {packet_record_1.direction, packet_record_2.direction} \ @@ -735,15 +1069,93 @@ async def test_async_send_on_one_receive_on_other_bus(self, example_addressing_i assert packet_record_1.raw_frame_data == packet_record_2.raw_frame_data assert packet_record_1.addressing_type == packet_record_2.addressing_type + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + # TODO: add more after https://github.com/mdabrowski1990/uds/issues/266 and + # https://github.com/mdabrowski1990/uds/issues/267 + ]) + def test_send_message_on_one_receive_on_other_bus(self, example_addressing_information, + example_addressing_information_2nd_node, message): + """ + Check for sending and receiving UDS message using two Transport Interfaces. + + Procedure: + 1. Send a UDS message using Transport Interface 1 (via CAN Interface 1). + Expected: UDS message record returned. + 2. Receive a UDS message using Transport Interface 2 (via CAN Interface 2). + Expected: UDS message is received. + 3. Validate received UDS message records attributes. + Expected: Attributes of UDS message 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 message: UDS message to send. + """ + 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.can_interface_2, + addressing_information=example_addressing_information_2nd_node) + sent_message_record = can_transport_interface_2.send_message(message) + received_message_record = can_transport_interface_1.receive_message(timeout=100) + assert isinstance(sent_message_record, UdsMessageRecord) + assert isinstance(received_message_record, UdsMessageRecord) + assert sent_message_record.direction == TransmissionDirection.TRANSMITTED + assert received_message_record.direction == TransmissionDirection.RECEIVED + assert sent_message_record.addressing_type == received_message_record.addressing_type == message.addressing_type + assert sent_message_record.payload == received_message_record.payload == message.payload + + @pytest.mark.parametrize("message", [ + UdsMessage(payload=[0x22, 0x12, 0x34], addressing_type=AddressingType.PHYSICAL), + UdsMessage(payload=[0x10, 0x01], addressing_type=AddressingType.FUNCTIONAL), + # TODO: add more after https://github.com/mdabrowski1990/uds/issues/266 and + # https://github.com/mdabrowski1990/uds/issues/267 + ]) + @pytest.mark.asyncio + async def test_async_send_message_on_one_receive_on_other_bus(self, example_addressing_information, + example_addressing_information_2nd_node, message): + """ + Check for asynchronous sending and receiving UDS message using two Transport Interfaces. + + Procedure: + 1. Send (using async method) a UDS message using Transport Interface 1 (via CAN Interface 1). + Expected: UDS message record returned. + 2. Receive (using async method) a UDS message using Transport Interface 2 (via CAN Interface 2). + Expected: UDS message is received. + 3. Validate received UDS message records attributes. + Expected: Attributes of UDS message 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 message: UDS message to send. + """ + 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.can_interface_2, + addressing_information=example_addressing_information_2nd_node) + tasks = [asyncio.create_task(can_transport_interface_2.async_send_message(message)), + asyncio.create_task(can_transport_interface_1.async_receive_message(timeout=100))] + done_tasks, _ = await asyncio.wait(tasks) + message_record_1, message_record_2 = [done_task.result() for done_task in done_tasks] + assert isinstance(message_record_1, UdsMessageRecord) and isinstance(message_record_2, UdsMessageRecord) + assert {message_record_1.direction, message_record_2.direction} \ + == {TransmissionDirection.TRANSMITTED, TransmissionDirection.RECEIVED} + assert message_record_1.addressing_type == message_record_2.addressing_type == message.addressing_type + assert message_record_1.payload == message_record_2.payload == message.payload + # 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): + def test_timeout_then_send_packet(self, example_addressing_information, payload, addressing_type): """ - Check for sending a CAN Packet after a timeout exception during receiving. + Check for sending a CAN packet after a timeout exception during receiving. Procedure: 1. Call method to receive packet via Transport Interface. @@ -754,8 +1166,8 @@ def test_timeout_then_send(self, example_addressing_information, payload, addres 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. + :param payload: Payload of UDS message to send. + :param addressing_type: Addressing Type of UDS message to send. """ can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) @@ -776,9 +1188,9 @@ def test_timeout_then_send(self, example_addressing_information, payload, addres ([0x10, 0x81], AddressingType.FUNCTIONAL), ]) @pytest.mark.asyncio - async def test_async_timeout_then_send(self, example_addressing_information, payload, addressing_type): + async def test_async_timeout_then_send_packet(self, example_addressing_information, payload, addressing_type): """ - Check for asynchronous sending a CAN Packet after a timeout exception during asynchronous receiving. + 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. @@ -789,8 +1201,8 @@ async def test_async_timeout_then_send(self, example_addressing_information, pay 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. + :param payload: Payload of UDS message to send. + :param addressing_type: Addressing Type of UDS message to send. """ can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) @@ -806,9 +1218,9 @@ async def test_async_timeout_then_send(self, example_addressing_information, pay assert packet_record.payload == packet.payload == tuple(payload) assert packet_record.can_id == packet.can_id - def test_timeout_then_receive(self, example_addressing_information, example_rx_frame): + def test_timeout_then_receive_packet(self, example_addressing_information, example_rx_frame): """ - Check for receiving a CAN Packet after a timeout exception during receiving. + Check for receiving a CAN packet after a timeout exception during receiving. Procedure: 1. Call method to receive packet via Transport Interface. @@ -831,12 +1243,14 @@ def test_timeout_then_receive(self, example_addressing_information, example_rx_f 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 + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # assert packet_record.transmission_time > datetime_before_send @pytest.mark.asyncio - async def test_async_timeout_then_receive(self, example_addressing_information, example_rx_frame): + async def test_async_timeout_then_receive_packet(self, example_addressing_information, example_rx_frame): """ - Check for asynchronous receiving a CAN Packet after a timeout exception during receiving. + Check for asynchronous receiving a CAN packet after a timeout exception during receiving. Procedure: 1. Call async method to receive packet via Transport Interface. @@ -859,11 +1273,13 @@ async def test_async_timeout_then_receive(self, example_addressing_information, 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 + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # 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. + 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. @@ -875,7 +1291,7 @@ def test_observe_tx_packet(self, example_addressing_information, example_tx_fram :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. + :param example_tx_uds_message: UDS message carried by CAN packet in example_tx_frame. """ can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) @@ -889,12 +1305,15 @@ def test_observe_tx_packet(self, example_addressing_information, example_tx_fram 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 + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # 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): + 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. + 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. @@ -906,7 +1325,7 @@ async def test_async_observe_tx_packet(self, example_addressing_information, exa :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. + :param example_tx_uds_message: UDS message carried by CAN packet in example_tx_frame. """ can_transport_interface = PyCanTransportInterface(can_bus_manager=self.can_interface_1, addressing_information=example_addressing_information) @@ -920,4 +1339,6 @@ async def test_async_observe_tx_packet(self, example_addressing_information, exa 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 + # performance checks + # TODO: https://github.com/mdabrowski1990/uds/issues/228 - uncomment when resolved + # 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 07c155f6..86af748c 100644 --- a/tests/system_tests/transport_interface/conftest.py +++ b/tests/system_tests/transport_interface/conftest.py @@ -1,8 +1,7 @@ from pytest import fixture from can import Message - -from uds.can import CanAddressingInformation, CanAddressingFormat +from uds.can import CanAddressingFormat, CanAddressingInformation from uds.message import UdsMessage from uds.transmission_attributes import AddressingType diff --git a/uds/can/__init__.py b/uds/can/__init__.py index 4c011046..bc6dfd97 100644 --- a/uds/can/__init__.py +++ b/uds/can/__init__.py @@ -15,14 +15,21 @@ - Flow Status """ -from .addressing_format import CanAddressingFormat from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias -from .normal_addressing_information import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation +from .addressing_format import CanAddressingFormat +from .addressing_information import CanAddressingInformation +from .consecutive_frame import CanConsecutiveFrameHandler from .extended_addressing_information import ExtendedCanAddressingInformation +from .first_frame import CanFirstFrameHandler +from .flow_control import ( + AbstractFlowControlParametersGenerator, + CanFlowControlHandler, + CanFlowStatus, + CanSTminTranslator, + DefaultFlowControlParametersGenerator, + FlowControlParametersAlias, +) +from .frame_fields import DEFAULT_FILLER_BYTE, CanDlcHandler, CanIdHandler from .mixed_addressing_information import Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation -from .addressing_information import CanAddressingInformation -from .frame_fields import DEFAULT_FILLER_BYTE, CanIdHandler, CanDlcHandler +from .normal_addressing_information import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation from .single_frame import CanSingleFrameHandler -from .first_frame import CanFirstFrameHandler -from .consecutive_frame import CanConsecutiveFrameHandler -from .flow_control import CanFlowControlHandler, CanFlowStatus, CanSTminTranslator diff --git a/uds/can/abstract_addressing_information.py b/uds/can/abstract_addressing_information.py index 04057099..46020f10 100644 --- a/uds/can/abstract_addressing_information.py +++ b/uds/can/abstract_addressing_information.py @@ -2,11 +2,12 @@ __all__ = ["AbstractCanAddressingInformation", "PacketAIParamsAlias"] -from typing import Optional, TypedDict from abc import ABC, abstractmethod from copy import deepcopy +from typing import Optional, TypedDict from uds.transmission_attributes import AddressingType + from .addressing_format import CanAddressingFormat from .frame_fields import CanIdHandler diff --git a/uds/can/addressing_format.py b/uds/can/addressing_format.py index d0e674c2..166b95db 100644 --- a/uds/can/addressing_format.py +++ b/uds/can/addressing_format.py @@ -2,7 +2,7 @@ __all__ = ["CanAddressingFormat"] -from aenum import unique, StrEnum +from aenum import StrEnum, unique from uds.utilities import ValidatedEnum diff --git a/uds/can/addressing_information.py b/uds/can/addressing_information.py index 5f52e710..c07082d1 100644 --- a/uds/can/addressing_information.py +++ b/uds/can/addressing_information.py @@ -6,17 +6,23 @@ __all__ = ["CanAddressingInformation"] -from typing import Optional, Dict, TypedDict, Type +from typing import Dict, Optional, Type, TypedDict -from uds.utilities import InconsistentArgumentsError, \ - RawBytesAlias, RawBytesListAlias, validate_raw_byte, validate_raw_bytes from uds.transmission_attributes import AddressingType -from .addressing_format import CanAddressingFormat -from .frame_fields import CanIdHandler +from uds.utilities import ( + InconsistentArgumentsError, + RawBytesAlias, + RawBytesListAlias, + validate_raw_byte, + validate_raw_bytes, +) + from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias -from .normal_addressing_information import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation +from .addressing_format import CanAddressingFormat from .extended_addressing_information import ExtendedCanAddressingInformation +from .frame_fields import CanIdHandler from .mixed_addressing_information import Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation +from .normal_addressing_information import Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation class CanAddressingInformation: diff --git a/uds/can/consecutive_frame.py b/uds/can/consecutive_frame.py index ce4a02a6..e3930ce0 100644 --- a/uds/can/consecutive_frame.py +++ b/uds/can/consecutive_frame.py @@ -9,8 +9,15 @@ from typing import Optional -from uds.utilities import RawBytesAlias, RawBytesListAlias, validate_raw_bytes, validate_raw_byte, validate_nibble, \ - InconsistentArgumentsError +from uds.utilities import ( + InconsistentArgumentsError, + RawBytesAlias, + RawBytesListAlias, + validate_nibble, + validate_raw_byte, + validate_raw_bytes, +) + from .addressing_format import CanAddressingFormat from .addressing_information import CanAddressingInformation from .frame_fields import DEFAULT_FILLER_BYTE, CanDlcHandler diff --git a/uds/can/extended_addressing_information.py b/uds/can/extended_addressing_information.py index fb9c32d8..070277db 100644 --- a/uds/can/extended_addressing_information.py +++ b/uds/can/extended_addressing_information.py @@ -4,11 +4,12 @@ from typing import Optional -from uds.utilities import InconsistentArgumentsError, UnusedArgumentError, validate_raw_byte from uds.transmission_attributes import AddressingType +from uds.utilities import InconsistentArgumentsError, UnusedArgumentError, validate_raw_byte + +from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias from .addressing_format import CanAddressingFormat from .frame_fields import CanIdHandler -from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias class ExtendedCanAddressingInformation(AbstractCanAddressingInformation): diff --git a/uds/can/first_frame.py b/uds/can/first_frame.py index 90482f03..ae686170 100644 --- a/uds/can/first_frame.py +++ b/uds/can/first_frame.py @@ -9,8 +9,15 @@ from typing import Optional -from uds.utilities import RawBytesAlias, RawBytesListAlias, int_to_bytes_list, bytes_list_to_int, validate_raw_bytes, \ - InconsistentArgumentsError +from uds.utilities import ( + InconsistentArgumentsError, + RawBytesAlias, + RawBytesListAlias, + bytes_list_to_int, + int_to_bytes_list, + validate_raw_bytes, +) + from .addressing_format import CanAddressingFormat from .addressing_information import CanAddressingInformation from .frame_fields import CanDlcHandler diff --git a/uds/can/flow_control.py b/uds/can/flow_control.py index ebf514ce..35f6dc06 100644 --- a/uds/can/flow_control.py +++ b/uds/can/flow_control.py @@ -7,15 +7,28 @@ - :ref:`Separation Time minimum (STmin) ` """ -__all__ = ["CanFlowStatus", "CanSTminTranslator", "CanFlowControlHandler", "UnrecognizedSTminWarning"] +__all__ = ["CanFlowStatus", "CanSTminTranslator", "CanFlowControlHandler", "UnrecognizedSTminWarning", + "AbstractFlowControlParametersGenerator", "DefaultFlowControlParametersGenerator", + "FlowControlParametersAlias"] -from typing import Optional +from abc import ABC, abstractmethod +from typing import Optional, Tuple from warnings import warn from aenum import unique -from uds.utilities import InconsistentArgumentsError, NibbleEnum, ValidatedEnum, TimeMillisecondsAlias, \ - RawBytesAlias, RawBytesListAlias, validate_nibble, validate_raw_byte, validate_raw_bytes +from uds.utilities import ( + InconsistentArgumentsError, + NibbleEnum, + RawBytesAlias, + RawBytesListAlias, + TimeMillisecondsAlias, + ValidatedEnum, + validate_nibble, + validate_raw_byte, + validate_raw_bytes, +) + from .addressing_format import CanAddressingFormat from .addressing_information import CanAddressingInformation from .frame_fields import DEFAULT_FILLER_BYTE, CanDlcHandler @@ -464,3 +477,85 @@ def __encode_any_flow_status(cls, validate_raw_byte(st_min) output.append(st_min) return output + + +FlowControlParametersAlias = Tuple[int, Optional[int], Optional[int]] +"""Alias of :ref:`Flow Control ` parameters which contains: +- :ref:`Flow Status ` +- :ref:`Block Size ` +- :ref:`Separation Time minimum `""" + + +class AbstractFlowControlParametersGenerator(ABC): + """Definition of Flow Control parameters generator.""" + + def __iter__(self) -> "AbstractFlowControlParametersGenerator": + """Get iterator object - called on each Single Frame reception.""" + return self + + @abstractmethod + def __next__(self) -> FlowControlParametersAlias: + """ + Generate next set of Flow Control parameters - called on each Flow Control message building. + + :return: Tuple with values of Flow Control parameters (Flow Status, Block Size, ST min). + """ + + +class DefaultFlowControlParametersGenerator(AbstractFlowControlParametersGenerator): + """ + Default (recommended to use) Flow Control parameters generator. + + Every generated Flow Control parameters will contain the same (valid) values. + """ + + def __init__(self, block_size: int = 0, st_min: int = 0): + """ + Set values of Block Size and Separation Time minimum parameters to use. + + :param block_size: Value of :ref:`Block Size ` parameter to use. + :param st_min: Value of :ref:`Separation Time minimum ` parameter to use. + """ + self.block_size = block_size + self.st_min = st_min + + def __next__(self): + """ + Generate next set of Flow Control parameters. + + :return: Tuple with values of Flow Control parameters: + - Flow Status=0 (continue to send packets) + - Block Size (user provided) + - Separation Time minimum (user provided) + """ + return CanFlowStatus.ContinueToSend, self.block_size, self.st_min + + @property + def block_size(self) -> int: + """Value of Block Size parameter.""" + return self.__block_size + + @block_size.setter + def block_size(self, value: int): + """ + Set value of Block Size parameter. + + :param value: Value to set. + """ + validate_raw_byte(value) + self.__block_size = value + + @property + def st_min(self) -> int: + """Value of Separation Time minimum parameter.""" + return self.__st_min + + @st_min.setter + def st_min(self, value: int): + """ + Set value of Separation Time minimum parameter. + + :param value: Value to set. + """ + validate_raw_byte(value) + self.__st_min = value diff --git a/uds/can/frame_fields.py b/uds/can/frame_fields.py index b0b8db51..a2a47d77 100644 --- a/uds/can/frame_fields.py +++ b/uds/can/frame_fields.py @@ -9,13 +9,13 @@ __all__ = ["CanIdHandler", "CanDlcHandler", "DEFAULT_FILLER_BYTE"] -from typing import Optional, Tuple, Set, TypedDict, Dict from bisect import bisect_left +from typing import Dict, Optional, Set, Tuple, TypedDict from uds.transmission_attributes import AddressingType from uds.utilities import validate_raw_byte -from .addressing_format import CanAddressingFormat +from .addressing_format import CanAddressingFormat DEFAULT_FILLER_BYTE: int = 0xCC """Default value of Filler Byte. diff --git a/uds/can/mixed_addressing_information.py b/uds/can/mixed_addressing_information.py index c1db6903..a13dd3b2 100644 --- a/uds/can/mixed_addressing_information.py +++ b/uds/can/mixed_addressing_information.py @@ -4,11 +4,12 @@ from typing import Optional -from uds.utilities import InconsistentArgumentsError, UnusedArgumentError, validate_raw_byte from uds.transmission_attributes import AddressingType +from uds.utilities import InconsistentArgumentsError, UnusedArgumentError, validate_raw_byte + +from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias from .addressing_format import CanAddressingFormat from .frame_fields import CanIdHandler -from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias class Mixed11BitCanAddressingInformation(AbstractCanAddressingInformation): diff --git a/uds/can/normal_addressing_information.py b/uds/can/normal_addressing_information.py index 8157c655..6fac3501 100644 --- a/uds/can/normal_addressing_information.py +++ b/uds/can/normal_addressing_information.py @@ -4,11 +4,12 @@ from typing import Optional -from uds.utilities import InconsistentArgumentsError, UnusedArgumentError, validate_raw_byte from uds.transmission_attributes import AddressingType +from uds.utilities import InconsistentArgumentsError, UnusedArgumentError, validate_raw_byte + +from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias from .addressing_format import CanAddressingFormat from .frame_fields import CanIdHandler -from .abstract_addressing_information import AbstractCanAddressingInformation, PacketAIParamsAlias class Normal11BitCanAddressingInformation(AbstractCanAddressingInformation): diff --git a/uds/can/single_frame.py b/uds/can/single_frame.py index d7ec5a9e..6eee7fa9 100644 --- a/uds/can/single_frame.py +++ b/uds/can/single_frame.py @@ -9,8 +9,15 @@ from typing import Optional -from uds.utilities import RawBytesAlias, RawBytesListAlias, validate_raw_bytes, validate_raw_byte, validate_nibble, \ - InconsistentArgumentsError +from uds.utilities import ( + InconsistentArgumentsError, + RawBytesAlias, + RawBytesListAlias, + validate_nibble, + validate_raw_byte, + validate_raw_bytes, +) + from .addressing_format import CanAddressingFormat from .addressing_information import CanAddressingInformation from .frame_fields import DEFAULT_FILLER_BYTE, CanDlcHandler diff --git a/uds/message/__init__.py b/uds/message/__init__.py index d02f900b..5a67c20f 100644 --- a/uds/message/__init__.py +++ b/uds/message/__init__.py @@ -8,7 +8,6 @@ - Negative Response Codes (NRC) definition """ -from .uds_message import AbstractUdsMessageContainer, UdsMessage, UdsMessageRecord -from .service_identifiers import RequestSID, ResponseSID, ALL_REQUEST_SIDS, ALL_RESPONSE_SIDS, \ - UnrecognizedSIDWarning from .nrc import NRC +from .service_identifiers import ALL_REQUEST_SIDS, ALL_RESPONSE_SIDS, RequestSID, ResponseSID, UnrecognizedSIDWarning +from .uds_message import AbstractUdsMessageContainer, UdsMessage, UdsMessageRecord diff --git a/uds/message/nrc.py b/uds/message/nrc.py index b62bfa1f..b193925f 100644 --- a/uds/message/nrc.py +++ b/uds/message/nrc.py @@ -9,7 +9,7 @@ from aenum import unique -from uds.utilities import ByteEnum, ValidatedEnum, ExtendableEnum +from uds.utilities import ByteEnum, ExtendableEnum, ValidatedEnum @unique diff --git a/uds/message/service_identifiers.py b/uds/message/service_identifiers.py index 7ad85123..3536f4d8 100644 --- a/uds/message/service_identifiers.py +++ b/uds/message/service_identifiers.py @@ -11,7 +11,7 @@ from aenum import unique -from uds.utilities import RawBytesSetAlias, ByteEnum, ValidatedEnum, ExtendableEnum +from uds.utilities import ByteEnum, ExtendableEnum, RawBytesSetAlias, ValidatedEnum # reserved SID values _REQUEST_SIDS_DEFINED_BY_SAEJ1979 = set(range(0x01, 0x10)) diff --git a/uds/message/uds_message.py b/uds/message/uds_message.py index 5d10cc9f..a93d4358 100644 --- a/uds/message/uds_message.py +++ b/uds/message/uds_message.py @@ -6,13 +6,13 @@ __all__ = ["AbstractUdsMessageContainer", "UdsMessage", "UdsMessageRecord"] -from typing import Sequence from abc import ABC, abstractmethod from datetime import datetime +from typing import Sequence -from uds.utilities import RawBytesAlias, RawBytesTupleAlias, RawBytesListAlias, validate_raw_bytes, ReassignmentError -from uds.transmission_attributes import TransmissionDirection, AddressingType -from uds.packet import AbstractUdsPacketRecord, PacketsRecordsTuple, PacketsRecordsSequence +from uds.packet import AbstractUdsPacketRecord, PacketsRecordsSequence, PacketsRecordsTuple +from uds.transmission_attributes import AddressingType, TransmissionDirection +from uds.utilities import RawBytesAlias, RawBytesListAlias, RawBytesTupleAlias, ReassignmentError, validate_raw_bytes class AbstractUdsMessageContainer(ABC): diff --git a/uds/packet/__init__.py b/uds/packet/__init__.py index 19b4e827..39306295 100644 --- a/uds/packet/__init__.py +++ b/uds/packet/__init__.py @@ -8,10 +8,17 @@ - storing historic information about packets that were either received or transmitted """ -from .abstract_packet_type import AbstractUdsPacketType -from .can_packet_type import CanPacketType -from .abstract_packet import AbstractUdsPacketContainer, AbstractUdsPacket, AbstractUdsPacketRecord, \ - PacketsContainersSequence, PacketsTuple, PacketsRecordsTuple, PacketsRecordsSequence from .abstract_can_packet_container import AbstractCanPacketContainer +from .abstract_packet import ( + AbstractUdsPacket, + AbstractUdsPacketContainer, + AbstractUdsPacketRecord, + PacketsContainersSequence, + PacketsRecordsSequence, + PacketsRecordsTuple, + PacketsTuple, +) +from .abstract_packet_type import AbstractUdsPacketType from .can_packet import CanPacket from .can_packet_record import CanPacketRecord +from .can_packet_type import CanPacketType diff --git a/uds/packet/abstract_can_packet_container.py b/uds/packet/abstract_can_packet_container.py index cbc5853f..34f4e627 100644 --- a/uds/packet/abstract_can_packet_container.py +++ b/uds/packet/abstract_can_packet_container.py @@ -5,10 +5,20 @@ from abc import ABC, abstractmethod from typing import Optional -from uds.utilities import RawBytesTupleAlias +from uds.can import ( + AbstractCanAddressingInformation, + CanAddressingFormat, + CanAddressingInformation, + CanConsecutiveFrameHandler, + CanDlcHandler, + CanFirstFrameHandler, + CanFlowControlHandler, + CanFlowStatus, + CanSingleFrameHandler, +) from uds.transmission_attributes import AddressingType -from uds.can import AbstractCanAddressingInformation, CanAddressingInformation, CanAddressingFormat, CanDlcHandler, \ - CanSingleFrameHandler, CanFirstFrameHandler, CanConsecutiveFrameHandler, CanFlowControlHandler, CanFlowStatus +from uds.utilities import RawBytesTupleAlias + from .can_packet_type import CanPacketType diff --git a/uds/packet/abstract_packet.py b/uds/packet/abstract_packet.py index e0a9c698..29fccf42 100644 --- a/uds/packet/abstract_packet.py +++ b/uds/packet/abstract_packet.py @@ -4,12 +4,13 @@ "PacketsContainersSequence", "PacketsTuple", "PacketsRecordsTuple", "PacketsRecordsSequence"] from abc import ABC, abstractmethod -from typing import Any, Optional, Tuple, Sequence from datetime import datetime +from typing import Any, Optional, Sequence, Tuple -from uds.utilities import RawBytesTupleAlias, ReassignmentError from uds.transmission_attributes.addressing import AddressingType from uds.transmission_attributes.transmission_direction import TransmissionDirection +from uds.utilities import RawBytesTupleAlias, ReassignmentError + from .abstract_packet_type import AbstractUdsPacketType diff --git a/uds/packet/abstract_packet_type.py b/uds/packet/abstract_packet_type.py index 63db1b46..b21c1d26 100644 --- a/uds/packet/abstract_packet_type.py +++ b/uds/packet/abstract_packet_type.py @@ -4,7 +4,7 @@ from abc import abstractmethod -from uds.utilities import NibbleEnum, ValidatedEnum, ExtendableEnum +from uds.utilities import ExtendableEnum, NibbleEnum, ValidatedEnum class AbstractUdsPacketType(NibbleEnum, ValidatedEnum, ExtendableEnum): diff --git a/uds/packet/can_packet.py b/uds/packet/can_packet.py index a761e50e..1ef74666 100644 --- a/uds/packet/can_packet.py +++ b/uds/packet/can_packet.py @@ -2,17 +2,29 @@ __all__ = ["CanPacket"] -from typing import Optional, Any +from typing import Any, Optional from warnings import warn -from uds.utilities import AmbiguityError, UnusedArgumentWarning, RawBytesAlias, RawBytesTupleAlias +from uds.can import ( + DEFAULT_FILLER_BYTE, + AbstractCanAddressingInformation, + CanAddressingFormat, + CanAddressingInformation, + CanConsecutiveFrameHandler, + CanDlcHandler, + CanFirstFrameHandler, + CanFlowControlHandler, + CanFlowStatus, + CanSingleFrameHandler, + ExtendedCanAddressingInformation, + Mixed11BitCanAddressingInformation, + Mixed29BitCanAddressingInformation, + Normal11BitCanAddressingInformation, + NormalFixedCanAddressingInformation, +) from uds.transmission_attributes import AddressingType -from uds.can import DEFAULT_FILLER_BYTE, CanDlcHandler, \ - CanAddressingFormat, AbstractCanAddressingInformation, CanAddressingInformation, \ - Normal11BitCanAddressingInformation, NormalFixedCanAddressingInformation, ExtendedCanAddressingInformation, \ - Mixed11BitCanAddressingInformation, Mixed29BitCanAddressingInformation, \ - CanSingleFrameHandler, CanFirstFrameHandler, CanConsecutiveFrameHandler, \ - CanFlowControlHandler, CanFlowStatus +from uds.utilities import AmbiguityError, RawBytesAlias, RawBytesTupleAlias, UnusedArgumentWarning + from .abstract_can_packet_container import AbstractCanPacketContainer from .abstract_packet import AbstractUdsPacket from .can_packet_type import CanPacketType diff --git a/uds/packet/can_packet_record.py b/uds/packet/can_packet_record.py index 67139fa6..9ee1cea0 100644 --- a/uds/packet/can_packet_record.py +++ b/uds/packet/can_packet_record.py @@ -2,20 +2,23 @@ __all__ = ["CanPacketRecord"] -from typing import Union, Any, Optional from datetime import datetime +from typing import Any, Optional, Union from can import Message as PythonCanMessage - -from uds.utilities import RawBytesTupleAlias, InconsistentArgumentsError +from uds.can import ( + AbstractCanAddressingInformation, + CanAddressingFormat, + CanAddressingInformation, + CanDlcHandler, + CanIdHandler, +) from uds.transmission_attributes import AddressingType, TransmissionDirection -from uds.can import CanAddressingFormat, \ - CanAddressingInformation, AbstractCanAddressingInformation, \ - CanDlcHandler, CanIdHandler -from .can_packet_type import CanPacketType +from uds.utilities import InconsistentArgumentsError, RawBytesTupleAlias + from .abstract_can_packet_container import AbstractCanPacketContainer from .abstract_packet import AbstractUdsPacketRecord - +from .can_packet_type import CanPacketType CanFrameAlias = Union[PythonCanMessage] """Alias of supported CAN frames objects.""" diff --git a/uds/packet/can_packet_type.py b/uds/packet/can_packet_type.py index 85f1d8e4..20db7d14 100644 --- a/uds/packet/can_packet_type.py +++ b/uds/packet/can_packet_type.py @@ -4,7 +4,8 @@ from aenum import unique -from uds.can import CanSingleFrameHandler, CanFirstFrameHandler, CanConsecutiveFrameHandler, CanFlowControlHandler +from uds.can import CanConsecutiveFrameHandler, CanFirstFrameHandler, CanFlowControlHandler, CanSingleFrameHandler + from .abstract_packet_type import AbstractUdsPacketType diff --git a/uds/segmentation/__init__.py b/uds/segmentation/__init__.py index 81c7501e..eb43d1b2 100644 --- a/uds/segmentation/__init__.py +++ b/uds/segmentation/__init__.py @@ -10,5 +10,5 @@ - classes that handles segmentation for each bus """ -from .abstract_segmenter import SegmentationError, AbstractSegmenter +from .abstract_segmenter import AbstractSegmenter, SegmentationError from .can_segmenter import CanSegmenter diff --git a/uds/segmentation/abstract_segmenter.py b/uds/segmentation/abstract_segmenter.py index b35939bd..942fdc17 100644 --- a/uds/segmentation/abstract_segmenter.py +++ b/uds/segmentation/abstract_segmenter.py @@ -2,12 +2,17 @@ __all__ = ["SegmentationError", "AbstractSegmenter"] -from typing import Optional, Union, Sequence, Type from abc import ABC, abstractmethod +from typing import Optional, Sequence, Type, Union from uds.message import UdsMessage, UdsMessageRecord -from uds.packet import AbstractUdsPacketContainer, AbstractUdsPacket, AbstractUdsPacketRecord, \ - PacketsContainersSequence, PacketsTuple +from uds.packet import ( + AbstractUdsPacket, + AbstractUdsPacketContainer, + AbstractUdsPacketRecord, + PacketsContainersSequence, + PacketsTuple, +) from uds.transmission_attributes import AddressingType diff --git a/uds/segmentation/can_segmenter.py b/uds/segmentation/can_segmenter.py index cd4e233f..65be7de8 100644 --- a/uds/segmentation/can_segmenter.py +++ b/uds/segmentation/can_segmenter.py @@ -2,16 +2,31 @@ __all__ = ["CanSegmenter"] -from typing import Optional, Union, Type - -from uds.utilities import RawBytesListAlias, RawBytesAlias, validate_raw_byte -from uds.transmission_attributes import AddressingType -from uds.can import AbstractCanAddressingInformation, CanAddressingInformation, PacketAIParamsAlias, \ - CanAddressingFormat, \ - CanDlcHandler, CanSingleFrameHandler, CanFirstFrameHandler, CanConsecutiveFrameHandler, DEFAULT_FILLER_BYTE -from uds.packet import CanPacket, CanPacketRecord, CanPacketType, \ - AbstractUdsPacket, AbstractUdsPacketRecord, PacketsContainersSequence, PacketsTuple +from typing import Optional, Tuple, Type, Union + +from uds.can import ( + DEFAULT_FILLER_BYTE, + AbstractCanAddressingInformation, + CanAddressingFormat, + CanAddressingInformation, + CanConsecutiveFrameHandler, + CanDlcHandler, + CanFirstFrameHandler, + CanSingleFrameHandler, + PacketAIParamsAlias, +) from uds.message import UdsMessage, UdsMessageRecord +from uds.packet import ( + AbstractUdsPacket, + AbstractUdsPacketRecord, + CanPacket, + CanPacketRecord, + CanPacketType, + PacketsContainersSequence, +) +from uds.transmission_attributes import AddressingType +from uds.utilities import RawBytesAlias, RawBytesListAlias, validate_raw_byte + from .abstract_segmenter import AbstractSegmenter, SegmentationError @@ -241,7 +256,7 @@ def desegmentation(self, packets: PacketsContainersSequence) -> Union[UdsMessage raise SegmentationError("Unexpectedly, something went wrong...") raise NotImplementedError(f"Missing implementation for provided CAN Packet: {type(packets[0])}") - def segmentation(self, message: UdsMessage) -> PacketsTuple: + def segmentation(self, message: UdsMessage) -> Tuple[CanPacket, ...]: """ Perform segmentation of a diagnostic message. @@ -262,7 +277,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: + def __physical_segmentation(self, message: UdsMessage) -> Tuple[CanPacket, ...]: """ Segment physically addressed diagnostic message. @@ -311,7 +326,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) -> Tuple[CanPacket, ...]: """ Segment functionally addressed diagnostic message. diff --git a/uds/transport_interface/abstract_transport_interface.py b/uds/transport_interface/abstract_transport_interface.py index 910354ad..365a90c6 100644 --- a/uds/transport_interface/abstract_transport_interface.py +++ b/uds/transport_interface/abstract_transport_interface.py @@ -2,13 +2,14 @@ __all__ = ["AbstractTransportInterface"] -from typing import Optional, Any from abc import ABC, abstractmethod from asyncio import AbstractEventLoop +from typing import Any, Optional -from uds.utilities import TimeMillisecondsAlias +from uds.message import UdsMessage, UdsMessageRecord from uds.packet import AbstractUdsPacket, AbstractUdsPacketRecord from uds.segmentation import AbstractSegmenter +from uds.utilities import TimeMillisecondsAlias class AbstractTransportInterface(ABC): @@ -87,7 +88,7 @@ async def async_send_packet(self, packet: AbstractUdsPacket, loop: Optional[AbstractEventLoop] = None) -> AbstractUdsPacketRecord: """ - Transmit UDS packet asynchronously. + Transmit asynchronously UDS packet. :param packet: A packet to send. :param loop: An asyncio event loop to use for scheduling this task. @@ -100,7 +101,7 @@ async def async_receive_packet(self, timeout: Optional[TimeMillisecondsAlias] = None, loop: Optional[AbstractEventLoop] = None) -> AbstractUdsPacketRecord: """ - Receive UDS packet asynchronously. + Receive asynchronously UDS packet. :param timeout: Maximal time (in milliseconds) to wait. :param loop: An asyncio event loop to use for scheduling this task. @@ -110,3 +111,51 @@ async def async_receive_packet(self, :return: Record with historic information about received 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_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> 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. + """ + + @abstractmethod + async def async_send_message(self, + message: UdsMessage, + loop: Optional[AbstractEventLoop] = None) -> UdsMessageRecord: + """ + Transmit asynchronously UDS message. + + :param message: A message to send. + :param loop: An asyncio event loop to use for scheduling this task. + + :return: Record with historic information about transmitted UDS message. + """ + + @abstractmethod + async def async_receive_message(self, + timeout: Optional[TimeMillisecondsAlias] = None, + loop: Optional[AbstractEventLoop] = None) -> UdsMessageRecord: + """ + Receive asynchronously UDS message. + + :param timeout: Maximal time (in milliseconds) to wait. + :param loop: An asyncio event loop to use for scheduling this task. + + :return: Record with historic information about received UDS message. + """ diff --git a/uds/transport_interface/can_transport_interface.py b/uds/transport_interface/can_transport_interface.py index d721ed93..c48af92a 100644 --- a/uds/transport_interface/can_transport_interface.py +++ b/uds/transport_interface/can_transport_interface.py @@ -2,20 +2,27 @@ __all__ = ["AbstractCanTransportInterface", "PyCanTransportInterface"] -from typing import Any, Optional from abc import abstractmethod -from warnings import warn -from asyncio import AbstractEventLoop, wait_for, get_running_loop +from asyncio import AbstractEventLoop, get_running_loop, wait_for from datetime import datetime from time import time +from typing import Any, Optional +from warnings import warn -from can import BusABC, AsyncBufferedReader, BufferedReader, Notifier, Message - -from uds.utilities import TimeMillisecondsAlias, ValueWarning -from uds.can import AbstractCanAddressingInformation, CanIdHandler, CanDlcHandler +from can import AsyncBufferedReader, BufferedReader, BusABC, Message, Notifier +from uds.can import ( + AbstractCanAddressingInformation, + AbstractFlowControlParametersGenerator, + CanDlcHandler, + CanIdHandler, + DefaultFlowControlParametersGenerator, +) +from uds.message import UdsMessage, UdsMessageRecord from uds.packet import CanPacket, CanPacketRecord, CanPacketType -from uds.transmission_attributes import TransmissionDirection from uds.segmentation import CanSegmenter +from uds.transmission_attributes import TransmissionDirection +from uds.utilities import TimeMillisecondsAlias, ValueWarning + from .abstract_transport_interface import AbstractTransportInterface @@ -39,6 +46,10 @@ class AbstractCanTransportInterface(AbstractTransportInterface): """Default value of :ref:`N_Br ` time parameter.""" DEFAULT_N_CS: Optional[TimeMillisecondsAlias] = None """Default value of :ref:`N_Cs ` time parameter.""" + DEFAULT_FLOW_CONTROL_PARAMETERS = DefaultFlowControlParametersGenerator() + """Default value of :ref:`Flow Control ` parameters ( + :ref:`Flow Status `, :ref:`Block Size `, + :ref:`Separation Time minimum `).""" def __init__(self, can_bus_manager: Any, @@ -60,6 +71,7 @@ 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_parameters_generator: Generator with Flow Control parameters to use. :raise TypeError: Provided Addressing Information value has unexpected type. """ @@ -73,6 +85,8 @@ def __init__(self, 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.flow_control_parameters_generator = kwargs.pop("flow_control_parameters_generator", + self.DEFAULT_FLOW_CONTROL_PARAMETERS) self.__segmenter = CanSegmenter(addressing_information=addressing_information, **kwargs) @property @@ -353,6 +367,22 @@ def filler_byte(self, value: int): """ self.segmenter.filler_byte = value + @property + def flow_control_parameters_generator(self) -> AbstractFlowControlParametersGenerator: + """Get generator of Flow Control parameters (Flow Status, Block Size, Separation Time minimum).""" + return self.__flow_control_parameters_generator + + @flow_control_parameters_generator.setter + def flow_control_parameters_generator(self, value: AbstractFlowControlParametersGenerator): + """ + Set value of Flow Control parameters (Flow Status, Block Size, Separation Time minimum) generator. + + :param value: Value to set. + """ + if not isinstance(value, AbstractFlowControlParametersGenerator): + raise TypeError("Provided Flow Control parameters generator value has incorrect type.") + self.__flow_control_parameters_generator = value + class PyCanTransportInterface(AbstractCanTransportInterface): """ @@ -363,7 +393,7 @@ class PyCanTransportInterface(AbstractCanTransportInterface): _MAX_LISTENER_TIMEOUT: float = 4280000. # ms """Maximal timeout value accepted by python-can listeners.""" - _MIN_NOTIFIER_TIMEOUT: float = 0.0000001 # s + _MIN_NOTIFIER_TIMEOUT: float = 0.001 # s """Minimal timeout for notifiers that does not cause malfunctioning of listeners.""" def __init__(self, @@ -389,6 +419,7 @@ 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_parameters_generator: Generator with Flow Control parameters to use. """ self.__n_as_measured: Optional[TimeMillisecondsAlias] = None self.__n_ar_measured: Optional[TimeMillisecondsAlias] = None @@ -534,19 +565,18 @@ def send_packet(self, packet: CanPacket) -> CanPacketRecord: # type: ignore 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, - is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) + can_frame = 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._setup_notifier() self.clear_frames_buffers() - self.bus_manager.send(can_message) + self.bus_manager.send(can_frame) 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 observed_frame.timestamp < time_start: + 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.") @@ -603,7 +633,7 @@ async def async_send_packet(self, packet: CanPacket, # type: ignore loop: Optional[AbstractEventLoop] = None) -> CanPacketRecord: """ - Transmit CAN packet. + Transmit asynchronously CAN packet. :param packet: CAN packet to send. :param loop: An asyncio event loop used for observing messages. @@ -620,17 +650,16 @@ async def async_send_packet(self, 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, - is_fd=CanDlcHandler.is_can_fd_specific_dlc(packet.dlc)) - self.bus_manager.send(can_message) + can_frame = 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_frame) 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 observed_frame.timestamp < time_start: + or not observed_frame.is_rx: 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: @@ -647,7 +676,7 @@ async def async_receive_packet(self, timeout: Optional[TimeMillisecondsAlias] = None, loop: Optional[AbstractEventLoop] = None) -> CanPacketRecord: """ - Receive CAN packet. + Receive asynchronously CAN packet. :param timeout: Maximal time (in milliseconds) to wait. :param loop: An asyncio event loop used for observing messages. @@ -683,3 +712,87 @@ async def async_receive_packet(self, addressing_type=packet_addressing_type, addressing_format=self.segmenter.addressing_format, transmission_time=datetime.fromtimestamp(received_frame.timestamp)) + + def send_message(self, message: UdsMessage) -> UdsMessageRecord: + """ + Transmit UDS message over CAN. + + :param message: A message to send. + + :return: Record with historic information about transmitted UDS message. + """ + packets_to_send = self.segmenter.segmentation(message) + if len(packets_to_send) == 1: + packet_record = self.send_packet(*packets_to_send) + return UdsMessageRecord((packet_record,)) + raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/267") + + def receive_message(self, timeout: Optional[TimeMillisecondsAlias] = None) -> UdsMessageRecord: + """ + Receive UDS message over CAN. + + :param timeout: Maximal time (in milliseconds) to wait for UDS message transmission to start. + This means that receiving might last longer if First Frame was received within provided time. + + :raise TypeError: Provided timeout value is not None neither int nor float type. + :raise ValueError: Provided timeout value is less or equal 0. + :raise TimeoutError: Timeout was reached. + Either Single Frame / First Frame not received within [timeout] ms + or N_As, N_Ar, N_Bs, N_Cr timeout reached. + + :return: Record with historic information about received UDS message. + """ + if timeout is not None: + if not isinstance(timeout, (int, float)): + raise TypeError("Provided timeout value is not None neither int nor float type.") + if timeout <= 0: + raise ValueError("Provided timeout value is less or equal 0.") + received_packet = self.receive_packet(timeout=timeout) + if received_packet.packet_type == CanPacketType.SINGLE_FRAME: + return UdsMessageRecord([received_packet]) + raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/266") + + async def async_send_message(self, + message: UdsMessage, + loop: Optional[AbstractEventLoop] = None) -> UdsMessageRecord: + """ + Transmit asynchronously UDS message over CAN. + + :param message: A message to send. + :param loop: An asyncio event loop to use for scheduling this task. + + :return: Record with historic information about transmitted UDS message. + """ + packets_to_send = self.segmenter.segmentation(message) + if len(packets_to_send) == 1: + packet_record = await self.async_send_packet(*packets_to_send, loop=loop) + return UdsMessageRecord((packet_record,)) # type + raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/267") + + async def async_receive_message(self, + timeout: Optional[TimeMillisecondsAlias] = None, + loop: Optional[AbstractEventLoop] = None) -> UdsMessageRecord: + """ + Receive asynchronously UDS message over CAN. + + :param timeout: Maximal time (in milliseconds) to wait for UDS message transmission to start. + This means that receiving might last longer if First Frame was received within provided time. + :param loop: An asyncio event loop to use for scheduling this task. + + :raise TypeError: Provided timeout value is not None neither int nor float type. + :raise ValueError: Provided timeout value is less or equal 0. + :raise TimeoutError: Timeout was reached. + Either Single Frame / First Frame not received within [timeout] ms + or N_As, N_Ar, N_Bs, N_Cr timeout reached. + + :return: Record with historic information about received UDS message. + """ + if timeout is not None: + if not isinstance(timeout, (int, float)): + raise TypeError("Provided timeout value is not None neither int nor float type.") + if timeout <= 0: + raise ValueError("Provided timeout value is less or equal 0.") + received_packet = await self.async_receive_packet(timeout=timeout, loop=loop) + if received_packet.packet_type == CanPacketType.SINGLE_FRAME: + return UdsMessageRecord([received_packet]) + raise NotImplementedError("TODO: https://github.com/mdabrowski1990/uds/issues/266") diff --git a/uds/utilities/__init__.py b/uds/utilities/__init__.py index b69b488c..519ac3a5 100644 --- a/uds/utilities/__init__.py +++ b/uds/utilities/__init__.py @@ -1,9 +1,16 @@ """Various helper functions, classes and variables that are shared and reused within the project.""" -from .enums import ValidatedEnum, ExtendableEnum, ByteEnum, NibbleEnum -from .common_types import TimeMillisecondsAlias, \ - RawBytesAlias, RawBytesTupleAlias, RawBytesListAlias, RawBytesSetAlias, \ - validate_nibble, validate_raw_bytes, validate_raw_byte -from .bytes_operations import Endianness, int_to_bytes_list, bytes_list_to_int -from .custom_exceptions import ReassignmentError, InconsistentArgumentsError, AmbiguityError, UnusedArgumentError +from .bytes_operations import Endianness, bytes_list_to_int, int_to_bytes_list +from .common_types import ( + RawBytesAlias, + RawBytesListAlias, + RawBytesSetAlias, + RawBytesTupleAlias, + TimeMillisecondsAlias, + validate_nibble, + validate_raw_byte, + validate_raw_bytes, +) +from .custom_exceptions import AmbiguityError, InconsistentArgumentsError, ReassignmentError, UnusedArgumentError from .custom_warnings import UnusedArgumentWarning, ValueWarning +from .enums import ByteEnum, ExtendableEnum, NibbleEnum, ValidatedEnum diff --git a/uds/utilities/bytes_operations.py b/uds/utilities/bytes_operations.py index 90af6ca4..36a34ba0 100644 --- a/uds/utilities/bytes_operations.py +++ b/uds/utilities/bytes_operations.py @@ -6,9 +6,9 @@ from aenum import StrEnum -from .enums import ValidatedEnum -from .common_types import RawBytesListAlias, RawBytesAlias, validate_raw_bytes +from .common_types import RawBytesAlias, RawBytesListAlias, validate_raw_bytes from .custom_exceptions import InconsistentArgumentsError +from .enums import ValidatedEnum class Endianness(StrEnum, ValidatedEnum): diff --git a/uds/utilities/common_types.py b/uds/utilities/common_types.py index 8b6d0067..7a20d1fe 100644 --- a/uds/utilities/common_types.py +++ b/uds/utilities/common_types.py @@ -3,8 +3,7 @@ __all__ = ["TimeMillisecondsAlias", "RawBytesAlias", "RawBytesTupleAlias", "RawBytesListAlias", "RawBytesSetAlias", "validate_nibble", "validate_raw_byte", "validate_raw_bytes"] -from typing import Union, Tuple, List, Set - +from typing import List, Set, Tuple, Union TimeMillisecondsAlias = Union[int, float] """Alias of a time value in milliseconds."""